为什么80%的码农都做不了架构师?>>>   

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

1. ReentrantLock

在synchronized使用及实现原理里面,已经总结过通过使用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;}@Overridepublic 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;}@Overridepublic void run() {fileControl.write();}
}

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

下面使用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);// 使用reentrantlocklock.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;}@Overridepublic 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;}@Overridepublic void run() {fileControl.write();}
}

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使用* <p>* 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() {@Overridepublic 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() {@Overridepublic 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<String, Object> map = new HashMap<String, Object>();//缓存器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是个不错的方案。

参考资料

Lock接口,ReentranctLock,ReentrantReadWriteLock

java中ReentrantReadWriteLock读写锁的使用

java并发控制:ReentrantLock Condition使用详解

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

Java多线程之Lock接口相关推荐

  1. Java多线程之Callable接口的实现

    import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.ut ...

  2. java多线程之Callable接口

    1.Callable VS Runnable Callable和Runnbale一样代表着是线程任务,区别在于Callable有返回值并且可以抛出异常. 2.创建并启动有返回值的线程的步骤 创建Cal ...

  3. Java多线程之Callable、Future和FutureTask

    Java多线程之Callable接口 自己想总结一下的,看到一篇总结的更好的博客,就转载了,突然感觉真轻松,哈哈哈哈 文章转载于:Matrix海子:Java并发编程:Callable.Future和F ...

  4. Java多线程之Synchronized和Lock的区别

    Java多线程之Synchronized和Lock的区别 目录: 原始构成 使用方法 等待是否可以中断 加锁是否公平 锁绑定多个条件Condition 小结:Lock相比较Synchronized的优 ...

  5. Java多线程之Runable与Thread

    Java多线程是Java开发中的基础内容,但是涉及到高并发就有很深的研究可做了. 最近看了下<Java并发实战>,发先有些地方,虽然可以理解,但是自己在应用中很难下手. 所以还是先回顾一下 ...

  6. JAVA多线程之wait/notify

    本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法. ①wait() 与 notify/notifyAll 方法必须在同步代码块中使用 ②wait ...

  7. Java多线程之8Lock问题解析

    Java多线程之8Lock问题解析 本文目录 1. 8Lock实例: 标准访问的时候,请问先打印邮件还是短信? sendEmail方法暂停4秒钟,请问先打印邮件还是短信? 新增Hello普通方法,请问 ...

  8. Java多线程之CAS缺点

    Java多线程之CAS缺点 目录: 循环时间开销很大 只能保证一个共享变量的原子操作 引来ABA问题及解决方案(重点) 1. 循环时间开销很大 通过看源码,我们发现有个do while,如果CAS失败 ...

  9. Java多线程之CAS深入解析

    Java多线程之CAS深入解析 目录: CAS是什么 CAS底层原理Unsafe深入解析 CAS缺点 引子:蚂蚁花呗一面:讲一讲AtomicInteger,为什么要用CAS而不是synchronize ...

最新文章

  1. 【FFmpeg】ffmpeg命令详解(一)
  2. 制造业采购审批流程设计示例
  3. Java+MyEclipse+Tomcat (四)Servlet提交表单和数据库操作
  4. (转载)WebSphere MQ安装过程
  5. navcat定时备份mysql_Linux实现MYSQl数据库的定时备份
  6. 安装和使用Ant Design Vue 图标库
  7. Serverless 实战 —— 基于 Serverless + 企业微信打造 nCoV 疫情监控小助手
  8. 吴恩达《机器学习》第十一章:机器学习系统的设计
  9. 9day条件语句和基本数据类型
  10. 中国教育电脑市场趋势报告、技术动态创新及市场预测
  11. mysql常用数据操作之增、删、改
  12. atitit。全局变量的设计与实现 java php的异同
  13. QT5-STK二次开发实例
  14. 李白的诗: 南陵别儿童入京
  15. Java实现家谱家族管理系统,图形化家谱家族树,单机应用程序
  16. 大数据第一季--Hadoop(day5)-徐培成-专题视频课程
  17. 计算机桌面都有说明,电脑重启后桌面所有的图标都没了怎么解决
  18. 一个小工具,帮你找到赚钱思路
  19. Stack frame omission (FPO) optimization part1
  20. 湘潭哪里学计算机编程,湘潭哪里学机器人编程?湘潭学机器人编程的学校有哪些?...

热门文章

  1. scala dynamics 示例
  2. MongoDB进阶系列(11)——“改”的那些事(二)文档的keyvalue为数组的修改方法...
  3. cocos2d Labels and Fonts 标签和字体(附:关于Hiero的二三事)
  4. Tails 3.13 发布,更新 Intel 微码,改进拼音输入法支持
  5. .net autofac Web Forms
  6. 131、ThreadLocal (转载)
  7. centos6.7上使用nginx实现负载均衡!
  8. 深入理解Linux内核-内存寻址
  9. HTML5之Canvas标签简要学习
  10. 看到一个flash做的超酷网站