2019独角兽企业重金招聘Python工程师标准>>>

1. 重入锁

重入锁可以完全替代synchronized关键字。使用java.util.concurrent.locks.ReentrantLock类实现,下面是一个重入锁的简单例子:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.ReentrantLock;public class Example1 implements Runnable {public static ReentrantLock lock = new ReentrantLock();public static int i = 0;public static void main(String[] args) throws InterruptedException {Example1 exp = new Example1();Thread t1 = new Thread(exp);Thread t2 = new Thread(exp);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.unlock();}}}}

使用重入锁保护临界区资源i,确保多线程对i操作的安全性。与synchronized相比,重入锁有着显示的操作过程。开发人员必须手动指定何时加锁,何时释放锁。也正因为这样,重入锁对逻辑控制的灵活性要远远好于synchronized。但在退出临界区时,必须记得释放锁。

1.1 中断响应

对于synchronized来说,如果一个线程在等待锁,那么结果只有两种,要么获得锁,要么就保持等待。而使用重入锁,则提供另一种可能,那就是线程可以被中断。

比如你和朋友约好去打球,如果你等了半小时,朋友还没到,然后你接到一个电话,说不能如约了,那么你就可以打道回府了。

中断正式提供了一套类似的机制。如果一个线程正在等待锁,那么它依然可以收到一个通知,被告知无需再等待,可以停止工作了。这种情况对死锁有一定的帮助。

下面的代码产生了一个死锁,但得益于锁中断,我们可以很轻松地解锁这个死锁:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.ReentrantLock;public class Example2 implements Runnable {public static ReentrantLock lock1 = new ReentrantLock();public static ReentrantLock lock2 = new ReentrantLock();int lock;public Example2(int lock) {this.lock = lock;}public static void main(String[] args) throws InterruptedException {Example2 emp1 = new Example2(1);Example2 emp2 = new Example2(2);Thread t1 = new Thread(emp1);Thread t2 = new Thread(emp2);t1.start();t2.start();Thread.sleep(1000);t2.interrupt();}@Overridepublic void run() {try {if (lock == 1) {lock1.lockInterruptibly();try {Thread.sleep(500);} catch (InterruptedException e) {}lock2.lockInterruptibly();} else {lock2.lockInterruptibly();try {Thread.sleep(500);} catch (InterruptedException e) {}lock1.lockInterruptibly();}} catch (InterruptedException e) {e.printStackTrace();} finally {if(lock1.isHeldByCurrentThread())lock1.unlock();if(lock2.isHeldByCurrentThread())lock2.unlock();System.out.println(Thread.currentThread().getId() + ": 线程退出");}}}

线程t1和t2启动后,t1先占用lock1,再占用lock2;t2先占用lock2,在请求lock1。因此很容易形成t1和t2的互相等待。使用lockInterruptiblu()方法,这是一个可以对中断进行响应的锁申请动作,即在等待锁的过程中,可以响应中断。

1.2 锁申请等待限时

出了等待外部中断通知,要避免死锁还有另一种方法,那就是限时等待。还是以打球为例,如果朋友迟迟不来,又无法联系到他,那么,在等待1,2个小时后,就可以打道回府了。

对于线程来说,通常,我们无法判断为什么一个线程迟迟拿不到锁。也许是因为死锁了,也许是因为产生了饥饿。但如果给定一个等待时间,让线程主动放弃,那么对系统来说是有意义的,可以使用tryLock()方法进行一次限时等待:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;public class Example3 implements Runnable {public static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {Example3 exp = new Example3();Thread t1 = new Thread(exp);Thread t2 = new Thread(exp);t1.start();t2.start();}@Overridepublic void run() {try {if (lock.tryLock(5, TimeUnit.SECONDS))Thread.sleep(6000);elseSystem.out.println("get lock failed");} catch (InterruptedException e) {e.printStackTrace();} finally {if (lock.isHeldByCurrentThread())lock.unlock();}}}

tryLock()方法接收两个参数,一个表示等待时长,一个表示计时单位。如果超过时间还没有得到锁,返回false,如果成功获得锁,则返回true。

