脏读

一个常见的概念。在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的。

多线程线程安全问题示例

看一段代码:

public class ThreadDomain13
{private int num = 0;public void addNum(String userName){try{if ("a".equals(userName)){num = 100;System.out.println("a set over!");Thread.sleep(2000);}else{num = 200;System.out.println("b set over!");}System.out.println(userName + " num = " + num);}catch (InterruptedException e){e.printStackTrace();}}
}

写两个线程分别去add字符串"a"和字符串"b":

public class MyThread13_0 extends Thread
{private ThreadDomain13 td;public MyThread13_0(ThreadDomain13 td){this.td = td;}public void run(){td.addNum("a");}
}

public class MyThread13_1 extends Thread
{private ThreadDomain13 td;public MyThread13_1(ThreadDomain13 td){this.td = td;}public void run(){td.addNum("b");}
}

写一个主函数分别运行这两个线程:

public static void main(String[] args)
{ThreadDomain13 td = new ThreadDomain13();MyThread13_0 mt0 = new MyThread13_0(td);MyThread13_1 mt1 = new MyThread13_1(td);mt0.start();mt1.start();
}

看一下运行结果:

a set over!
b set over!
b num = 200
a num = 200

按照正常来看应该打印"a num = 100"和"b num = 200"才对,现在却打印了"b num = 200"和"a num = 200",这就是线程安全问题。我们可以想一下是怎么会有线程安全的问题的:

1、mt0先运行,给num赋值100,然后打印出"a set over!",开始睡觉

2、mt0在睡觉的时候,mt1运行了,给num赋值200,然后打印出"b set over!",然后打印"b num = 200"

3、mt1睡完觉了,由于mt0的num和mt1的num是同一个num,所以mt1把num改为了200了,mt0也没办法,对于它来说,num只能是100,mt0继续运行代码,打印出"a num = 200"

分析了产生问题的原因,解决就很简单了,给addNum(String userName)方法加同步即可:

public class ThreadDomain13
{private int num = 0;public synchronized void addNum(String userName){try{if ("a".equals(userName)){num = 100;System.out.println("a set over!");Thread.sleep(2000);}else{num = 200;System.out.println("b set over!");}System.out.println(userName + " num = " + num);}catch (InterruptedException e){e.printStackTrace();}}
}

看一下运行结果:

a set over!
a num = 100
b set over!
b num = 200

多个对象多个锁

在同步的情况下,把main函数内的代码改一下:

public static void main(String[] args)
{ThreadDomain13 td0 = new ThreadDomain13();ThreadDomain13 td1 = new ThreadDomain13();MyThread13_0 mt0 = new MyThread13_0(td0);MyThread13_1 mt1 = new MyThread13_1(td1);mt0.start();mt1.start();
}

看一下运行结果:

a set over!
b set over!
b num = 200
a num = 100

打印结果的方式变了,打印的顺序是交叉的,这又是为什么呢?

这里有一个重要的概念。关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁,其他线程都只能呈等待状态。但是这有个前提:既然锁叫做对象锁,那么势必和对象相关,所以多个线程访问的必须是同一个对象

如果多个线程访问的是多个对象,那么Java虚拟机就会创建多个锁,就像上面的例子一样,创建了两个ThreadDomain13对象,就产生了2个锁。既然两个线程持有的是不同的锁,自然不会受到"等待释放锁"这一行为的制约,可以分别运行addNum(String userName)中的代码。

synchronized方法与锁对象

上面我们认识了对象锁,对象锁这个概念,比较抽象,确实不太好理解,看一个例子,在一个实体类中定义一个同步方法和一个非同步方法:

public class ThreadDomain14_0
{public synchronized void methodA(){try{System.out.println("Begin methodA, threadName = " + Thread.currentThread().getName());Thread.sleep(5000);System.out.println("End methodA, threadName = " + Thread.currentThread().getName() + ", end Time = " + System.currentTimeMillis());} catch (InterruptedException e){e.printStackTrace();}}public void methodB(){try{System.out.println("Begin methodB, threadName = " + Thread.currentThread().getName() + ", begin time = " + System.currentTimeMillis());Thread.sleep(5000);System.out.println("End methodB, threadName = " + Thread.currentThread().getName());} catch (InterruptedException e){e.printStackTrace();}}
}

一个线程调用其同步方法,一个线程调用其非同步方法:

public class MyThread14_0 extends Thread
{private ThreadDomain14_0 td;public MyThread14_0(ThreadDomain14_0 td){this.td = td;}public void run(){td.methodA();}
}

