博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java多线程之Lock接口
阅读量:6866 次
发布时间:2019-06-26

本文共 14482 字,大约阅读时间需要 48 分钟。

  hot3.png

Lock接口通过底层框架的形式为设计更面向对象、可更加细粒度控制线程代码、更灵活控制线程通信提供了基础。实现Lock接口且使用得比较多的是可重入锁ReentrantLock以及读写锁ReentrantReadWriteLock(成员内部类:WriteLock、ReadLock)。

1. ReentrantLock

在里面,已经总结过通过使用Synchronized关键字实现线程内的方法锁定。但使用Synchronized关键字有一些局限性,上锁和释放锁是由JVM决定的,用户没法上锁和释放进行控制。那么问题就来了:假如有一个线程业务类管理某一全局变量的读和写。对于每条线程,在读的时候数据是共享的可以让多个线程同时去读。但有某个线程在对该全局变量进行写的时候,其他的线程都不能够对变量进行读或者写(对应数据库内的读共享写互斥)。

ReentrantLock提供了一个可中断、拥有并发竞争机制[指线程对锁的竞争方式:公平竞争或不公平竞争]的方式。

正如ReentrantLock跟Synchronized关键字所使用的功能基本一样,而且Synchronized还能自己释放锁,那什么时候使用ReentrantLock?

  1. 在中断线程的时候,可以使用ReentrantLock进行控制:如线程1有一个耗时很大的任务在执行,执行时线程2必须进行等待。当线程1执行的任务时间实在太长了,线程2放弃等待进行线程后续的操作。该情况下如果使用Synchronized,只能通过抛出异常的形式进行异常操作。
  2. 多条件变量通讯:如有3条线程,线程1完成任务后通知线程2执行,线程2执行完业务逻辑以后通知线程3执行,线程3执行完通知线程1继续执行。用Synchronized关键字很难处理这种问题。用Lock却可以很好的处理这些内容。当然,线程1 、2、3 同样地可以换由一个线程组去执行这些任务。

1.1 ReentrantLock对线程中断的控制

首先,单纯地使用synchronized关键字不能进行锁中断控制. 在synchronized关键字控制的代码块内,不会因为线程中断而做出相关处理。

先查看使用synchronized关键字在处理线程中断时的结果。

业务逻辑主要为:开辟两条线程,一条线程对文件进行读操作,另一条线程对文件进行写操作。写操作内容需要时间较长,且先执行。读操作后执行,若读线程等待超过4秒。让读线程中断,进行格式化文件。

使用接口,区分使用synchronized关键字及Lock方式控制线程中断的业务逻辑。

public interface IFileHandler {    boolean isGetReadLock = false;    void read();    void write();    void formatFile();}

在synchronized关键字控制代码块的前提下,对线程进行中断的业务逻辑代码。synchronized关键字不会去响应线程中断。