tryLock()方法也可以不输入参数直接运行。在这种情况下,当前线程会尝试获得锁,如果锁并未被其他线程占用,则申请成功,并立即返回true。如果获得不到锁,则不会进行等待,立即返回false。这种模式不会引起线程等待,因此也不会产生死锁:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.ReentrantLock;public class Example4 implements Runnable {public static ReentrantLock lock1 = new ReentrantLock();public static ReentrantLock lock2 = new ReentrantLock();int lock;public Example4(int lock) {this.lock = lock;}public static void main(String[] args) {Example4 r1 = new Example4(1);Example4 r2 = new Example4(2);Thread t1 = new Thread(r1);Thread t2 = new Thread(r2);t1.start();t2.start();}@Overridepublic void run() {if (lock == 1) {while (true) {if (lock1.tryLock()) {try {try {Thread.sleep(500);} catch (InterruptedException e) {}if (lock2.tryLock()) {try {System.out.println(Thread.currentThread().getId() + ": My Job done");return;} finally {lock2.unlock();}}} finally {lock1.unlock();}}}} else {while (true) {if (lock2.tryLock()) {try {try {Thread.sleep(500);} catch (InterruptedException e) {}if (lock1.tryLock()) {try {System.out.println(Thread.currentThread().getId() + ": My Job done");return;} finally {lock1.unlock();}}} finally {lock2.unlock();}}}}}}

上面的代码采用了非常容易死锁的加锁顺序,引发死锁。

但是使用tryLock()后,这种情况就大大改善了。只要执行足够长的时间,线程总会得到所有需要的资源,从而正常执行。

1.3 公平锁

在大多数情况下,锁的申请都是非公平的。而公平的锁,则不是这样,它会按照时间的先后顺序,保证先到先得,后到后得。公平锁的一大特点是:它不会产生饥饿现象。只要你排队,最终还是可以等到资源:

public ReentrantLock(boolean fair);

公平锁看似优美,但是要实现公平锁必然要系统维护一个有序队列,因此公平锁的实现成本比较高,性能相对也非常低下,因此,默认情况下,锁是非公平的。

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.ReentrantLock;public class Example5 implements Runnable {public static ReentrantLock fairLock = new ReentrantLock(true);public static void main(String[] args) {Example5 exp = new Example5();Thread t1 = new Thread(exp, "t1");Thread t2 = new Thread(exp, "t2");t1.start();t2.start();}@Overridepublic void run() {while (true) {try {fairLock.lock();System.out.println(Thread.currentThread().getName());} finally {fairLock.unlock();}}}}

对上面ReentrantLock的几个重要方法整理如下:

  • lock():获得锁,如果锁已经被占用,则等待;
  • lockInterruptibly():获得锁,但优先响应中断;
  • tryLock():尝试获得锁,如果成功,返回true,失败返回false。该方法不等待;
  • tryLock(long time, TimeUnit unit):在给定时间内尝试获得锁;
  • unlock():释放锁;

就重入锁的实现来看,主要集中在Java层面。包含三个要素:

  1. 原子状态;
  2. 等待队列;
  3. 阻塞与恢复;

2. Condition条件

Condition与重入锁是相关联的,通过Lock接口的newCondition()方法可以生成一个与当前重入锁绑定的Condition实例。利用它,可以让线程在合适的时间等待,或者在某一个特定的时刻得到通知,继续执行。

Condition接口提供的基本方法如下:

void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUnitl(Date deadline) throws InterruptedException;
void signal();
void signalAll();

以上方法含义如下:

  • await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()或者signalAll()方法时,线程会重新获得锁继续执行。或者当线程被中断时,也能跳出等待;
  • awaitUninterruptible()方法与await()方法基本相同,但是它并不会在等待过程中响应中断;
  • singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程;