public class MyThread14_1 extends Thread
{private ThreadDomain14_0 td;public MyThread14_1(ThreadDomain14_0 td){this.td = td;}public void run(){td.methodB();}
}

写一个main函数去掉用这两个线程:

public static void main(String[] args)
{ThreadDomain14_0 td = new ThreadDomain14_0();MyThread14_0 mt0 = new MyThread14_0(td);mt0.setName("A");MyThread14_1 mt1 = new MyThread14_1(td);mt1.setName("B");mt0.start();mt1.start();
}

看一下运行效果:

Begin methodA, threadName = A
Begin methodB, threadName = B, begin time = 1443697780869
End methodB, threadName = B
End methodA, threadName = A, end Time = 1443697785871

从结果看到,第一个线程调用了实体类的methodA()方法,第二个线程完全可以调用实体类的methodB()方法。但是我们把methodB()方法改为同步就不一样了,就不列修改之后的代码了,看一下运行结果:

Begin methodA, threadName = A
End methodA, threadName = A, end Time = 1443697913156
Begin methodB, threadName = B, begin time = 1443697913156
End methodB, threadName = B

从这个例子我们得出两个重要结论:

1、A线程持有Object对象的Lock锁,B线程可以以异步方式调用Object对象中的非synchronized类型的方法

2、A线程持有Object对象的Lock锁,B线程如果在这时调用Object对象中的synchronized类型的方法则需要等待,也就是同步

synchronized锁重入

关键字synchronized拥有锁重入的功能。所谓锁重入的意思就是:当一个线程得到一个对象锁后,再次请求此对象锁时时可以再次得到该对象的锁的。看一个例子:

public class ThreadDomain16
{public synchronized void print1(){System.out.println("ThreadDomain16.print1()");print2();}public synchronized void print2(){System.out.println("ThreadDomain16.print2()");print3();}public synchronized void print3(){System.out.println("ThreadDomain16.print3()");}
}

public class MyThread16 extends Thread
{public void run(){ThreadDomain16 td = new ThreadDomain16();td.print1();}
}

public static void main(String[] args)
{MyThread16 mt = new MyThread16();mt.start();
}

看一下运行结果:

ThreadDomain16.print1()
ThreadDomain16.print2()
ThreadDomain16.print3()

看到可以直接调用ThreadDomain16中的打印语句,这证明了对象可以再次获取自己的内部锁。这种锁重入的机制,也支持在父子类继承的环境中

异常自动释放锁

最后一个知识点是异常。当一个线程执行的代码出现异常时,其所持有的锁会自动释放。模拟的是把一个long型数作为除数,从MAX_VALUE开始递减,直至减为0,从而产生ArithmeticException。看一下例子:

public class ThreadDomain17
{public synchronized void testMethod(){try{System.out.println("Enter ThreadDomain17.testMethod, currentThread = " + Thread.currentThread().getName());long l = Integer.MAX_VALUE;while (true){long lo = 2 / l;l--;}} catch (Exception e){e.printStackTrace();}}
}

public class MyThread17 extends Thread
{private ThreadDomain17 td;public MyThread17(ThreadDomain17 td){this.td = td;}public void run(){td.testMethod();}
}

public static void main(String[] args)
{ThreadDomain17 td = new ThreadDomain17();MyThread17 mt0 = new MyThread17(td);MyThread17 mt1 = new MyThread17(td);mt0.start();mt1.start();
}

看一下运行结果:

Enter ThreadDomain17.testMethod, currentThread = Thread-0
Enter ThreadDomain17.testMethod, currentThread = Thread-1
java.lang.ArithmeticException: / by zeroat com.xrq.example.e17.ThreadDomain17.testMethod(ThreadDomain17.java:14)at com.xrq.example.e17.MyThread17.run(MyThread17.java:14)
java.lang.ArithmeticException: / by zeroat com.xrq.example.e17.ThreadDomain17.testMethod(ThreadDomain17.java:14)at com.xrq.example.e17.MyThread17.run(MyThread17.java:14)

因为打印结果是静态的,所以不是很明显。在l--前一句加上Thread.sleep(1)结论会更明显,第一句打出来之后,整个程序都停住了,直到Thread-0抛出异常后,Thread-1才可以运行,这也证明了我们的结论。

后记

文章里面的这些个结论,记一下都是很快的,但是是否记一下就好了?我认为记住这些结论一点都不重要,重要的应该是学习如何通过代码去验证这些结论。因为只有知道了如何通过代码去验证结论,才可以说真正对于synchronized关键字的各种细节有了感性、有了深入的理解,以后碰到其他synchronized的场景就可以以自己的理解去正确分析问题。

