一、锁重入

  1. package com.roocon.thread.t6;
  2.  
  3. public class Demo {
  4. /*
  5. 当第一个线程A拿到当前实例锁后,进入a方法,那么,线程A还能拿到被当前实例所加锁的另一个
  6. 同步方法b吗?是不是只有当线程A释放了a方法的同步锁后,才可以去获取b方法的同步锁呢?
  7. */
  8. public synchronized void a(){
  9. System.out.println("a");
  10. b();
  11. }
  12.  
  13. public synchronized void b(){
  14. System.out.println("b");
  15. }
  16.  
  17. public static void main(String[] args) {
  18. new Thread(new Runnable() {
  19. @Override
  20. public void run() {
  21. Demo demo = new Demo();
  22. demo.a();
  23. }
  24. }).start();
  25. }
  26. }

运行结果:

  1. a
  2. b

以上结果说明,线程A在释放方法a的同步锁之前,是可以重新获得b方法的同步锁的。同一个线程拿到同一个对象的锁,它是可以进入另一个同步方法的,这就是锁的重入。以上代码仅仅是同一个线程在一个同步方法中去成功调用另一个同步方法,并且,锁的是同一个实例。那么,不同的线程拿同一把对象去加锁,会怎样进行呢?

  1. package com.roocon.thread.t6;
  2.  
  3. public class Demo {
  4. /*
  5. 当第一个线程A拿到当前实例锁后,进入a方法,那么,线程A还能拿到被当前实例所加锁的另一个
  6. 同步方法b吗?是不是只有当线程A释放了a方法的同步锁后,才可以去获取b方法的同步锁呢?
  7. */
  8. public synchronized void a(){
  9. System.out.println("a");
  10. try {
  11. Thread.sleep(5000);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16.  
  17. public synchronized void b(){
  18. System.out.println("b");
  19. try {
  20. Thread.sleep(8000);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25.  
  26. public static void main(String[] args) {
  27. Demo demo = new Demo();
  28. //Demo demo1 = new Demo();
  29. new Thread(new Runnable() {
  30. @Override
  31. public void run() {
  32. demo.a();
  33. }
  34. }).start();
  35. new Thread(new Runnable() {
  36. @Override
  37. public void run() {
  38. demo.b();
  39. }
  40. }).start();
  41. }
  42. }

运行结果:

  1. a
  2. b

虽然以上运行结果还是a b,但是,由于锁的是同一个实例,所以,在输出a之后,要等待5s才会输出b。若将以上代码修改为如下,锁的不是同一个实例:

  1. package com.roocon.thread.t6;
  2.  
  3. public class Demo {
  4. /*
  5. 当第一个线程A拿到当前实例锁后,进入a方法,那么,线程A还能拿到被当前实例所加锁的另一个
  6. 同步方法b吗?是不是只有当线程A释放了a方法的同步锁后,才可以去获取b方法的同步锁呢?
  7. */
  8. public synchronized void a(){
  9. System.out.println("a");
  10. try {
  11. Thread.sleep(5000);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16.  
  17. public synchronized void b(){
  18. System.out.println("b");
  19. try {
  20. Thread.sleep(8000);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25.  
  26. public static void main(String[] args) {
  27. Demo demo = new Demo();
  28. Demo demo1 = new Demo();
  29. new Thread(new Runnable() {
  30. @Override
  31. public void run() {
  32. demo.a();
  33. }
  34. }).start();
  35. new Thread(new Runnable() {
  36. @Override
  37. public void run() {
  38. demo1.b();
  39. }
  40. }).start();
  41. }
  42. }

运行结果:

  1. a
  2. b

a b几乎是同时输出的。

以上两个代码说明,如果多个线程同时去执行同步方法,如果锁的是同一个实例,那么必须等当前这个同步方法释放锁后,才可以去获取另一个同步锁方法。

而如果锁的不是同一个实例,那么,两个同步方法几乎是可以同时执行。有了以上基础,那么再来理解以下代码,就很简单了。

  1. package com.roocon.thread.t6;
  2.  
  3. public class Demo {
  4.  
  5. public synchronized void a(){
  6. System.out.println("a");
  7. try {
  8. Thread.sleep(5000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println("print b()");
  13. b();
  14. }
  15.  
  16. public synchronized void b(){
  17. System.out.println("b");
  18. try {
  19. Thread.sleep(8000);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24.  
  25. public static void main(String[] args) {
  26. Demo demo = new Demo();
  27. new Thread(new Runnable() {
  28. @Override
  29. public void run() {
  30. demo.a();
  31. }
  32. }).start();
  33. new Thread(new Runnable() {
  34. @Override
  35. public void run() {
  36. demo.b();
  37. }
  38. }).start();
  39. }
  40. }

运行结果:

  1. a
  2. print b()
  3. b
  4. b

以上结果,先输出a,过了5s后再输出print b()  b,再过了8s输出b,也就是,由于锁的是同一个实例,所以,只有线程1当a方法调用完毕后,线程2才可以获取该实例锁进入b方法。

二、自旋锁

自旋锁,自己在不停的旋转,旋的是CPU的时间片,也就是空转CPU。当另外一个线程没有执行结束时,它一直在自旋等待。它会一直等待另外的线程执行完毕。

  1. package com.roocon.thread.t6;
  2.  
  3. public class Demo2 {
    //多个线程执行完毕后,输出,全部执行完毕
    public static void main(String[] args) {
    new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName() + "开始执行...");
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "执行完毕了");
    }
    }).start();
    new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName() + "开始执行...");
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "执行完毕了");
    }
    }).start();
    System.out.println("全部执行完毕");
    }
    }

运行结果:

全部执行完毕
Thread-0开始执行...
Thread-1开始执行...
Thread-1执行完毕了
Thread-0执行完毕了

以上结果明显,主线程执行结束后,其他线程还在继续执行。那么,怎么解决这个问题呢?

加入条件判断,如果最后只剩下主线程了,则打印。

  1. package com.roocon.thread.t6;
  2.  
  3. public class Demo2 {
  4. //多个线程执行完毕后,输出,全部执行完毕
  5. public static void main(String[] args) {
  6. new Thread(new Runnable() {
  7. @Override
  8. public void run() {
  9. System.out.println(Thread.currentThread().getName() + "开始执行...");
  10. try {
  11. Thread.sleep(2000);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. System.out.println(Thread.currentThread().getName() + "执行完毕了");
  16. }
  17. }).start();
  18. new Thread(new Runnable() {
  19. @Override
  20. public void run() {
  21. System.out.println(Thread.currentThread().getName() + "开始执行...");
  22. try {
  23. Thread.sleep(2000);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println(Thread.currentThread().getName() + "执行完毕了");
  28. }
  29. }).start();
  30. if (Thread.activeCount()==1) {
  31. System.out.println("全部执行完毕");
  32. }
  33. }
  34. }

运行结果:

  1. Thread-0开始执行...
  2. Thread-1开始执行...
  3. Thread-0执行完毕了
  4. Thread-1执行完毕了

为什么不输出“全部执行完毕"呢?因为,以上代码是并行执行的,在执行if语句时,Thread.activeCount()根本就不等于1。所以呢,我们让它在不等于1的时候,也就是除了主线程还有别的线程时,让它自旋等待。自旋完毕后,再去执行输出”全部执行完毕“,达到想要的效果。

  1. package com.roocon.thread.t6;
  2.  
  3. import java.util.Random;
  4.  
  5. public class Demo2 {
  6. //多个线程执行完毕后,输出,全部执行完毕
  7. public static void main(String[] args) {
  8. new Thread(new Runnable() {
  9. @Override
  10. public void run() {
  11. System.out.println(Thread.currentThread().getName() + "开始执行...");
  12. try {
  13. Thread.sleep(new Random().nextInt(2000));
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println(Thread.currentThread().getName() + "执行完毕了");
  18. }
  19. }).start();
  20. new Thread(new Runnable() {
  21. @Override
  22. public void run() {
  23. System.out.println(Thread.currentThread().getName() + "开始执行...");
  24. try {
  25. Thread.sleep(new Random().nextInt(2000));
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. System.out.println(Thread.currentThread().getName() + "执行完毕了");
  30. }
  31. }).start();
  32. while (Thread.activeCount() != 1) {//其实在实际应用中,不能这样去判断线程的个数。全部执行完毕不一定会被正确输出。
          //自旋等待
  33. }
  34. System.out.println("全部执行完毕");
  35.  
  36. }
  37. }

运行结果:

  1. Thread-0开始执行...
  2. Thread-1开始执行...
  3. Thread-1执行完毕了
  4. Thread-0执行完毕了
    全部执行完毕

以上代码只能说是模拟自旋等待过程。

三、模拟死锁

  1. package com.roocon.thread.t6;
  2.  
  3. public class Demo3 {
  4. private Object obj1 = new Object();
  5. private Object obj2 = new Object();
  6.  
  7. public void a(){
  8. synchronized (obj1){
  9. try {
  10. Thread.sleep(3000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. synchronized (obj2){
  15. System.out.println("a");
  16. }
  17. }
  18. }
  19.  
  20. public void b(){
  21. synchronized (obj2){
  22. try {
  23. Thread.sleep(3000);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. synchronized (obj1){
  28. System.out.println("b");
  29. }
  30. }
  31. }
  32.  
  33. public static void main(String[] args) {
  34. Demo3 demo3 = new Demo3();
  35. new Thread(new Runnable() {
  36. @Override
  37. public void run() {
  38. demo3.a();
  39. }
  40. }).start();
  41. new Thread(new Runnable() {
  42. @Override
  43. public void run() {
  44. demo3.b();
  45. }
  46. }).start();
  47. }
  48.  
  49. }

运行结果:

控制台一直在运行,但是无任何输出。

通过命令检测是否真的发生了死锁:

点击线程,检测死锁:

参考资料:

《java并发编程与实战》龙果学院

Java并发编程原理与实战十一:锁重入&自旋锁&死锁的更多相关文章

  1. Java并发编程原理与实战五:创建线程的多种方式

    一.继承Thread类 public class Demo1 extends Thread { public Demo1(String name) { super(name); } @Override ...

  2. Java并发编程原理与实战四十二:锁与volatile的内存语义

    锁与volatile的内存语义 1.锁的内存语义 2.volatile内存语义 3.synchronized内存语义 4.Lock与synchronized的区别 5.ReentrantLock源码实 ...

  3. Java并发编程原理与实战三十一:Future&FutureTask 浅析

    一.Futrue模式有什么用?------>正所谓技术来源与生活,这里举个栗子.在家里,我们都有煮菜的经验.(如果没有的话,你们还怎样来泡女朋友呢?你懂得).现在女票要你煮四菜一汤,这汤是鸡汤, ...

  4. Java并发编程原理与实战二十一:线程通信wait&notify&join

    wait和notify wait和notify可以实现线程之间的通信,当一个线程执行不满足条件时可以调用wait方法将线程置为等待状态,当另一个线程执行到等待线程可以执行的条件时,调用notify可以 ...

  5. Java并发编程原理与实战十五:手动实现一个可重入锁

     package com.roocon.thread.ta1; public class Sequence { private MyLock lock = new MyLock(); private ...

  6. Java并发编程原理与实战十:单例问题与线程安全性深入解析

    单例模式我想这个设计模式大家都很熟悉,如果不熟悉的可以看我写的设计模式系列然后再来看本文.单例模式通常可以分为:饿汉式和懒汉式,那么分别和线程安全是否有关呢? 一.饿汉式 先看代码: package ...

  7. Java并发编程原理与实战九:synchronized的原理与使用

    一.理论层面 内置锁与互斥锁 修饰普通方法.修饰静态方法.修饰代码块 package com.roocon.thread.t3; public class Sequence { private sta ...

  8. Java并发编程原理与实战三十三:同步容器与并发容器

    1.什么叫容器? ----->数组,对象,集合等等都是容器.   2.什么叫同步容器? ----->Vector,ArrayList,HashMap等等.   3.在多线程环境下,为什么不 ...

  9. Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理

    1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...

随机推荐

  1. express框架结合ejs模板引擎使用

    我们在项目里建立一个views文件夹(必须),如果你不想使用views文件夹的话需要调用app.set("views","自定义文件夹名"),然后在里面建立一个 ...

  2. PAT 甲级 1004 Counting Leaves

    https://pintia.cn/problem-sets/994805342720868352/problems/994805521431773184 A family hierarchy is ...

  3. [微软官方]SQLSERVER的兼容级别

    ALTER DATABASE (Transact-SQL) 兼容级别 https://docs.microsoft.com/zh-cn/sql/t-sql/statements/alter-datab ...

  4. C# 调用 taskkill命令结束服务进程

    获取服务映像名称 windows服务安装后会在注册表中存储服务信息,路径是HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\[服务名称] 通过I ...

  5. java 数据结构与算法---队列

    原理来自百度百科 一.队列的定义 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表.进行插 ...

  6. VS2012 Nuget 安装 AutoMapper时报错的解决方法

    VS2012 在.net 4.0下安装AutoMapper时,会报以下错误: “AutoMapper”已拥有为“Standard.Library”定义的依赖项. 'AutoMapper' alread ...

  7. UVA10917_Walk Through the Forest

    无向图.对于两个相连的点,如果A到终点的最短路径大于B到终点的最短路径,那么A可以往B走,求最终从起点到终点有多少种走法? 首先我们可以直接预处理所有点到终点的最短路径.然后分别判断所有的边两点是否满 ...

  8. hbase 过滤器属性及其兼容性

    内容来自于<HBASE权威指南>,留存备查,由于版本的原因,可能已经有变化,在应用前兼容性需要测试.

  9. CVE-2018-1111劫持dhcp造成centos代码执行漏洞

    0x01 漏洞概述 近日,红帽官方发布了安全更新,修复了编号为CVE-2018-1111的远程代码执行漏洞,攻击者可以通过伪造DHCP服务器发送响应包,攻击红帽系统,获取root权限并执行任意命令. ...

  10. Hbase(七)hbase高级编程

    一.Hbase结合mapreduce 为什么需要用 mapreduce 去访问 hbase 的数据?     ——加快分析速度和扩展分析能力     Mapreduce 访问 hbase 数据作分析一 ...