public class SyncFileHandler implements IFileHandler {    private volatile boolean isGetReadLock = false;    public boolean isGetReadLock() {        return isGetReadLock;    }    public void read() {        synchronized (FileHandlerByThreads.class.getClass()) {            System.out.println(Thread.currentThread().getName() + " start");            // 能进来则设置变量标志位            isGetReadLock = true;        }    }    // 模拟运行时间比较久的写操作    public void write() {        try {            synchronized (FileHandlerByThreads.class.getClass()) {                System.out.println(Thread.currentThread().getName() + " start");                long startTime = System.currentTimeMillis();                // 模拟一个耗时较长的操作                for (; ; ) {                    if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) {                        break;                    }                }            }            System.out.println("Writer has writered down everything! bravo");        } catch (Exception e) {            e.printStackTrace();        }    }    public void formatFile() {        System.out.println("begin to format the file");        // format the file    }}

客户端测试代码

public class TestLock {    public static void main(String[] args) throws Exception {        // 1. 根据lock控制中断        // FileHandlerByThreads fileControl = new FileHandlerByThreads();        // Thread readthr = new Thread(new ReadThread(fileControl), "reader");        // Thread writethr = new Thread(new WriteThread(fileControl), "writer");        // 2. 使用synchronized关键字控制中断线程        SyncFileHandler sync = new SyncFileHandler();        Thread readthr = new Thread(new ReadThread(sync), "reader");        Thread writethr = new Thread(new WriteThread(sync), "writer");        writethr.start();        readthr.start();        long startTime = System.currentTimeMillis();        // 循环判是否有线程获取到了读锁断        while (!sync.isGetReadLock()) {            long endTime = System.currentTimeMillis();            // 如果4秒后读线程仍然没有等到读锁,离开等待            if (endTime - startTime > 4000) {                readthr.interrupt();                System.out.println("4 seconds have passed,try to interrupt reader Thread");                break;            }        }    }}class ReadThread implements Runnable {    private IFileHandler fileControl;    public ReadThread(IFileHandler fileControl) {        this.fileControl = fileControl;    }    @Override    public void run() {        fileControl.read();        // 测试单纯使用synchronized关键字控制线程中断        System.out.println("reader thread end");        fileControl.formatFile();    }}class WriteThread implements Runnable {    private IFileHandler fileControl;    public WriteThread(IFileHandler fileControl) {        this.fileControl = fileControl;    }    @Override    public void run() {        fileControl.write();    }}

代码运行结果:线程未中断:

f1539b975584432ad4611abdd77256e3ad7.jpg

下面使用ReentrantLock实现可中断线程控制

public class FileHandlerByThreads implements IFileHandler {    private volatile boolean isGetReadLock = false;    private ReentrantLock lock = new ReentrantLock();    public boolean isGetReadLock() {        return isGetReadLock;    }    public void read() {        try {            // 等待20毫秒再进行后续操作,防止主线程操作过快            Thread.sleep(50);            // 使用reentrantlock            lock.lockInterruptibly();            System.out.println(Thread.currentThread().getName() + " start");            isGetReadLock = true;        } catch (InterruptedException e) {            e.printStackTrace();            System.out.println("reader Thread leave the file and going to format the file");        }    }    // 模拟运行时间比较久的写操作    public void write() {        try {            // 1.使用lock实现写锁定            // 等待20毫秒再进行后续操作,防止主线程操作过快            Thread.sleep(20);            lock.lock();            System.out.println(Thread.currentThread().getName() + " start");            long startTime = System.currentTimeMillis();            // 模拟一个耗时较长的操作            for (; ; ) {                if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) {                    break;                }            }            System.out.println("Writer has writered down everything! bravo");        } catch (Exception e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void formatFile() {        System.out.println("begin to format the file");        // format the file    }}

客户端测试代码

public class TestLock {    public static void main(String[] args) throws Exception {        // 1. 根据lock控制中断        FileHandlerByThreads fileControl = new FileHandlerByThreads();        Thread readthr = new Thread(new ReadThread(fileControl), "reader");        Thread writethr = new Thread(new WriteThread(fileControl), "writer");        // 2. 使用synchronized关键字控制中断线程        // SyncFileHandler sync = new SyncFileHandler();        //Thread readthr = new Thread(new ReadThread(sync), "reader");        //Thread writethr = new Thread(new WriteThread(sync), "writer");        writethr.start();        readthr.start();        long startTime = System.currentTimeMillis();        // 循环判是否有线程获取到了读锁断        while (!fileControl.isGetReadLock()) {            long endTime = System.currentTimeMillis();            // 如果4秒后读线程仍然没有等到读锁,离开等待            if (endTime - startTime > 4000) {                readthr.interrupt();                System.out.println("4 seconds have passed,try to interrupt reader Thread");                break;            }        }    }}class ReadThread implements Runnable {    private IFileHandler fileControl;    public ReadThread(IFileHandler fileControl) {        this.fileControl = fileControl;    }    @Override    public void run() {        fileControl.read();        // 测试单纯使用synchronized关键字控制线程中断        System.out.println("reader thread end");        fileControl.formatFile();    }}class WriteThread implements Runnable {    private IFileHandler fileControl;    public WriteThread(IFileHandler fileControl) {        this.fileControl = fileControl;    }    @Override    public void run() {        fileControl.write();    }}

f8d94c1d9ff19417082927f272e248a7c24.jpg

1.2 ReentrantLock实现条件变量的控制

package lock;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * ReentrantLock Condition使用 * 

* Created by Jiacheng on 2018/7/3. */public class ConditionLock { /** * BoundedBuffer 是一个定长100的集合,当集合中没有元素时,take方法需要等待,直到有元素时才返回元素 * 当其中的元素数达到最大值时,要等待直到元素被take之后才执行put的操作 */ static class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { System.out.println("put wait lock"); lock.lock(); System.out.println("put get lock"); try { while (count == items.length) { System.out.println("buffer full, please wait"); notFull.await(); } items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { System.out.println("take wait lock"); lock.lock(); System.out.println("take get lock"); try { while (count == 0) { System.out.println("no elements, please wait"); notEmpty.await(); } Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } } public static void main(String[] args) { final BoundedBuffer boundedBuffer = new BoundedBuffer(); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("t1 run"); for (int i = 0; i < 1000; i++) { try { System.out.println("putting.."); boundedBuffer.put(Integer.valueOf(i)); } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { try { Object val = boundedBuffer.take(); System.out.println(val); } catch (InterruptedException e) { e.printStackTrace(); } } } }); t1.start(); t2.start(); }}

2. ReentrantReadWriteLock (读写锁)

ReentrantReadWriteLock会使用两把锁来解决问题,一个读锁,一个写锁

线程进入读锁的前提条件:

  1. 没有其他线程的写锁
  2. 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个

线程进入写锁的前提条件:

  1. 没有其他线程的读锁
  2. 没有其他线程的写锁

到ReentrantReadWriteLock,首先要做的是与ReentrantLock划清界限。它和后者都是单独的实现,彼此之间没有继承或实现的关系。然后就是总结这个锁机制的特性了: 

  1. 重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。 
  2. WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵. 
  3. ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。 
  4. 不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。 
  5. WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。 
package lock;import java.util.Random;import java.util.concurrent.locks.ReentrantReadWriteLock;/** * 读写锁 * * Created by Jiacheng on 2018/7/3. */public class ReadWriteLockTest {    public static void main(String[] args) {        final Queue3 q3 = new Queue3();        for (int i = 0; i < 3; i++) {            new Thread(() -> {                while (true) {                    q3.get();                }            }).start();        }        for (int i = 0; i < 3; i++) {            new Thread(() -> {                while (true) {                    q3.put(new Random().nextInt(10000));                }            }).start();        }    }}class Queue3 {    private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();    public void get() {        rwl.readLock().lock();//上读锁,其他线程只能读不能写        System.out.println(Thread.currentThread().getName() + " be ready to read data!");        try {            Thread.sleep((long) (Math.random() * 1000));        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + "have read data :" + data);        rwl.readLock().unlock(); //释放读锁,最好放在finnaly里面    }    public void put(Object data) {        rwl.writeLock().lock();//上写锁,不允许其他线程读也不允许写        System.out.println(Thread.currentThread().getName() + " be ready to write data!");        try {            Thread.sleep((long) (Math.random() * 1000));        } catch (InterruptedException e) {            e.printStackTrace();        }        this.data = data;        System.out.println(Thread.currentThread().getName() + " have write data: " + data);        rwl.writeLock().unlock();//释放写锁    }}

 下面使用读写锁模拟一个缓存器:

package lock;import java.util.HashMap;import java.util.Map;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;/** * 读写锁模拟的缓存器 * * Created by Jiacheng on 2018/7/3. */public class CacheByReadWriteLock {    private Map
map = new HashMap
();//缓存器 private ReadWriteLock rwl = new ReentrantReadWriteLock(); public static void main(String[] args) { } public Object get(String id) { Object value = null; rwl.readLock().lock();//首先开启读锁,从缓存中去取 try { value = map.get(id); if (value == null) { //如果缓存中没有释放读锁,上写锁 rwl.readLock().unlock(); rwl.writeLock().lock(); try { if (value == null) { value = "aaa"; //此时可以去数据库中查找 } } finally { rwl.writeLock().unlock(); //释放写锁 } rwl.readLock().lock(); //然后再上读锁 } } finally { rwl.readLock().unlock(); //最后释放读锁 } return value; }}

3. synchronizedlock的区别

  1. (用法)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
  2. (用法)lock(显示锁):需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类作为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。 如果没有主动释放锁,就有可能导致死锁现象。
  3. (机制)synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁,等待的线程会一直等待下去,不能够响应中断。Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就 是CAS操作(Compare and Swap)。
  4. (性能)synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock是个不错的方案。

 

参考资料

转载于:https://my.oschina.net/ljc94/blog/1839439

你可能感兴趣的文章
lbp纹理特征
查看>>
elastic的gc相关
查看>>
perl学习(3)正则表达式
查看>>
案例分析:免费的维护服务
查看>>
HDU_2152 Fruit(生成函数)
查看>>
css关于定位那些事情
查看>>
WCF IIS上部署服务
查看>>
微软职位内部推荐-Software Development Engineering II
查看>>
Senparc.Weixin.MP SDK 微信公众平台开发教程(五):使用Senparc.Weixin.MP SDK
查看>>
mariadb 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
查看>>
面向.Net程序员的后端性能优化实战
查看>>
基于CSS3 3D百叶窗图像过渡特效
查看>>
关于同步的一点思考-下
查看>>
ADB原理,Wi-Fi连接,常用命令及拓展
查看>>
Python学习之网络编程
查看>>
Eclipse 如何快速修改工程名及包名
查看>>
spring cloud微服务分布式云架构 - Spring Cloud简介
查看>>
Android之哭笑不得的BUG--RelativeLayout设置的marginbottom失效,马萨卡..
查看>>
Application.onCreate()会造成Service启动ANR么?
查看>>
css-水平居中、垂直居中(初级篇)
查看>>