多线程编程学习四(Lock 的使用)

时间:2022-05-04
本文章向大家介绍多线程编程学习四(Lock 的使用),主要内容包括一、前言、二、使用ReentrantLock 类、三、使用ReentrantReadWriteLock 类、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

一、前言

    本文要介绍使用Java5中 Lock 对象,同样也能实现同步的效果,而且在使用上更加方便、灵活,主要包括 ReentrantLock 类的使用和ReentrantReadWriteLock 类的使用。

二、使用ReentrantLock 类

1、在java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5中新增加的ReentrantLock也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比synchronized更加的灵活。

2、调用lock.lock()代码的线程就持有了“对象监视器”,即lock 持有的是对象锁,依赖于该类的实例存在。

public class MyService {
    private Lock lock=new ReentrantLock();
    public void testMethod(){
        lock.lock();
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+(i+1));
        }
        lock.unlock();
    }
}

3、关键字synchronized 与wait() 和 notify()/notifyAll() 方法相结合可以实现等待/通知模式,类ReentrantLock 也可以实现同样的功能,但需要借助于Condition对象。

Object类中的wait()方法相当于Condition类中的await()方法

Object类中的wait(long timeout)方法相当于Condition类中的await(long time,TimeUnit unit)方法

Object类中的notify()方法相当于Condition类中的signal()方法

Object类中的notifyAll()方法相当于Condition类中的signalAll()方法

public class Myservice {
    private Lock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();