下面的代码简单演示了Condition的功能:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;public class Example6 implements Runnable {public static ReentrantLock lock = new ReentrantLock();public static Condition condition = lock.newCondition();public static void main(String[] args) throws InterruptedException {Example6 exp = new Example6();Thread t1 = new Thread(exp);t1.start();Thread.sleep(2000);lock.lock();condition.signal();lock.unlock();}@Overridepublic void run() {try {lock.lock();condition.await();System.out.println("Thread is going on");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}

当线程使用Condition.await()时,要求线程持有相关的重入锁,在await()调用后,这个线程会释放这把锁,同理,在Condition.signal()方法调用时,也要求线程先获得相关的锁。在调用signal()方法后,一般需要释放相关的锁,谦让给被唤醒的线程,让它可以继续执行。

3. 信号量

信号量为多线程协作提供了更为强大的控制方法。广义上说,信号量是对锁的扩展。无论是内部锁synchronzied还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而信号量却可以指定多个线程,同时访问某一个资源。信号量主要提供了一下构造函数:

public Semaphore(int permits)
public Semaphore(int permits, boolean fair)

信号量的主要逻辑方法有:

public void acquire();
public void acquireUninterruptibly();
public boolean tryAcquire();
public boolean tryAcquire(long timeout, TimeUnit unit);
public void release();

acquire()方法尝试获得一个准入的许可。若无法获得,则线程会等待,直到有线程释放一个许可或当前线程被中断。acquireUninterruptibly()方法和acquire()方法类似,但是不影响中断。tryAcquire()尝试获得一个许可,如果成功返回true,失败则返回false,它不会进行等待,例子返回。release()用于在线程访问资源结束后,释放一个许可,以使其他等待许可的线程可以进行资源访问:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;public class Example7 implements Runnable {final Semaphore semp = new Semaphore(5);public static void main(String[] args) {ExecutorService exec = Executors.newFixedThreadPool(20);final Example7 exp = new Example7();for (int i = 0; i < 20; i++) {exec.submit(exp);}}@Overridepublic void run() {try {semp.acquire();Thread.sleep(2000);System.out.println(Thread.currentThread().getId() + ": done!");semp.release();} catch (InterruptedException e) {e.printStackTrace();}}}

这里声明了一个包含5个许可的信号量。这意味着同时可以有5个线程进入代码段。申请信号量使用acquire()操作,在离开时,务必使用release()释放信号量。

4. ReadWriteLock读写锁

读写分离锁可以有效地帮助减少锁竞争,以提升系统性能。用锁分离的机制来提升性能非常容易理解,必须线程A1,A2,A3进行写操作,B1,B2,B3进行读操作,如果使用重入锁或者内部锁,则理论上说所有读之间、读与写之间、写和写之间都是串行操作。

 
非阻塞 阻塞
阻塞 阻塞
  • 读-读不互斥;
  • 读-写互斥;
  • 写-写互斥;

如果在系统中,读操作次数远远大于写操作,则读写锁就可以发挥最大的功效:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class Example8 {private static ReentrantLock lock = new ReentrantLock();private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();private static Lock readLock = readWriteLock.readLock();private static Lock writeLock = readWriteLock.readLock();private 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 Example8 exp = new Example8();Runnable readRunnable = new Runnable() {@Overridepublic void run() {try {exp.handleRead(readLock);// exp.handleRead(lock);} catch (InterruptedException e) {e.printStackTrace();}}};Runnable writeRunnable = new Runnable() {@Overridepublic void run() {try {exp.handleWrite(writeLock, new Random().nextInt());// exp.handleWrite(lock, new Random().nextInt());} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 18; i++) {new Thread(readRunnable).start();}for (int i = 18; i < 20; i++) {new Thread(writeRunnable).start();}}}

上面这段代码使用读写锁,程序大约两秒就可以运行完成,而使用注释中的重入锁,则需要20秒才可以运行完成。

5. 倒计时器

CountDownLatch是一个非常实用的多线程控制工具类。通常用来控制线程等待,它可以让某一个线程等待知道倒计时结束,再开始执行。

public CountDownLatch(int count)

构造函数接收一个整数作为参数,即当前这个计数器的计数个数。

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Example9 implements Runnable {static final CountDownLatch end = new CountDownLatch(10);static final Example9 exp = new Example9();public static void main(String[] args) throws InterruptedException {ExecutorService exec = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {exec.submit(exp);}end.await();System.out.println("fire!");exec.shutdown();}@Overridepublic void run() {try {Thread.sleep(new Random().nextInt(10) * 1000);System.out.println("check complete");end.countDown();} catch (InterruptedException e) {e.printStackTrace();}}}

上面代码生成了一个计数器,量为10。表示需要有10个线程完成任务,等待在计数器上的线程才能继续执行。待10个任务全部完成后,主线程才能继续执行。

6. 循环栅栏

CyclicBarrirer是另外一种多线程控制使用工具。和CountDownLatch非常类似,它也可以实现线程间的计数等待,但功能更复杂且强大。

假设我们将计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,然后接着凑齐下一批10个线程,这就是循环栅栏的含义。

下面使用循环栅栏演示了司令命令士兵完成任务的场景:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class Example10 {public static class Soldier implements Runnable {private String soldier;private final CyclicBarrier cyclic;public Soldier(CyclicBarrier cyclic, String soldierName) {this.cyclic = cyclic;this.soldier = soldierName;}@Overridepublic void run() {try {cyclic.await();doWork();cyclic.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}public void doWork() {try {Thread.sleep(Math.abs(new Random().nextInt() % 10000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println(soldier + "完成任务");}}public static class BarrierRun implements Runnable {boolean flag;int N;public BarrierRun(boolean flag, int N) {this.flag = flag;this.N = N;}@Overridepublic void run() {if (flag) {System.out.println("司令:[士兵" + N + "个,任务完成!]");} else {System.out.println("司令:[士兵" + N + "个,集合完毕!]");flag = true;}}}public static void main(String[] args) {final int N = 10;Thread[] allSoldier = new Thread[N];boolean flag = false;CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));System.out.println("集合队伍!");for (int i = 0; i < N; ++i) {System.out.println("士兵" + i + "报道!");allSoldier[i] = new Thread(new Soldier(cyclic, "士兵" + i));allSoldier[i].start();}}}

在计数器打到指标时,执行run方法(),每一个士兵线程会执行定义的run()方法。每一个士兵线程都会等待,直到所有的士兵集合完毕。集合完毕后,意味着CyclicBarrier的一次计数器完成,当再一次调用CyclicBarrier.await()时,会进行下一次计数。上面的程序打印结果如下:

集合队伍!
士兵0报道!
士兵1报道!
士兵2报道!
士兵3报道!
士兵4报道!
士兵5报道!
士兵6报道!
士兵7报道!
士兵8报道!
士兵9报道!
司令:[士兵10个,集合完毕!]
士兵3完成任务
士兵6完成任务
士兵2完成任务
士兵0完成任务
士兵8完成任务
士兵5完成任务
士兵4完成任务
士兵9完成任务
士兵1完成任务
士兵7完成任务
司令:[士兵10个,任务完成!]

7. 线程阻塞工具类

LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程内任意位置让线程阻塞。和Thread.suspend()相比,它弥补了由于resume()在前发生,导致线程无法继续执行的情况。也不需要先获得某个对象的锁,也不会抛出InterruptedException异常。

LockSupport的静态方法park()可以阻塞当前线程,类似的还有parkNanos()、parkUnitl()等方法:

package cn.net.bysoft.java.concurrency.design.ch03;import java.util.concurrent.locks.LockSupport;public class Example11 {public static Object u = new Object();static ChangeObjectThread t1 = new ChangeObjectThread("t1");static ChangeObjectThread t2 = new ChangeObjectThread("t2");public static class ChangeObjectThread extends Thread {public ChangeObjectThread(String name) {super.setName(name);}@Overridepublic void run() {synchronized (u) {System.out.println("in " + getName());LockSupport.park();}}}public static void main(String[] args) throws InterruptedException {t1.start();Thread.sleep(100);t2.start();LockSupport.unpark(t1);LockSupport.unpark(t2);t1.join();t2.join();}}

LockSupport类使用类似信号量的机制。它为每一个线程准备了一个许可,如果许可可用,那么park()函数会立即返回。并且消费这个许可,如果许可不可用,就会阻塞。而unpark()则使得一个许可变为可用。

这个特点使得:即使unpark()操作发生在park()之前,它也可以使下一次的park()操作立即返回。这也就是上诉代码可顺利结束的主要原因。

转载于:https://my.oschina.net/u/2450666/blog/831951

读书笔记 — Java高并发程序设计 — 第三章 — 锁相关推荐

  1. 读书笔记 — Java高并发程序设计 — 第二章 — 基础(上)

    2019独角兽企业重金招聘Python工程师标准>>> 1 有关线程的一些事 线程的所有状态都在Thread中的State枚举中定义: public enum State {/*** ...

  2. Java高并发程序设计(三)——JDK并发包(二)

    引言 好久没来学习Java高并发程序设计了,感觉在慢慢遗忘之前学过的内容,今天打算重新拾起. Condition Condition与前两章讲的Object.wait() 和Object.notify ...

  3. Java高并发程序设计(三)——JDK并发包(一)

    引言 读书读了一个多月了,这个月工作很多,空闲时间少,但是我还是在每天上班之前,下班之后挤出零星的时间写写博客,看看社区.每天下班以后,经常搜一些最近学习的相关知识点来加深理解.上次我介绍了前两章的内 ...

  4. 【实战Java高并发程序设计6】挑战无锁算法

    我们已经比较完整得介绍了有关无锁的概念和使用方法.相对于有锁的方法,使用无锁的方式编程更加考验一个程序员的耐心和智力.但是,无锁带来的好处也是显而易见的,第一,在高并发的情况下,它比有锁的程序拥有更好 ...

  5. JAVA高并发程序设计(葛一鸣著)读书笔记

    本文为JAVA高并发程序设计(葛一鸣著)读书笔记.这本书对于刚刚入门的童鞋来讲可能有点深,我推荐可以先看看Java多线程编程核心技术(高洪岩著)一书. 第一章 走入并行世界 什么是同步和异步? 同步就 ...

  6. 《实战 Java 高并发程序设计》笔记——第3章 JDK 并发包(二)

    文章目录 3.2 线程复用:线程池 3.2.1 什么是线程池 3.2.2 不要重复发明轮子:JDK 对线程池的支持 1. 固定大小的线程池 2. 计划任务 3.2.3 刨根究底:核心线程池的内部实现 ...

  7. 《实战Java高并发程序设计》github笔记和源码

    笔记 <实战Java高并发程序设计>中有很多代码范例,适合初学者通过实践入门并发编程,这本书有个问题就是前面的代码都用JDK7,第六章开始又用JDK8了 笔者做了相关笔记并整理源代码,欢迎 ...

  8. 实战java高并发程序设计-笔记进行中

    <JAVA并发编程实践>:出书时间太早,内容比较散,专业术语翻译较早和现在有差异 <Java并发编程的艺术>:手绘图较多文字内容较少,主要讲解并发实现的底层原理和面临的问题,底 ...

  9. Java高并发程序设计入门

    转自:http://blog.csdn.net/johnstrive/article/details/50667557 说在前面 本文绝大部分参考<JAVA高并发程序设计>,类似读书笔记和 ...

最新文章

  1. 35 w年薪,入职CV算法岗,我一个双非本科生如何做到的?
  2. 强大的德国教育如何锻造孩子的健全人格
  3. python中sin函数在哪个模块中_从零开始第5步:Python 函数和模块
  4. [HNOI2017]单旋
  5. elasticsearch index 之merge
  6. 前端学习(2627):node安装
  7. oracle数据库速度测试,【原创】验证在Oracle数据库中操纵数据的各种方法的速度...
  8. Java微服务:这个画饼是个谎言,但你却不能忽视它
  9. offsetLeft
  10. matlab实现2dpsk调制与解调,(完整版)matlab设计2DPSK信号调制与解调
  11. WEP(有线等效保密)
  12. 租车APP都哪些,租车平台成功案例
  13. 在Sever 2012中应用iSCSI目标程序
  14. echarts图表折线图柱状图多个X轴Y轴以及一个Y轴反向
  15. 学习/思考 - 优秀资源 - 收集
  16. 橘子树拍击弦贝司音源 Orange Tree Samples CoreBass Cherry Slapped
  17. 行列式的计算方法(含四种,看完就会!)
  18. 首席工程师揭秘:LinkedIn大数据后台是如何运作的
  19. SAP 使用ABNAN后资本化和价值补录
  20. 产品思维驱动自我成长

热门文章

  1. 自学计算机二级office用什么书,暑假里想要自学计算机二级office有哪些什么好的建议...
  2. omwin密立根油滴实验数据处理程序_大物实验报告更新啦~
  3. java线程泄露_面试官:小伙子先来说一下可能引起Java内存泄露的场景吧
  4. 百练OJ:1835:宇航员
  5. thingsboard官网单机并发量
  6. 信息安全与网络安全,你分清了吗?
  7. Docker 更换下载镜像源
  8. Thrift介绍以及Java中使用Thrift实现RPC示例
  9. textarea怎样隐藏滚动条
  10. MyBatisPlus3.x代码生成器生成实体类自定义需要填充的字段