转自:http://www.cnblogs.com/xrq730/p/4851350.html。

转载于:https://www.cnblogs.com/mei0619/p/6635543.html

线程 synchronized锁机制相关推荐

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

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

  2. JUC多线程:synchronized锁机制原理 与 Lock锁机制

    前言: 线程安全是并发编程中的重要关注点,造成线程安全问题的主要原因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据.因此为了解决这个问题,我们可能需要这样一个方案,当存在多 ...

  3. 【Java 并发编程】线程锁机制 ( 线程安全 | 锁机制 | 类锁 | 对象锁 | 轻量级锁 | 重量级锁 )

    文章目录 一.线程安全 二.锁机制 ( 类锁 | 对象锁 ) 三.锁分类 ( 轻量级锁 | 重量级锁 ) 一.线程安全 多个线程同时访问 同一个共享变量 时 , 只要能保证 数据一致性 , 那么该变量 ...

  4. Java多线程4:synchronized锁机制

    脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过 ...

  5. synchronized锁机制 之 代码块锁

    synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...

  6. JAVA synchronized关键字锁机制(中)

    synchronized 锁机制简单的用法,高效的执行效率使成为解决线程安全的首选. 下面总结其特性以及使用技巧,加深对其理解. 特性: 1. Java语言的关键字,当它用来修饰一个方法或者一个代码块 ...

  7. Java多线程(五) —— 线程并发库之锁机制

    参考文献: http://www.blogjava.net/xylz/archive/2010/07/08/325587.html 一.Lock与ReentrantLock 前面的章节主要谈谈原子操作 ...

  8. 转载:Lock锁机制的原理及与Synchronized的比较——源码解说

    文章转载自:https://blog.csdn.net/Luxia_24/article/details/52403033(为了简化阅读难度,本文只挑选了大部分内容进行转载,并对代码进行了注释,更加详 ...

  9. synchronized()_JMM(四):浅谈synchronized锁

    对于synchronized锁机制而言,准确来说其应该属于JVM的范畴.这里基于行文的连贯性.完整性考虑,故将该部分内容在JMM系列中一并进行介绍 信号量.管程 Semaphore 信号量 Semap ...

最新文章

  1. Fedora 18下 升级内核后VirtualBox不能正常使用的问题
  2. 没有调查就没有发言权
  3. broker可以禁用吗 time_RuntimeBroker是什么进程,能禁用RuntimeBroker.exe进程么?
  4. 我是如何解决gitlab 命令行上传需要输密码
  5. jdbc mysql分页_JDBC【数据库连接池、DbUtils框架、分页】
  6. Golang实践录:oracle数据库实践
  7. 倍频程分析函数matlab,瞬时声压时域数据怎么用matlab进行1/3倍频程声压级分析
  8. bzoj2150,poj1422,poj1548
  9. Oracle 11.2.4.0 ACTIVE DATAGUARD 单实例安装(COPY创建备库)
  10. 一个react项目案例02 注册和登陆实现原理分析
  11. 网络的攻防,简单两步像黑客一样实现命令行对话,不需要社交软件也可以聊天,基础知识(一)
  12. 【Spark】Spark的机器学习算法库——Spark MLilb
  13. c语言程序设计教学工作总结,c语言教学的工作总结.docx
  14. vc语言c1083错误,vc++常见错误之二:“fatal error C1083: ”无法打开包括文件-Go语言中文社区...
  15. 帆软部署服务器linux,部署集成
  16. mysql max_allowed_packet 到底什么意思
  17. Android 设置投影效果
  18. 工程师学乐理(二)音阶及倾向性
  19. Mac安装brew报错汇总
  20. 【地图知识】城际通+凯立德+高德 各地图的介绍和比较.让你更清楚的了解地图

热门文章

  1. vb和python-vb与python
  2. python官网下载安装教程-各种版本的Python下载安装教程
  3. 零基础是学java还是python-零基础更适合学习Java还是python?
  4. 怎么自学python自动化测试-python自动化测试如何自动生成测试用例?
  5. python开发工程师面试题-一名python web后端开发工程师的面试总结
  6. python stm32-实现Python与STM32通信方式
  7. python银行系统-Python实现银行账户资金交易管理系统
  8. 刚安装的python如何使用-Python requests的安装与简单运用
  9. python爬虫项目-32个Python爬虫实战项目,满足你的项目慌
  10. pythonurllib模块-Python3中核心模块urllib的用法大全