    //等待
    public void waitMethod(){
        try {
            lock.lock();
            System.out.println("A");
            condition.await();//调用的Condition的await等待方法也需要在同步方法中,否则会报错
            System.out.println("B");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    //唤醒
    public void signal(){
        try {
            lock.lock();
            System.out.println("现在开始唤醒...");
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
}

4、使用多个Condition对象 实现线程之间的选择性通知。

public class MyService {
    private Lock lock=new ReentrantLock();
    //通过定义多个Condition实现选择性通知,可以唤醒指定种类的线程,这是
    //控制部分线程行为的方便形式
    private Condition conditionA=lock.newCondition();
    private Condition conditionB=lock.newCondition();

    public void awaitA(){
        try {
            lock.lock();
            System.out.println("awaitA begin");
            conditionA.await();
            System.out.println("awaitA end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void awaitB(){
        try {
            lock.lock();
            System.out.println("awaitB begin");
            conditionB.await();
            System.out.println("awaitB end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void signalA(){
        try {
            lock.lock();
            System.out.println("现在开始唤醒awaitA");
            conditionA.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void signalB(){
        try {
            lock.lock();
            System.out.println("现在开始唤醒awaitB");
            conditionB.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
public class Run
{
    public static void main(String[] args) throws InterruptedException
    {
        MyService myService=new MyService();
        Thread threadA=new Thread(){
            @Override
            public void run()
            {
                super.run();
                myService.awaitA();
            }
        };

        Thread threadB=new Thread(){
            @Override
            public void run()
            {
                super.run();
                myService.awaitB();
            }
        };

        threadA.start();
        threadB.start();
        Thread.sleep(1000);
        myService.signalA();
        Thread.sleep(1000);
        myService.signalB();

    }
}

5、公平锁和非公平锁

公平锁:表示线程获得锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。

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

public class Service {
    private Lock lock;

    public Service(boolean isFair)
    {
        //通过这种方式创建公平锁(true)和非公平锁(false)
        lock=new ReentrantLock(isFair);
    }

    public void methodA(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"正在运行");
        }finally {
            lock.unlock();
        }
    }
}
public class Run {
    public static void main(String[] args)
    {
        final Service service=new Service(true);
        Runnable runnable=new Runnable() {
            @Override
            public void run()
            {
             service.methodA();
            }
        };

        Thread[] threads=new Thread[10];
        for (int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
            threads[i].setName("线程"+(i+1));
            threads[i].start();
        }
    }
}

6、ReentrantLock 常用方法介绍

(1) int getHoldCount() 查询当前线程保持此锁定的个数,也就是线程中调用lock方法的次数。

(2) int getQueueLength() 返回正等待此锁定的线程估计数,比如有5个线程,1个线程正占用了这个Lock锁在执行,则调用此方法返回的就是4。该值仅是估计的数字,因为在此方法遍历内部数据结构的同时,线程的数目可能动态地变化。此方法用于监视系统状态,不用于同步控制。

(3) int getWaitQueueLength(Condition condition) 返回等待与此锁定相关的给定条件Condition的线程估计数,比如有五个线程,每个线程都执行了同一个condition对象的await()方法,则调用此方法返回的值就是5。

public class Service {
    private ReentrantLock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();

    public void methodA(){
        try {
            lock.lock();
            System.out.println("A getHoldCount 调用lock的次数=>"+lock.getHoldCount());
            Thread.sleep(2000);
            System.out.println("A getQueueLength 正在等待的线程数=>"+lock.getQueueLength());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //测试getWaitQueueLength方法
    public Integer methodC(){
        try {
            lock.lock();
            return lock.getWaitQueueLength(condition);
        }finally {
            lock.unlock();
        }

    }
}
public class Run{

    public static void main(String[] args) throws InterruptedException {
        Service service=new Service();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
               service.methodA();
            }
        };

        Thread[] threads=new Thread[5];
        for (int i=0;i<5;i++){
            threads[i]=new Thread(runnable);
            threads[i].start();
        }

        Thread.sleep(1000);
        System.out.println("执行了同一个Condition对象的的await()的线程有:"+service.methodC());
    }
}

(4) boolean hasQueuedThread(Thread thread) 查询指定的线程是否正在等待获取此锁定。

(5) boolean hasQueuedThreads() 查询是否有线程正在等待获取此锁定。

(6) boolean hasWaiters(Condition condition) 查询是否有线程正在等待与此锁定有关的condition条件

(7) boolean isFair() 判断是不是公平锁。

(8) boolean isHelpByCurrentThread() 查询当前线程是否保持此锁定。

(9) boolean isLocked() 查询此锁定是否由任意线程保持。

(10) void lockInterruptibly() 如果当前线程未被中断,则获取锁定,如果已经被中断,则出现异常。

(11) boolean tryLock() 仅在调用时锁定未被另一个线程锁定的情况下,才获得此锁定。

(12) boolean tryLock(long timeout,TimeUnit unit) 如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。

public class Service
{
    private ReentrantLock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();

    //测试lockInterruptibly
    public void methodA(){
        try {
            lock.lockInterruptibly();
            System.out.println("methodA=》"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()){//如果当前线程依旧保持对此锁的锁定,则释放
                lock.unlock();
            }
        }
    }
    //测试tryLock
    public void methodB(){
          if (lock.tryLock()){
              System.out.println(Thread.currentThread().getName()+"获得锁");
          }else{
              System.out.println(Thread.currentThread().getName()+"未获得锁");
          }
    }
}
public class Run
{
    public static void main(String[] args) throws InterruptedException
    {
        Service service=new Service();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
                service.methodA();
                service.methodB();
            }
        };

        Thread threadA=new Thread(runnable);
        threadA.setName("A");
        threadA.start();

        Thread.sleep(1000);
        Thread threadB=new Thread(runnable);
        threadB.setName("B");
        threadB.start();
        threadB.interrupt();
    }
}

(13) lock.awaitUninterruptibly():这个线程将不会被中断,一直睡眠直到其他线程调用signal()或signalAll()方法。

(14) lock.awaitUntil(Date date):这个线程将会一直睡眠直到:

  • 它被中断
  • 其他线程在这个condition上调用singal()或signalAll()方法
  • 指定的日期已经到了

三、使用ReentrantReadWriteLock 类 

      类RenntrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行RenntrantLock.lock()方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的,因为即使有时候锁内没有写入内容,而也要等锁释放后,才能进行读取。所以JDK提供了一种读写锁ReentrantReadWriteLock 类,使用它可以加快运行效率。              

      ReentrantReadWriteLock有两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也叫排他锁。也就是 多个读锁之间不互斥、读锁与写锁互斥、写锁与写锁互斥。在没有线程Thread进行写入操作时,进行读取操作的多个Thread 都可以获取读锁。而进行写入操作的Thread 只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作,但是同一个时刻只允许一个Thread 进行写入操作。

读读不互斥:

public class Read
{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

    public void read(){
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName()+"正在读"+System.currentTimeMillis());
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}
public class Run
{
    public static void main(String[] args)
    {
        Read read=new Read();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
                read.read();
            }
        };
        Thread[] threads=new Thread[10];
        for (int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
            threads[i].start();
            //通过结果可以看到所有线程几乎同时进入lock()方法
            //后面的代码,读读不互斥,可以提高程序运行效率,允许
            //多个线程同时执行lock()方法后面的代码
        }
    }
}

写写互斥:

public class Write
{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

    public void write(){
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName()+"正在写"+System.currentTimeMillis());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
}
public class Run
{
    public static void main(String[] args)
    {
        Write write=new Write();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
                write.write();
            }
        };
        Thread[] threads=new Thread[10];
        for (int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
            threads[i].start();
            //通过结果可以看到所有线程每隔两秒运行一次,写写互斥,线程之间是同步运行的
        }
    }
}

    另外,写读、读写都是互斥的,就不举例了。总之,只要出现"写"操作,就是互斥的!