首页 技术 正文
技术 2022年11月12日
0 收藏 600 点赞 3,541 浏览 12244 个字

本文主要介绍使用Java5中Lock对象也能实现同步的效果,而且在使用上更加方便。

本文着重掌握如下2个知识点:

  1. ReentrantLock 类的使用。
  2. ReentrantReadWriteLock 类的使用。

1. 使用ReentrantLock 类

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

1.1 使用ReentrantLock实现同步

调用ReentrantLock对象的lock()方法获取锁,调用unlock()方法释放锁。

下面是初步的程序示例:

public class Demo {    private Lock lock = new ReentrantLock();    public void test(){        lock.lock();        for (int i= 0;i<5;i++){            System.out.println(Thread.currentThread().getName()+" - "+i);        }        lock.unlock();    }    public static void main(String[] args) {        Demo demo =  new Demo();        for (int i = 0;i<5;i++){            new Thread(new Runnable() {                @Override                public void run() {                    demo.test();                }            }).start();        }    }}

运行结果:

Thread-0 - 0Thread-0 - 1Thread-0 - 2Thread-0 - 3Thread-0 - 4Thread-1 - 0Thread-1 - 1Thread-1 - 2Thread-1 - 3Thread-1 - 4Thread-2 - 0Thread-2 - 1Thread-2 - 2Thread-2 - 3Thread-2 - 4Thread-3 - 0Thread-3 - 1Thread-3 - 2Thread-3 - 3Thread-3 - 4Thread-4 - 0Thread-4 - 1Thread-4 - 2Thread-4 - 3Thread-4 - 4

从运行的结果来看,当前线程打印完毕后将锁进行释放,其他线程才可以继续打印。

1.1.2 锁住类的所有实例对象

上面的示例是所有线程调用一个ReentrantLock实例对象实现同步,如果每个线程都调用各自ReentrantLock实例对象的同一段代码呢?

示例代码:

