文档章节

Java多线程4:synchronized锁机制

M
 MackCC_Sun
发布于 2016/07/08 14:52
字数 1900
阅读 32
收藏 0

脏读 一个常见的概念。在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的。 多线程线程安全问题示例 看一段代码: 复制代码 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 zero at com.xrq.example.e17.ThreadDomain17.testMethod(ThreadDomain17.java:14) at com.xrq.example.e17.MyThread17.run(MyThread17.java:14) java.lang.ArithmeticException: / by zero at 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才可以运行,这也证明了我们的结论。

本文转载自:http://www.cnblogs.com/xrq730/p/4851350.html

M
粉丝 4
博文 158
码字总数 29095
作品 0
朝阳
技术主管
私信 提问
4种常用Java线程锁的特点,性能比较及使用场景

多个线程同时对同一个对象进行读写操作,很容易会出现一些难以预料的问题。所以很多时候我们需要给代码块加锁,同一时刻只允许一个线程对某个对象进行操作。多线程之所以会容易引发一些难以发现...

mikechen优知
03/10
176
0
《成神之路-高级篇》Java并发编程——锁

本文是《成神之路系列文章》的第一篇,主要是关于JVM的一些介绍。 持续更新中 数据库相关锁机制 数据库的锁机制 表级锁、行级锁、页级锁 共享锁、排他锁 乐观锁与悲观锁 乐观锁、悲观锁 乐观...

HollisChuang's Blog
2018/10/14
0
0
Java多线程学习(四)等待/通知(wait/notify)机制

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
2018/04/16
0
0
synchronized与ThreadLocal

synchronized是实现java的同步机制。同步机制是为了实现同步多线程对相同资源的并发访问控制。保证多线程之间的通信。 同步的主要目的是保证多线程间的数据共享。同步会带来巨大的性能开销,...

bigYuan
2013/07/18
596
2
Java多线程学习(二)synchronized关键字(2)

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
2018/04/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

c语言实现Sqlite3的创建db和增删改查db操作

SQLite,是一款轻型的数据库,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中使用广泛,现在准备学习一下sqlite3的使用方法并写一个测试demo,后面在项目智能...

jorin_zou
28分钟前
4
0
【2019年8月版本】OCP 071认证考试最新版本的考试原题-第2题

choose three Which three are true about the CREATE TABLE command? A) It can include the CREATE...INDEX statement for creating an index to enforce the primary key constraint. B) ......

oschina_5359
31分钟前
5
0
如何在二维码中循环批量插入图片

现在二维码种类比较多,为了突出二维码的个性及吸引客户,很多朋友都喜欢在二维码上插入图片。想要每个二维码都与众不同,但是有的时候需要批量插入图片数量有限,如果制作的二维码比较多的话...

中琅软件
32分钟前
6
0
LTR那点事—AUC及其与线上点击率的关联详解

LTR(Learning To Rank)学习排序是一种监督学习(SupervisedLearning)的排序方法,现已经广泛应用于信息索引,内容推荐,自然语言处理等多个领域。以推荐系统为例,推荐一般使用多个子策略...

达观数据
32分钟前
5
0
IntelliJ 如何显示代码的代码 docs

希望能够在 IntelliJ 代码上面显示方法的 docs。 如何进行显示? 你可以使用 Ctrl + Q 这个快捷键来查看方法的 Docs。 https://blog.ossez.com/archives/3061...

honeymoose
36分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部