一、synchronized与Lock

基于synchronized关键字去实现同步的,(jvm内置锁,也叫隐式锁,由我们的jvm自动去加锁跟解锁的)juc下的基于Lock接口的这样的一种锁的实现方式(显式锁,他需要我们手动的去加锁,跟解锁)

一张图了解一下区别:

二、Lock接口的实现


Lock锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。

Lock l = ...;l.lock();try {// access the resource protected by this lock} finally {l.unlock();}

三、Lock实现ReentrantLock

由英文知道:ReentrantLock称是重入锁,可以反复得到相同的一把锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放(重入锁)。围绕着去实现的核心实质就是AQS(AbstractQueuedSynchronizer)(抽象队列同步器)ReentrantLock和synchronized关键字一样可以用来实现线程之间的同步互斥,但是在功能是比synchronized关键字更强大而且更灵活。

ReentrantLock类常见方法(Lock接口已有方法这里没加上):

class X {private final ReentrantLock lock = new ReentrantLock();// ...public void m() {lock.lock();  // block until condition holdstry {// ... method body} finally {lock.unlock()}}}

例子:

public class ReentrantLockTest extends Thread {public static ReentrantLock lock = new ReentrantLock();public static int i = 0;public ReentrantLockTest(String name) {super.setName(name);}@Overridepublic void run() {for (int j = 0; j < 100; j++) {lock.lock();try {System.out.println(this.getName() + " " + i);i++;} finally {lock.unlock();}}}/*** @param args* @throws InterruptedException*/public static void main(String[] args) throws InterruptedException {ReentrantLockTest test1 = new ReentrantLockTest("thread1");ReentrantLockTest test2 = new ReentrantLockTest("thread2");test1.start();test2.start();test1.join();test2.join();System.out.println(i);}
}
//以上例子,如果没加lock会输出一个小于200的数

注意:ReentrantLock并不是一种替代内置加锁的方法,而是作为一种可选择的高级功能。相比于synchronized,ReentrantLock在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点。

以下是他和synchronized的对比:

  • 1.synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
  • 2.synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行(其实这也是Lock的相比synchronized的优点,更加灵活),且次数需一样,否则其他线程无法获得锁。
  • 3.synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
  • 4.ReentrantLock可设置超时退出,不会永久等待构成死锁。

在synchronized和reentrantLock之间进行选择:
虽然reentrantLock在1.5和1.6的表现中,性能远好于synchronized,但还是提倡使用synchronized:

  • 1.reentrantLock忘记关锁的话,很危险
  • 2.未来更可能提升的是 synchronized的性能,而不是reentrantLock
  • 3.当需要用到某些高级性能时,才考虑reentrantLock,例如:可定时的,可轮询的,可中断的,公平队列等

公平锁与非公平锁(AQS的原因)
AQS内部维护着一个FIFO的队列,即CLH队列。AQS的同步机制就是依靠CLH队列实现的。

  • 一般意义上的锁是不公平的,不一定先来的线程能先得到锁,后来的线程就后得到锁。不公平的锁可能会产生饥饿现象。
  • 公平锁的意思就是,这个锁能保证线程是先来的先得到锁。虽然公平锁不会产生饥饿现象,但是公平锁的性能会比非公平锁差很多。

公平锁:

非公平锁:

注意: 默认情况下ReentranLock类使用的是非公平锁,若要使用公平锁,如下 :

public ReentrantLock(boolean fair)
public static ReentrantLock fairLock = new ReentrantLock(true);

四、Condition接口

我们知道synchronized关键字与wait()和notify/notifyAll()方法相配合就可以实现等待/通知机制,借助于Condition接口与newCondition() 方法ReentrantLock类也可以实现。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 condition可以通俗的理解为条件队列。当一个线程在调用了await方法以后,直到线程等待的某个条件为真的时候才会被唤醒。这种方式为线程提供了更加简单的等待/通知模式。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。

在使用notify/notifyAll()方法进行通知时,被通知的线程是由JVM选择的,使用ReentrantLock类结合Condition实例可以实现“选择性通知”,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。

class ShareDataOne {private int number = 0;private  Lock lock = new ReentrantLock();private  Condition cd = lock.newCondition();public  void incr() throws InterruptedException {lock.lock();try {while  (number != 0) {cd.await();}number++;System.out.println(Thread.currentThread().getName() + "\t" + number);cd.signalAll();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void decr() throws InterruptedException {lock.lock();try {while  (number != 1) {cd.await();}number--;System.out.println(Thread.currentThread().getName() + "\t" + number);cd.signalAll();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}
/*** 现在两个线程* 操作一个初始值为0的变量* 实现一个线程对变量增加1,一个线程对变量减少1* 交替10轮* 、线程 操作 资源类 2、高内聚低耦合* 1、判断* 2、干活* 3、通知* 注意多线程之间的虚假唤醒*/
public class NotifyWaitDemo {public static void main(String[] args) {ShareDataOne shareDataOne = new ShareDataOne();new Thread(() -> {for (int i = 1; i <= 10; i++) {try {shareDataOne.incr();} catch (InterruptedException e) {e.printStackTrace();}}}, "AA").start();new Thread(() -> {for (int i = 1; i <= 10; i++) {try {shareDataOne.decr();} catch (InterruptedException e) {e.printStackTrace();}}}, "BB").start();}
}

简单地说:一个线程运行完之后通过condition.signal()或者condition.signalAll()方法通知下一个特定的运行,就这样循环往复即可。

五、ReadWriteLock接口的实现类:ReentrantReadWriteLock

ReentrantLock(排他锁)具有完全互斥排他的效果,即同一时刻只允许一个线程访问,这样做虽然虽然保证了实例变量的线程安全性,但效率非常低下。ReadWriteLock接口的实现类ReentrantReadWriteLock读写锁就是为了解决这个问题。读写锁维护了两个锁,一个是读操作相关的锁也成为共享锁,一个是写操作相关的锁也称为排他锁。通过分离读锁和写锁,其并发性比一般排他锁有了很大提升。多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥(只要出现写操作的过程就是互斥的。)。在没有线程Thread进行写入操作时,进行读取操作的多个Thread都可以获取读锁,而进行写入操作的Thread只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。

ReentrantReadWriteLock的特性:

ReentrantReadWriteLock常见方法: 构造方法

问题例子:

class MyCache{private volatile Map<String,Object> map = new HashMap<>();public void put(String key,Object value){System.out.println(Thread.currentThread().getName()+"\t 正在写"+key);//暂停一会儿线程try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace(); }map.put(key,value);System.out.println(Thread.currentThread().getName()+"\t 写完了"+key);}public Object get(String key){Object result = null;System.out.println(Thread.currentThread().getName()+"\t 正在读"+key);try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace(); }result = map.get(key);System.out.println(Thread.currentThread().getName()+"\t 读完了"+result);return result;}
}
public class ReadWriteLockDemo {public static void main(String[] args) {MyCache myCache = new MyCache();for (int i = 1; i <= 3; i++) {final int num = i;new Thread(()->{myCache.put(num+"",num+"");},String.valueOf(i)).start();}for (int i = 1; i <= 3; i++) {final int num = i;new Thread(()->{myCache.get(num+"");},String.valueOf(i)).start();}}
}
//结果(还没写完就重新再写一个)
1    正在写1
3    正在写3
2    正在写2
1    正在读1
2    正在读2
3    正在读3
2    写完了2
3    读完了null
2    读完了null
1    读完了null
1    写完了1
3    写完了3

完善代码:

class MyCache{private  volatile  Map<String,String> map = new HashMap<>();private  ReadWriteLock rwLock = new ReentrantReadWriteLock();public  void put(String key,String value){rwLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName()+"\t 准备写入数据"+key);TimeUnit.MILLISECONDS.sleep(200);map.put(key, value);System.out.println(Thread.currentThread().getName()+"\t 写入数据完成"+key);} catch (Exception e) {e.printStackTrace();} finally {rwLock.writeLock().unlock();}}public  void get(String key){rwLock.readLock().lock();try {System.out.println(Thread.currentThread().getName()+"\t 准备读取数据"+key);TimeUnit.MILLISECONDS.sleep(200);String value = map.get(key);System.out.println(Thread.currentThread().getName()+"\t 读取数据完成"+value);} catch (Exception e) {e.printStackTrace();} finally {rwLock.readLock().unlock();}}
}
public class ReadWriteLockDemo {public static void main(String[] args) throws InterruptedException {MyCache myCache = new MyCache();for (int i = 1; i <=3 ; i++) {String key = String.valueOf(i);new Thread(()->{myCache.put(key,UUID.randomUUID().toString().substring(0,8));},String.valueOf(i)).start();}TimeUnit.SECONDS.sleep(2);for (int i = 1; i <=3 ; i++) {String key = String.valueOf(i);new Thread(()->{myCache.get(key);},String.valueOf(i)).start();}}
}
2    准备写入数据2
2    写入数据完成2
1    准备写入数据1
1    写入数据完成1
3    准备写入数据3
3    写入数据完成3
1    准备读取数据1
2    准备读取数据2
3    准备读取数据3
1    读取数据完成c013a300
2    读取数据完成347ce814
3    读取数据完成7387e9c6

参考文章

Lock接口Condition,以及Lock与synchronized异同相关推荐

  1. 多线程—Lock、Condition、ReentrantLock、ReentrantReadWriteLock

    Lock接口 public interface Lock { //下面4个方法都是获得锁void lock(); void lockInterruptibly() throws Interrupted ...

  2. 【JUC】第一章 JUC概述、Lock 接口

    第一章 JUC 概述.Lock 接口 文章目录 第一章 JUC 概述.Lock 接口 一.JUC 概述 1.什么是 JUC 2.线程和进程概念 3.线程的状态 4.并发与并行 5.管程 6.用户线程和 ...

  3. java lock接口_Java Lock接口

    Java Lock接口 java.util.concurrent.locks.Lock接口用作线程同步机制,类似于同步块.新的锁定机制更灵活,提供比同步块更多的选项. 锁和同步块之间的主要区别如下: ...

  4. 并发王者课-铂金1:探本溯源-为何说Lock接口是Java中锁的基础

    欢迎来到<并发王者课>,本文是该系列文章中的第14篇. 在黄金系列中,我们介绍了并发中一些问题,比如死锁.活锁.线程饥饿等问题.在并发编程中,这些问题无疑都是需要解决的.所以,在铂金系列文 ...

  5. 铂金1:探本溯源-为何说Lock接口是Java中锁的基础

    欢迎来到<并发王者课>,本文是该系列文章中的第14篇. 在黄金系列中,我们介绍了并发中一些问题,比如死锁.活锁.线程饥饿等问题.在并发编程中,这些问题无疑都是需要解决的.所以,在铂金系列文 ...

  6. java中多线程模拟(多生产,多消费,Lock实现同步锁,替代synchronized同步代码块)...

    import java.util.concurrent.locks.*; class DuckMsg{int size;//烤鸭的大小String id;//烤鸭的厂家和标号 DuckMsg(){}D ...

  7. 多线程笔记补充之线程通信wait和notify方法以及Lock和Condition接口的使用

    线程通信-wait和notify方法介绍: java.lang.Object类提供类两类用于操作线程通信的方法. wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他的 ...

  8. 【java线程】锁机制:synchronized、Lock、Condition

    [Java线程]锁机制:synchronized.Lock.Condition 原创 2013年08月14日 17:15:55 标签:Java /多线程 74967 http://www.infoq. ...

  9. Synchronized和Lock接口

    关于synchronized字段,不管该关键字是修饰方法还是修饰同步代码块,synchronzed拿到的都是对象. 当synchronized修饰的是方法时,synchronized所拿到的是调用该方 ...

最新文章

  1. C/C++、嵌入式开发岗笔试集锦
  2. 6.二元查找树的后序遍历结果[PostOrderOfBST]
  3. 从源码剖析SpringBoot中Tomcat的默认最大连接数
  4. 搞懂 SynchronizationContext
  5. cs1.5 linux服务端,Linux下架设CS1.5服务器
  6. 360全景倒车影像怎么看_别克关怀-后视镜和倒车影像 倒车时到底看哪个
  7. 隐身专家 FreeEIM 合作版
  8. magento产品批量导出导入
  9. 为什么建议使用count(*)来统计数据行数
  10. asp.net数据库连接php代码,ASP.NET 数据库连接
  11. linux怎么看文件是否orc格式,hive文件存储格式orc,parquet,avro对比
  12. TensorFlow by Google过拟合优化 Machine Learning Foundations: Ep #7 - Image augmentation and overfitting
  13. 【肌电信号】肌电信号处理系统含Matlab源码
  14. UVa 12261 High Score (贪心“向左走,向右走”)
  15. python爬取酷狗音乐_python 爬虫 爬取酷狗音乐
  16. Android 4.0以上设备虚拟按键中显示Menu键
  17. 什么是网络攻击?网络攻击手段有哪些?
  18. MATLAB 打不开coder,MATLAB CODER初次使用的错误提示,希望大侠可以帮忙解决!...
  19. 流型Charting
  20. 只是为了纪念机房颓废的日子

热门文章

  1. Mac系统下安装Homebrew后无法使用brew命令
  2. Python元组tuple(不可变)
  3. C# PrintDocument 打印表格
  4. Kafka- Spark消费Kafka
  5. 关于索引的相关 day45
  6. Jmeter-获取响应结果中参数出现的次数
  7. ACM学习历程—Hihocoder [Offer收割]编程练习赛1
  8. linux的终端,网络虚拟终端,伪终端
  9. Git bash:初步入门(1)
  10. malloc 不能返回动态内存