Synchronized

synchronized 的 3 种用法:

指定加锁对象(代码块):对给定对象加锁,进入同步代码前要获得给定对象的锁。

void resource1() {synchronized ("resource1") {System.out.println("作用在同步块中");}}

直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。

synchronized void resource3() {System.out.println("作用在实例方法上");}

直接作用于静态方法:相当于对当前类加锁,进入同步代码块前要获得当前类的锁。

static synchronized void resource2() {System.out.println("作用在静态方法上");}

synchronized 在发生异常的时候会释放锁,这点需要注意一下。


Lock接口:

/*** @since 1.5* @author Doug Lea*/
public interface Lock {void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition();}

lock():用来获取锁,若锁已被其他线程获取,则需等待
Lock()方法必须主动去释放锁,并且在发生异常时也不会自动释放锁
使用Lock必须在try…catch…块中进行:

  1. 释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生
  2. 获取锁的操作应该方法try的外面,防止误释放锁。

(如果说在获取锁时发生了异常,那么肯定也会走 finally 代码块,执行lock.unlock();去释放锁,可问题是我还没获取到锁啊!!!)
在 try-finally 外加锁的话,如果因为发生异常导致加锁失败,try-finally 块中的代码不会执行。
相反,如果在 try{ } 代码块中加锁失败,finally 中的代码无论如何都会执行,但是由于当前线程加锁失败并没有持有 lock 对象锁,所以程序会抛出异常。

链接:
https://blog.csdn.net/u013568373/article/details/98480603?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

unlock():解锁

tryLock():尝试获取锁,若获取不到,立刻返回false;这个方法无论如何都会立即返回,在拿不到锁时不会一直在那等待。

tryLock(long time, TimeUnit unit):在给定的时间里等待锁,超过时间则自动放弃,返回false;在等待期间内拿到了锁,返回true。

