Java多线程-Lock锁

x33g5p2x  于2021-10-04 转载在 Java  
字(5.6k)|赞(0)|评价(0)|浏览(370)

介绍

在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。

Lock 接口与synchronized关键字的区别

Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。

Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。

Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。

lock使用方法

在Java多线程中,可以使用synchronized关键字实现线程之间的同步互斥,在jdk1.5后新增的ReentrantLock类同样可达到此效果,且在使用上比synchronized更加灵活。

创建成员属性 private Lock lock = new ReentrantLock();

在需要锁的代码前加上lock.lock(); 进行上锁

在需要锁的代码结束位置lock.unlock(); 释放锁

使用lock的使用 尽量使用try catch finally

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread implements Runnable {
    public  static  Integer num=1000;

    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
        try {
                lock.lock();//上锁
                if (num<=0){
                  break;
                }
            System.out.println(Thread.currentThread().getName()+"___"+num);
            num--;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
        }
    }

}
public static void main(String[] args) {
        MyThread myThread = new MyThread();
        for (int i = 0; i < 3; i++) {
            new Thread(myThread ).start();
        }

    }

注意:

使用lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象

使用Condition实现等待/通知

synchronized关键字结合wait()和notify()及notifyAll()方法的使用可以实现线程的等待与通知模式。在使用notify()、notifyAll()方法进行通知时,被通知的线程是JVM随机选择的。

ReentrantLock类同样可以实现该功能,需要借助Condition对象,可实现“选择性通知”。Condition类是jdk1.5提供的,且在一个Lock对象中可以创建多个Condition(对象监视器)实例。

注意

await() 和 signal() 必须在lock.lock(); 加锁后才能使用

和wait()和notify() 必须在synchronized使用原理一样

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyCondition implements Runnable{
    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();
    public void run() {
        try {
            //上锁
            lock.lock();
            System.out.println(" 开始等待时间:"+System.currentTimeMillis());
            System.out.println("我陷入了等待...");
            //线程等待
            condition.await();
            //释放锁
            lock.unlock();
            System.out.println("锁释放了!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //通知方法
    public void signal(){
        try {
            上锁
            lock.lock();
            System.out.println("结束等待时间:"+System.currentTimeMillis());
            //通知等待线程
            condition.signal();
        } finally {
            lock.unlock();//释放锁
        }
    }
}
public class MyLock{
    public static void main(String[] args) throws InterruptedException {
        MyCondition myCondition = new MyCondition();
        Thread thread1 = new Thread(myCondition,"线程1");
        thread1.start();
        Thread.sleep(3000);
        myCondition.signal();
    }
}

Object类中的wait(long timeout)方法等同于Condition类中的await(long time,TimeUnit unit)方法。

Object类中的notify()方法等同于Condition类中的singal()方法。

Object类中的notifyAll()方法等同于Condition类中的singalAll()方法。

注意 同一个 Condition对象才能唤醒等待的线程 这样的好处就是可以控制到底唤醒那个线程了

比如上面代码 condition.await(); 那么只能使用 condition.signal(); 进行唤醒 而不是condition1 condition2

而不像Object类中wait,和notify()… 随机进行唤醒正在等待状态的线程线程 如果有多个线程都在等待状态那么不好进行控制

公平锁与非公平锁

锁Lock分为“公平锁”和“非公平锁”。

公平锁:表示线程获取锁的顺序是按照线程加锁的顺序来的进行分配的,即先来先得FIFO先进先出顺序。 (true) 不会出现一个线程执行多次的结果

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread implements Runnable {

    public  static  Integer num=1000;
    private ReentrantLock lock;
    public  MyThread(boolean isFair) {
        lock = new ReentrantLock(isFair);
    }
    
    @Override
    public void run() {
        while (true){
            try {
                lock.lock();//上锁
                if (num<=0){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"___"+num);
                num--;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();//释放锁
            }
        }
    }
    
}
public static void main(String[] args) {
        MyThread myThread = new MyThread(true);
        for (int i = 0; i < 3; i++) {
            new Thread((Runnable) myThread).start();
        }

    }
Thread-1___1000
Thread-2___999
Thread-0___998
Thread-1___997
Thread-2___996
Thread-0___995
Thread-1___994
Thread-2___993
Thread-0___992

非公平锁(默认):一种获取锁的抢占机制,是随机拿到锁的,和公平锁不一样的是先来的不一定先拿到锁,这个方式可能造成某些线程一直拿不到锁,结果就是不公平的·。 (false)

public static void main(String[] args) {
		//默认或者设置当前为false
		final MyService myService = new MyService(false);
private ReentrantLock lock;
	public MyService(boolean isFair) {
		lock = new ReentrantLock(isFair);
	}

类似于这种

Thread-0___1000
Thread-0___999
Thread-0___998
Thread-0___997
Thread-0___996
Thread-0___995
Thread-0___994

其他线程有可能一直拿不到锁

读写锁表示两个锁

主要的作用我们会有一种需求,在对数据进行读写的时候,为了保证数据的一致性和完整性

需要读和写是互斥的,写和写是互斥的,但是读和读是不需要互斥的,这样读和读不互斥性能更高些

private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

支持公平锁

读操作相关的锁,也成为共享锁。 和没锁一样

lock.readLock().lock(); 读锁

lock.readLock().unlock();释放读锁

同一时间允许多个线程执行lock()后的代码

写操作相关的锁,也叫排他锁。 和同步一个原理

lock.writeLock().lock(); 写锁

lock.writeLock().unlock();释放写锁

同一时间只允许一个线程执行lock()后的代码。

未使用读写锁

Thread-2 have read data: 1000
Thread-3 have read data: 1000
Thread-6 have read data: 999
Thread-1 have read data: 1000
Thread-0 have read data: 1000
Thread-5 have read data: 995
Thread-8 have read data: 995
Thread-4 have read data: 995
Thread-10 have read data: 993
Thread-7 have read data: 994
Thread-9 have read data: 991
Thread-11 have read data: 992

读取了修改前的数据了 因为我在写的过程中还能被其他线程读取到数据

解决办法使用了读写锁 让读写分离

public class test {

    private  static Integer data = 1000; //共享数据,只能有一个线程能写该数据,但可以有多个线程同时读
  static   ReadWriteLock  lock = new ReentrantReadWriteLock();
   
    public static void main(String[] args) {
        for(int i=0;i<1000;i++){    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        lock.readLock().lock();
                        System.out.println(Thread.currentThread().getName()+" have read data: "+data);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }finally{
                        lock.readLock().unlock();
                    }
                    try {
                        lock.writeLock().lock();
                        data = data-1;
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        lock.writeLock().unlock();
                    }
                }
            }).start();

        }

    }

}
Thread-11 have read data: 991
Thread-13 have read data: 991
Thread-10 have read data: 991
Thread-14 have read data: 991
Thread-12 have read data: 991
Thread-23 have read data: 991
Thread-29 have read data: 988
Thread-18 have read data: 988
Thread-31 have read data: 986
Thread-32 have read data: 986
Thread-20 have read data: 986
Thread-33 have read data: 986
Thread-19 have read data: 986
Thread-25 have read data: 980
Thread-37 have read data: 978

可以看出来了吧

相关文章