public class MyService implements Runnable{    private ReentrantLock lock = new ReentrantLock();    public void method(){        try {            lock.lock();            System.out.println(Thread.currentThread().getName()+"锁定...");            Thread.sleep(2000);            System.out.println(Thread.currentThread().getName()+"解锁。");        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public static void main(String[] args) {        new Thread(new MyService()).start();        new Thread(new MyService()).start();        new Thread(new MyService()).start();    }    @Override    public void run() {        method();    }}

运行结果:

Thread-0锁定...Thread-2锁定...Thread-1锁定...Thread-2解锁。Thread-0解锁。Thread-1解锁。

从运行结果来看,并没有实现想要的方法同步的效果。如果我们想要实现类似synchronized(class),也就是给Class类上锁,可以把 ReentrantLock 声明为 static 静态变量。

示例代码:

public class MyService implements Runnable{    private static ReentrantLock lock = new ReentrantLock();    public void method(){        try {            lock.lock();            System.out.println(Thread.currentThread().getName()+"锁定...");            Thread.sleep(2000);            System.out.println(Thread.currentThread().getName()+"解锁。");        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public static void main(String[] args) {        new Thread(new MyService()).start();        new Thread(new MyService()).start();        new Thread(new MyService()).start();    }    @Override    public void run() {        method();    }}

运行结果:

Thread-0锁定...Thread-0解锁。Thread-1锁定...Thread-1解锁。Thread-2锁定...Thread-2解锁。

从运行结果来看,成功实现了预期的结果。

1.2 使用Condition 实现等待 / 通知

关键字 synchronized 与 wait() 和 notify() / notifyAll() 方法相结合可以实现等待 / 通知模式,类 ReentrantLock 也可以实现同样的功能,但需要借助于 Condition(即对象监视器)实例,线程对象可以注册在指定的 Condition 中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。

在使用 notify() / notifyAll() 方法进行通知时,被通知的线程却是由JVM随机选择的。但使用 ReentrantLock 结合 Condition 类是可以实现前面介绍过的“选择性通知”,这个功能是非常重要的,而且在 Condition 类中是默认提供的。

示例代码:

public class Demo {    private Lock lock = new ReentrantLock();    private Condition condition = lock.newCondition();    public void await() {        try {            lock.lock();            System.out.println("开始等待:" + System.currentTimeMillis());            condition.await();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void signal() {        try {            lock.lock();            System.out.println("结束等待:" + System.currentTimeMillis());            condition.signal();        } finally {            lock.unlock();        }    }    public static void main(String[] args) throws InterruptedException {        Demo demo = new Demo();        new Thread(new Runnable() {            @Override            public void run() {                demo.await();            }        }).start();        Thread.sleep(3000);        demo.signal();    }}

运行结果:

开始等待:1537352883839结束等待:1537352886839

成功实现等待 / 通知模式。

在Object中,有wait() 、wait(long)、notify()、notifyAll()方法。

在Condition类中,有 await()、await(long)、signal()、signalAll()方法。

1.3使用多个Condition实现通知部分线程

示例代码:

public class Demo {    private Lock lock = new ReentrantLock();    private Condition conditionA = lock.newCondition();    private Condition conditionB = lock.newCondition();    public void awaitA() {        try {            lock.lock();            System.out.println("A开始等待:" + System.currentTimeMillis());            conditionA.await();            System.out.println("A结束等待:" + System.currentTimeMillis());        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void awaitB() {        try {            lock.lock();            System.out.println("B开始等待:" + System.currentTimeMillis());            conditionB.await();            System.out.println("B结束等待:" + System.currentTimeMillis());        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void signalAll_B() {        try {            lock.lock();            conditionB.signalAll();        } finally {            lock.unlock();        }    }    public static void main(String[] args) throws InterruptedException {        Demo demo = new Demo();        new Thread(new Runnable() {            @Override            public void run() {                demo.awaitA();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                demo.awaitB();            }        }).start();        Thread.sleep(3000);        demo.signalAll_B();    }}

运行结果:

A开始等待:1537354021740B开始等待:1537354021741B结束等待:1537354024738

可以看到,只有B线程被唤醒了。

通过此实验可知,使用 ReentrantLock 对象可以唤醒指定种类的线程,这是控制部分线程行为的方便行为。

1.4 公平锁和非公平锁

锁Lock分为”公平锁“和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加载的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。

设置公平锁:

Lock lock = new ReentrantLock(true);

使用ReentrantLock类设置公平锁只需要在构造时传入boolean参数即可。默认false。需要明白的是,即使设置为true也不能保证百分百公平。

总结:

公平锁:先去判断等待队列是否为空,也就是是否有线程在等待,没有就去获取锁,否则把自己加入等待队列。

非公平锁:先去尝试获取锁,如果失败再加入到等待队列。

1.5 方法getHoldCount()、getQueryLength()和getWaitQueryLength()

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

示例代码:

public class Service {    private ReentrantLock lock = new ReentrantLock();    public void method() {        try {            lock.lock();            System.out.println("getHoldCount() " + lock.getHoldCount());            method2();        } finally {            lock.unlock();        }    }    public void method2() {        try {            lock.lock();            System.out.println("getHoldCount() " + lock.getHoldCount());        } finally {            lock.unlock();        }    }    public static void main(String[] args) {        Service service = new Service();        service.method();    }}

运行结果:

getHoldCount() 1getHoldCount() 2

2.方法getQueryLength() 的作用是返回正等待获取此锁定的线程估计数。比如有5个方法,1个线程首先执行 await()方法,那么在调用getQueueLength()方法后返回值是4,说明有4个线程同时在等待 lock 的释放。

示例代码:

public class Service {    private ReentrantLock lock = new ReentrantLock();    public void method() {        try {            lock.lock();            System.out.println("Name: " + Thread.currentThread().getName());            Thread.sleep(Integer.MAX_VALUE);        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public static void main(String[] args) throws InterruptedException {        Service service = new Service();        Runnable runnable = new Runnable() {            @Override            public void run() {                service.method();            }        };        for (int i = 0; i < 5; i++) {            new Thread(runnable).start();        }        Thread.sleep(1000);        ReentrantLock lock = service.getLock();        System.out.println("有多少线程在等待:"+lock.getQueueLength());    }    private ReentrantLock getLock() {        return lock;    }}

运行结果:

Name: Thread-1有多少线程在等待:4

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

示例代码:

public class Service {    private ReentrantLock lock = new ReentrantLock();    private Condition condition = lock.newCondition();    public void method() {        try {            lock.lock();            condition.await();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void notifyMethod() {        try {            lock.lock();            System.out.println("等待condition的线程数" + lock.getWaitQueueLength(condition));            condition.signalAll();        } finally {            lock.unlock();        }    }    public static void main(String[] args) throws InterruptedException {        Service service = new Service();        Runnable runnable = new Runnable() {            @Override            public void run() {                service.method();            }        };        for (int i = 0; i < 5; i++) {            new Thread(runnable).start();        }        Thread.sleep(1000);        service.notifyMethod();    }}

运行结果:

等待condition的线程数5

1.6 方法hasQueuedThread()、hasQueuedThreads()和hasWaiters()

1.方法 boolean hasQueuedThread(Thread thread) 的作用是查询指定的线程是否正在等待获取此锁定。

2.方法 boolean hasQueuedThreads() 的作用是查询是否有线程正在等待获取此锁定。

1、2示例代码:

public class Service {    private ReentrantLock lock = new ReentrantLock();    private Condition condition = lock.newCondition();    public void waitMethod(){        try {            lock.lock();            Thread.sleep(Integer.MAX_VALUE);        }catch (InterruptedException e){            e.printStackTrace();        }finally {            lock.unlock();        }    }    public ReentrantLock getLock(){        return lock;    }    public static void main(String[] args) throws InterruptedException {        final Service service = new Service();        Runnable runnable = new Runnable() {            @Override            public void run() {                service.waitMethod();            }        };        Thread thread1 = new Thread(runnable);        Thread thread2 = new Thread(runnable);        thread1.start();        thread2.start();        Thread.sleep(1000);        ReentrantLock lock = service.getLock();        System.out.println(lock.hasQueuedThreads());        System.out.println(lock.hasQueuedThread(thread1));        System.out.println(lock.hasQueuedThread(thread2));    }}

运行结果:

truefalsetrue

3.方法 boolean hasWaiters(Condition condition) 的作用是查询是否有线程正在等待与此锁定有关的 condition 条件。

示例代码:

public class Service {    private ReentrantLock lock = new ReentrantLock();    private Condition condition = lock.newCondition();    public void waitMethod(){        try {            lock.lock();            condition.await();        }catch (InterruptedException e){            e.printStackTrace();        }finally {            lock.unlock();        }    }    public void notifyMethod(){        try {            lock.lock();            System.out.println("有没有线程正在等待 condition ?" + lock.hasWaiters(condition) + " 线程数是多少?" + lock.getWaitQueueLength(condition));            condition.signalAll();        }finally {            lock.unlock();        }    }    public static void main(String[] args) throws InterruptedException {        Service service = new Service();        Runnable runnable = new Runnable() {            @Override            public void run() {                service.waitMethod();            }        };        for (int i = 0; i < 10; i++) {            new Thread(runnable).start();        }        Thread.sleep(2000);        service.notifyMethod();    }}

运行结果:

有没有线程正在等待 condition ?true 线程数是多少?10

1.7 方法isFair()、isHeldByCurrentThread()和isLocked()