lockInterruptibly():
获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的;
而用synchronized修饰的话,synchronized 只有2种情况:1继续执行,2保持等待。

 public void method(Lock lock) throws InterruptedException {lock.lockInterruptibly();try {//执行代码} catch (Exception e) {// 异常处理} finally {lock.unlock();}}

一般将lockTry.lockInterruptibly();写在了try{}catch{}之外,原因同上。


Lock 的标准实现是重入锁 ReentrantLock, 和读写锁 ReadWriteLock。

ReentrantLock 重入锁

可重入性:

重入锁ReentrantLock,是支持重进入的锁,该锁能够支持一个线程对资源的重复加锁。
先来一个简单的例子:

package cn.think.in.java.lock;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockText implements Runnable {/*** Re - entrant - Lock* 重入锁,表示在单个线程内,这个锁可以反复进入,也就是说,一个线程可以连续两次获得同一把锁。* 如果你不允许重入,将导致死锁。注意,lock 和 unlock 次数一定要相同,如果不同,就会导致死锁和监视器异常。** synchronized 只有2种情况:1继续执行,2保持等待。*/static Lock lock = new ReentrantLock();static int i;public static void main(String[] args) throws InterruptedException {LockText lockText = new LockText();Thread t1 = new Thread(lockText);Thread t2 = new Thread(lockText);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}@Overridepublic void run() {for (int j = 0; j < 1000000; j++) {lock.lock();try {i++;} finally {// 因为lock 如果发生了异常,是不会释放锁的,所以必须在 finally 块中释放锁// synchronized 发生异常会主动释放锁lock.unlock();}}}
}

PS:
Synchronized 也支持重进入,但只支持隐式的重进入。

public synchronized void test1() {value = value + 1;test2();
}
public synchronized void test2() {value = value + 1;
}

ReentrantLock与synchronized隐式获取和释放锁相比,它缺少了便捷性,但却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
Lock的使用:

Lock lock = new ReentrantLock();
lock.lock();
lock.lock();
try {// 业务逻辑
} finally {lock.unlock();lock.unlock();
}

注意:
1.在finally块中释放锁,保证在获取到锁之后,最终能够被释放。
2.不要将获取锁的过程写在try块中,因为如果在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,也会导致锁无故释放。

中断响应

synchronized修饰的线程在等待锁,那么只有2种情况:1获取到锁继续执行,2保持等待;
lockInterruptibly 方法修饰的线程,可在获取锁的过程种响应线程中断,那么就会抛出异常。

package cn.think.in.java.lock;import java.util.concurrent.locks.ReentrantLock;/*** ReentrantLock(重入锁)** Condition(条件)** ReadWriteLock(读写锁)*/
public class IntLock implements Runnable {/*** 默认是不公平的锁,设置为 true 为公平锁** 公平:在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程;* 使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢)* 还要注意的是,未定时的 tryLock 方法并没有使用公平设置** 不公平:此锁将无法保证任何特定访问顺序** 拾遗:1 该类的序列化与内置锁的行为方式相同:一个反序列化的锁处于解除锁定状态,不管它被序列化时的状态是怎样的。*      2.此锁最多支持同一个线程发起的 2147483648 个递归锁。试图超过此限制会导致由锁方法抛出的 Error。*/static ReentrantLock lock1 = new ReentrantLock(true);static ReentrantLock lock2 = new ReentrantLock();int lock;/*** 控制加锁顺序,方便制造死锁* @param lock*/public IntLock(int lock) {this.lock = lock;}/*** lockInterruptibly 方法: 获得锁,但优先响应中断* tryLock 尝试获得锁,不等待* tryLock(long time , TimeUnit unit) 尝试获得锁,等待给定的时间*/@Overridepublic void run() {try {if (lock == 1) {// 如果当前线程未被中断,则获取锁。lock1.lockInterruptibly();// 即在等待锁的过程中,可以响应中断。try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}// 试图获取 lock 2 的锁lock2.lockInterruptibly();} else {lock2.lockInterruptibly();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}// 该线程在企图获取 lock1 的时候,会死锁,但被调用了 thread.interrupt 方法,导致中断。中断会放弃锁。lock1.lockInterruptibly();}} catch (InterruptedException e) {e.printStackTrace();} finally {if (lock1.isHeldByCurrentThread()) {lock1.unlock();}// 查询当前线程是否保持此锁。if (lock2.isHeldByCurrentThread()) {lock2.unlock();}System.out.println(Thread.currentThread().getId() + ": 线程退出");}}public static void main(String[] args) throws InterruptedException {/*** 这部分代码主要是针对 lockInterruptibly 方法,该方法在线程发生死锁的时候可以中断线程。让线程放弃锁。* 而 synchronized 是没有这个功能的, 他要么获得锁继续执行,要么继续等待锁。*/IntLock r1 = new IntLock(1);IntLock r2 = new IntLock(2);Thread t1 = new Thread(r1);Thread t2 = new Thread(r2);t1.start();t2.start();Thread.sleep(1000);// 中断其中一个线程(只有线程在等待锁的过程中才有效)// 如果线程已经拿到了锁,中断是不起任何作用的。// 注意:这点 synchronized 是不能实现此功能的,synchronized 在等待过程中无法中断t2.interrupt();// t2 线程中断,抛出异常,并放开锁。没有完成任务// t1 顺利完成任务。}
}

锁申请

trylock(),tryLock(long time, TimeUnit unit)在获取锁如果尝试失败或者超时,线程就放弃获取=锁,这点synchronized 是不支持的,这样可以有效避免死锁。那么,如何使用呢?

package cn.think.in.java.lock;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;public class TimeLock implements Runnable {static ReentrantLock lock = new ReentrantLock(false);@Overridepublic void run() {try {// 最多等待5秒,超过5秒返回false,若获得锁,则返回trueif (lock.tryLock(5, TimeUnit.SECONDS)) {// 锁住 6 秒,让下一个线程无法获取锁System.out.println("锁住 6 秒,让下一个线程无法获取锁");Thread.sleep(6000);} else {System.out.println("get lock failed");}} catch (InterruptedException e) {e.printStackTrace();} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}public static void main(String[] args) {TimeLock tl = new TimeLock();Thread t1 = new Thread(tl);Thread t2 = new Thread(tl);t1.start();t2.start();}
}

公平锁和非公平锁

公平锁:不会产生饥饿现象。线程按照等待顺序得到资源
非公平锁:系统在选择锁的时候都是随机的,不会按照某种顺序,比如时间顺序。
synchronized 得到的锁是非公平锁,而ReentrantLock可以自由选择使用公平锁或者非公平锁,同时非公平锁 比 公平锁 更有效率,一般选择非公平锁。

 package cn.think.in.java.lock;import java.util.concurrent.locks.ReentrantLock;public class FairLock implements Runnable {// 公平锁和非公平锁的结果完全不同/** 10 获得锁10 获得锁10 获得锁10 获得锁10 获得锁10 获得锁10 获得锁10 获得锁10 获得锁10 获得锁9 获得锁9 获得锁9 获得锁9 获得锁9 获得锁9 获得锁9 获得锁9 获得锁9 获得锁9 获得锁======================下面是公平锁,上面是非公平锁10 获得锁9 获得锁10 获得锁9 获得锁10 获得锁9 获得锁10 获得锁9 获得锁10 获得锁9 获得锁10 获得锁9 获得锁10 获得锁9 获得锁10 获得锁9 获得锁10 获得锁9 获得锁10 获得锁9 获得锁10 获得** */static ReentrantLock unFairLock = new ReentrantLock(false);static ReentrantLock fairLock = new ReentrantLock(true);@Overridepublic void run() {while (true) {try {fairLock.lock();System.out.println(Thread.currentThread().getId() + " 获得锁");} finally {fairLock.unlock();}}}/*** 默认是不公平的锁,设置为 true 为公平锁** 公平:在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程;* 使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢)* 还要注意的是,未定时的 tryLock 方法并没有使用公平设置** 不公平:此锁将无法保证任何特定访问顺序,但是效率很高**/public static void main(String[] args) {FairLock fairLock = new FairLock();Thread t1 = new Thread(fairLock, "cxs - t1");Thread t2 = new Thread(fairLock, "cxs - t2");t1.start();t2.start();}
}

可以看到,公平锁的打印顺序是完全交替运行,而不公平锁的顺序完全是随机的。

重入锁相比 synchronized 有哪些优势:

  1. 可以在线程等待锁的时候中断线程,synchronized 是做不到的。
  2. 可以尝试获取锁,如果获取不到就放弃,或者设置一定的时间,这也是 synchroized 做不到的。
  3. 可以设置公平锁,synchronized 默认是非公平锁,无法实现公平锁。

重入锁的好搭档-----Condition

synchronized 通过 Object 类的 wait 方法和 notify 方法实现线程之间的通信;
重入锁 ReentrantLock 通过 Condition 接口中的await()、signal()方法进行通信。

public interface Condition {void await() throws InterruptedException;boolean await(long time, TimeUnit unit) throws InterruptedException;long awaitNanos(long nanosTimeout) throws InterruptedException;boolean await(long time, TimeUnit unit) throws InterruptedException;void awaitUninterruptibly();boolean awaitUntil(Date deadline) throws InterruptedException;void signal();void signalAll();
}

Condition 为不和 Object 类的冲突,使用 await 方法 对应 wait 方法,signal 方法对应 notify 方法。signalAll 方法对应 notifyAll 方法;
awaitUninterruptibly 方法,该方法不会响应线程的中断,但 Object 的 wait 方法是会响应的。 awaitUntil 方法是等待到一个给定的绝对时间,除非调用了 signal 或者中断了。

package cn.think.in.java.lock.condition;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 重入锁的好搭档** await 使当前线程等待,同时释放当前锁,当其他线程中使用 signal 或者 signalAll 方法时,线程会重新获得锁并继续执行。*       或者当线程被中断时,也能跳出等待,这和 Object.wait 方法很相似。* awaitUninterruptibly() 方法与 await 方法基本相同,但是它并不会在等待过程中响应中断。* singal() 该方法用于唤醒一个在等待中的线程,相对的 singalAll 方法会唤醒所有在等待的线程,这和 Object.notify 方法很类似。*/
public class ConditionTest implements Runnable {static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();@Overridepublic void run() {try {lock.lock();// 该线程会释放 lock 的锁,也就是说,一个线程想调用 condition 的方法,必须先获取 lock 的锁。// 否则就会像 object 的 wait 方法一样,监视器异常condition.await();System.out.println("Thread is going on");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {ConditionTest t = new ConditionTest();Thread t1 = new Thread(t);t1.start();Thread.sleep(1000);// 通知 t1 继续执行// main 线程必须获取 lock 的锁,才能调用 condition 的方法。否则就是监视器异常,这点和 object 的 wait 方法是一样的。lock.lock(); // IllegalMonitorStateException// 从 condition 的等待队列中,唤醒一个线程。condition.signal();lock.unlock();}
}

ReadWriteLock:

线程不安全的原因来自于多线程对数据的修改,如果你不修改数据,根本不需要锁。我们完全可以将读写分离,提高性能,在读的时候不使用锁,在写的时候才加入锁。这就是 ReadWriteLock 的设计原理。

public interface ReadWriteLock {Lock readLock();Lock writeLock();
}

读写锁的使用:

package cn.think.in.java.lock;import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockDemo {static Lock lock = new ReentrantLock();static ReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();static Lock readLock = reentrantReadWriteLock.readLock();static Lock writeLock = reentrantReadWriteLock.writeLock();int value;public Object handleRead(Lock lock) throws InterruptedException {try {lock.lock();// 模拟读操作,读操作的耗时越多,读写锁的优势就越明显Thread.sleep(1000);return value;} finally {lock.unlock();}}public void handleWrite(Lock lock, int index) throws InterruptedException {try {lock.lock();Thread.sleep(1000); // 模拟写操作value = index;} finally {lock.unlock();}}public static void main(String[] args) {final ReadWriteLockDemo demo = new ReadWriteLockDemo();Runnable readRunnable = new Runnable() {@Overridepublic void run() {try {demo.handleRead(readLock);
//          demo.handleRead(lock);} catch (InterruptedException e) {e.printStackTrace();}}};Runnable writeRunnable = new Runnable() {@Overridepublic void run() {try {demo.handleWrite(writeLock, new Random().nextInt());
//          demo.handleWrite(lock, new Random().nextInt());} catch (InterruptedException e) {e.printStackTrace();}}};/*** 使用读写锁,这段程序只需要2秒左右* 使用普通的锁,这段程序需要20秒左右。*/for (int i = 0; i < 18; i++) {new Thread(readRunnable).start();}for (int i = 18; i < 20; i++) {new Thread(writeRunnable).start();}}}

两个循环:一个循环开启18个线程去读数据,一个循环开启两个线程去写。如果使用普通的重入锁,将耗时20秒,因为普通的重入锁在读的时候依然是串行的。而如果使用读写锁,只需要2秒,也就是写的时候是串行的。读的时候是并行的,极大的提高了性能。
注意:只要涉及到写都是串行的。比如读写操作,写写操作,都是串行的,只有读读操作是并行的。

ReadWriteLock遵守以下三条基本原则:

  1. 允许多个线程同时读共享变量;
  2. 只允许一个线程写共享变量;
  3. 如果一个写线程正在执行写操作,此时禁止读线程读共享变量。

ReentrantReadWriteLock是ReadWriteLock的一个实现类:

  1. 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。

  2. 重进入:读锁和写锁都支持线程重进入。

  3. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。

并发编程 Java 三把锁(Synchronized、ReentrantLock、ReadWriteLock)相关推荐

  1. JUC里面的相关分类|| java并发编程中,关于锁的实现方式有两种synchronized ,Lock || Lock——ReentrantLock||AQS(抽象队列同步器)

    JUC分类 java并发编程中,关于锁的实现方式有两种synchronized ,Lock AQS--AbstractQueuedSynchronizer

  2. java并发编程(并发编程的三个问题)

    什么是并发编程? 首先我们要知道什么是并发? 什么是并行? 并行: 多件事情在同一时刻同时发生 并发: 在同一时间内,多个事情交替执行 并发编程: 比如抢票,秒杀等在同一场景下,有大量的请求访问同一资 ...

  3. Java并发编程实战之互斥锁

    文章目录 Java并发编程实战之互斥锁 如何解决原子性问题? 锁模型 Java synchronized 关键字 Java synchronized 关键字 只能解决原子性问题? 如何正确使用Java ...

  4. java的尝试性问题_Java并发编程实战 03互斥锁 解决原子性问题

    文章系列 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和有序性的问题,那么还有一个原子性问题咱们还没解决.在第一篇文章01并发编程的Bug源头当中,讲到了把一个或者多 ...

  5. [Java并发编程(三)] Java volatile 关键字介绍

    [Java并发编程(三)] Java volatile 关键字介绍 摘要 Java volatile 关键字是用来标记 Java 变量,并表示变量 "存储于主内存中" .更准确的说 ...

  6. 由面试题“并发编程的三个问题”深入浅出Synchronied

    在面试的时候经常会问道一个问题 并发编程的三个问题是什么?? 那么我在这里先回答这个问题的答案,有三个问题,可见性,原子性还有有序性. 可见性:一个线程对一个主内存中的数据进行修改,其他线程也可以第一 ...

  7. 并发编程-java内存模型

    1. 基本概念 程序:静态,用于完成某些功能的代码. 进程:动态,运行中的程序 线程:进程中的实际运作单位,一个进程可以包含一个或多个线程. 2. JVM内存区域 堆:线程共享,存放实例对象 (OOM ...

  8. 徐无忌并发编程笔记:无锁机制CAS及其底层实现原理?

    徐无忌并发编程笔记:无锁机制CAS及其底层实现原理? 完成:第一遍 1.什么是CAS算法? compare and swap:比较与交换,是一种有名的无锁算法 CAS带来了一种无锁解决线程同步,冲突问 ...

  9. 学习笔记(28):Python网络编程并发编程-死锁与递归锁

    立即学习:https://edu.csdn.net/course/play/24458/296445?utm_source=blogtoedu 1.死锁(Lock()的局限性) 知识点:Lock()只 ...

最新文章

  1. Alibaba Cloud Linux 2.1903 LTS 64位服务器yum源下载404,Alibaba Cloud Linux 2实例中使用docker-ce、epel等YUM源安装软件失败
  2. android用什么测试类,android – 使用firebase对类进行JUnit测试
  3. mysql 备份大表 存储过程_mysql批量备份表的存储过程写法
  4. Codeforces Round #726 (Div. 2) E2. Erase and Extend (Hard Version) 贪心
  5. java 反射 json_java 反射机制构建JSON字符串
  6. 如何使用 TStringGrid 呈现大量的颜色信息 - 回复 冷公子 的问题
  7. 任务管理器使用不了-灰色
  8. SpringBoot23 分模块开发
  9. zabbix3.4+grafana5.0.1数据可视化
  10. 【数学建模】基于matlab时变参数随机波动率向量自回归模型(TVP-VAR)【含Matlab源码 037期】
  11. LaTeX快速入门(超详细~)
  12. 批量给pdf加水印,请用这个办法
  13. 华为手机如何安装Goole play教程及安装包
  14. 【DM642】ICELL Interface—Cells as Algorithm Containers
  15. axure 调整中继器列宽_Axure教程:用中继器做图片轮播
  16. python理财基金数据分析可视化系统
  17. 软件体系结构期末考试总结
  18. python average函数详解_Python内置函数详解——总结篇
  19. 题目:分别统计字符串中大写字母和小写字母的个数。
  20. 自己写的PDF转JPG的程序

热门文章

  1. 计算机 屏幕卡住,电脑卡屏是怎么回事 电脑屏幕死机或卡死处理方法
  2. Artifactory 简介
  3. HD-SDI芯片方案选择及其应用与发展方向
  4. 《分析服务从入门到精通读书笔记》第一章、数据分析基础(1)
  5. Python爬虫入门——2. 2爬取酷狗音乐top1-500歌曲信息
  6. 概率统计·样本及抽样分布【随机样本、抽样分布】
  7. Android6.0新特性
  8. WmiPrvSE.exe内存占用异常
  9. python股票量化交易(3)---趋势类指标MACD
  10. 用Matlab提取图片中泰文,利用Matlab提取图片中的数据