  1. 方法boolean isFair() 的作用是判断是不是公平锁。
  2. 方法boolean isHeldByCurrentThread() 的作用是查询当前线程是否保持此锁定。
  3. 方法boolean isLocked() 的作用是查询此锁定是否由任意线程保持。

更改上面的部分代码:

System.out.println(lock.isHeldByCurrentThread());System.out.println(lock.isLocked());lock.lock();System.out.println(lock.isLocked());System.out.println(lock.isHeldByCurrentThread());

运行结果:

falsefalsetruetrue

1.8 方法lockInterruptibly()、tryLock()和tryLock(long timeout, TimeUnit unit)

下面的三个方法都是对lock.lock()方法的另一种变形:

  1. 方法void lockInterruptibly()的作用是:如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。

    而使用 lock() 方法,即使线程被中断(调用thread.interrupt()方法),也不会出现异常。

  2. 方法boolean tryLock() 的作用是,仅在未被另一个线程保持的情况下,才获取该锁定。

    假设有两个线程同时调用同一个lock对象的tryLock()方法,那么除了第一个获得锁(返回true),其它都获取不到锁(返回false)。

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

1.9 方法 condition.awaitUninterruptibly()的使用

前面讲到,执行condition.await()方法后,线程进入等待状态,如果这时线程被中断(调用thread.interrupt()方法)则会抛出异常。而使用 condition.awaitUninterruptibly() 方法代替 condition.await() 方法则不会抛出异常。

1.10 方法 condition.awaitUntil(Date deadline)的使用

使用方法 condition.awaitUntil(Date deadline) 可以代替 await(long time, TimeUnit unit) 方法进行线程等待,该方法在等待时间到达前是可以被提前唤醒的。

1.11 使用Condition实现顺序执行

使用Condition对象可以对线程执行的业务进行排序规划。

示例代码:

public class DThread{    volatile private static int nextPrintWho = 1;    private static ReentrantLock lock = new ReentrantLock();    final private static Condition conditionA = lock.newCondition();    final private static Condition conditionB = lock.newCondition();    final private static Condition conditionC = lock.newCondition();    public static void main(String[] args) {        Thread threadA = new Thread(){            @Override            public void run() {                try {                    lock.lock();                    while (nextPrintWho != 1){                        conditionA.await();                    }                    for (int i = 0;i<3;i++){                        System.out.println("ThreadA "+(i+1));                    }                    nextPrintWho = 2;                    conditionB.signalAll();                } catch (InterruptedException e) {                    e.printStackTrace();                }finally {                    lock.unlock();                }            }        };        Thread threadB = new Thread(){            @Override            public void run() {                try {                    lock.lock();                    while (nextPrintWho != 2){                        conditionA.await();                    }                    for (int i = 0;i<3;i++){                        System.out.println("ThreadB "+(i+1));                    }                    nextPrintWho = 3;                    conditionB.signalAll();                } catch (InterruptedException e) {                    e.printStackTrace();                }finally {                    lock.unlock();                }            }        };        Thread threadC = new Thread(){            @Override            public void run() {                try {                    lock.lock();                    while (nextPrintWho != 3){                        conditionA.await();                    }                    for (int i = 0;i<3;i++){                        System.out.println("ThreadC "+(i+1));                    }                    nextPrintWho = 1;                    conditionB.signalAll();                } catch (InterruptedException e) {                    e.printStackTrace();                }finally {                    lock.unlock();                }            }        };        for (int i= 0;i<5;i++){            new Thread(threadA).start();            new Thread(threadB).start();            new Thread(threadC).start();        }    }}

打印结果:

ThreadA 1ThreadA 2ThreadA 3ThreadB 1ThreadB 2ThreadB 3ThreadC 1ThreadC 2ThreadC 3....

2.使用ReentrantReadWriteLock类

类 ReentrantLock 具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock() 方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的。所以在JDK中提供了一种读写锁 ReentrantReadWriteLock 类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写 ReentrantReadWriteLock 来提升该方法的代码运行速度。
读写锁表示也有两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程 Thread进行写入操作时,进行读取操作的多个 Thread 都可以获取读锁,而进行写入操作的 Thread 只有在获取写锁后才能进行写入操作。即多个 Thread可以同时进行读取操作但是同一时刻只允许一个 Thread 进行写入操作。

总结起来就是:读读共享,写写互斥,读写互斥,写读互斥。

声明读写锁:

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

获取读锁:

lock.readLock().lock();

获取写锁:

lock.writeLock().lock();

3.文末总结

学习完本文完全可以使用Lock对象将 synchronized关键字替换掉,而且其具有的独特功能也是 synchronized 所不具有的。在学习并发时,Lock是synchronized关键字的进阶,掌握Lock有助于学习并发包中源代码的实现原理,在并发包中大量的类使用了Lock 接口作为同步的处理方式。

参考

《Java多线程编程核心技术》高洪岩著

扩展

Java多线程编程核心技术(一)Java多线程技能

Java多线程编程核心技术(二)对象及变量的并发访问

Java多线程编程核心技术(三)多线程通信

Java多线程核心技术(五)单例模式与多线程

Java多线程核心技术(六)线程组与线程异常

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,494
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,908
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,740
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,495
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,133
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,297