Java多线程/并发04、synchronized同步
来源:互联网 发布:怎么看淘宝卖家信誉 编辑:程序博客网 时间:2024/05/20 06:52
一、synchronized使用
多个线程并发时,经常面临的问题就是会操作相同的资源。比如商品下单,库存量修改,转帐等。
举个例子:
家里有一口炒菜的锅,老大要做辣椒炒肉(制作步骤:a1.放肉,a2.放盐,a3.放辣椒),老二要做番茄炒蛋(制作步骤,b1.放蛋,b2.放盐,b3.放番茄)。两人把食材准备好后,都要抢着炒自己拿手的菜。老大往锅里放肉(a1),老二这时挤过来,放蛋(b1),放盐(b2);老大又挤过来,放盐(a2)…….后面自己脑补,这菜还能吃吗?在这里老大是线程A,老二是线程B,锅是资源。
下面我们模拟一下这种场景:
package JConcurrence.Study;/*定义烹饪外部类*/class Pan { /*烹饪方法,该方法输出步骤*/ public void Cook(String[] steps) { for (int i = 0; i < steps.length; i++) { /*模拟竞争造成的线程等待,这样效果明显些*/ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print(steps[i]); } System.out.println(""); } /*青椒炒肉制作步骤:a1.放肉,a2.放盐,a3.放辣椒 a4 a5....*/ String[] steps_LaJiaoChaoRou={"a1.","a2.","a3.","a4.","a5.","a6.","a7.","a8.","a9.","a10.","a11.","a12.","a13.","a14.","a15.","a16.","a17.","a18.","OK:辣椒炒肉"}; /*番茄炒蛋制作步骤:b1.放蛋,b2.放盐,b3.放番茄......*/ String[] steps_FanQieChaoDan={"b1.","b2.","b3.","b4.","b5.","b6.","OK:番茄炒蛋"};}public class chapter2 { public static void main(String[] args) { final Pan pan=new Pan(); /*线程1:老大炒青椒炒肉。*/ new Thread(){ public void run() { /*为了看出错乱效果,这里用死循环,一段时间后手工点击停止运行按钮*/ while (true) { try { /*模拟青椒炒肉需要5秒;*/ Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } pan.Cook(pan.steps_LaJiaoChaoRou); } } }.start(); /*线程2:老二炒番茄炒蛋。*/ new Thread(){ public void run() { /*为了看出错乱效果,这里用死循环,一段时间后手工点击停止运行按钮*/ while (true) { try { /*模拟番茄炒蛋需要2秒;*/ Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } pan.Cook(pan.steps_FanQieChaoDan); } } }.start(); }}
输出结果:
这是我的电脑输出的结果,红框标出来的番茄炒蛋和辣椒炒肉这两盘菜肯定不好吃,因为里面步骤和放入的材料都不对。
我们希望的是炒菜是一个连贯的事务工作,中间环节不可断开。每个人在做菜的时侯,要求是独占这口锅完成炒菜的动作,谁也不能中途打断。
为了解决这个问题,可以使用synchronized同步代码块来对公共部分进行同步操作。
在上面这个场景中,公共部分都就是Cook方法,每个人在调用Cook方法时,都不允许别人同时也执行Cook方法。因此对于线程来说,它希望的是自己独占Cook方法,直到Cook方法运行结束。
所以,我们只需要修改Cook方法就行了。下面我们借助Synchronzied关键字来实现。
Synchronzied关键字有两种用法:synchronized 方法和 synchronized 块:
1、第一种方法:在Cook方法上加关键字synchronized
/*烹饪方法,该方法输出步骤加入synchronized关键字*/public synchronized void Cook(String[] steps) { for (int i = 0; i < steps.length; i++) { /*模拟竞争造成的线程等待,这样效果明显些*/ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print(steps[i]); } System.out.println(""); }
可以正常输出:
2、第二种方法:定义锁,使用synchronized{}块
/*定义一个锁,锁可以是任意类型,如:Object,Int,String等,值也可以任意,但需要保证所有访问该值的线程得到同样的结果*/ private String lock = "anything"; /*烹饪方法,该方法输出步骤*/ public void Cook(String[] steps) { /*用synchronized块包含公共执行部分*/ synchronized(lock){ for (int i = 0; i < steps.length; i++) { /*模拟竞争造成的线程等待,这样效果明显些*/ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print(steps[i]); } System.out.println(""); } }
可以正常输出:
总结:Synchronzied可以用来修改对象中的方法,将对象加锁。不管哪个线程,每次运行到这个方法或者语句块时,都要检查这个方法或语句块有没有其它线程占用,有的话要等这个线程运行完成后。如果没有的话,直接运行。
最后补充一点:把synchronized关键字放在类的前面,这个类中的所有方法都是同步方法。
二、synchronized锁的对象
在上例中,synchronized方法在执行中并没有像synchronized同步代码块 一样,声明一个自定义的锁对像。那么它锁住的是什么呢?
先看代码:
package JConcurrence.Study;/*定义外部测试类SyncLockTest*/class SyncLockTest{ /*使用synchronized同步方法 */ public synchronized void testSyncMethod() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":testSyncMethod:" + i); } } /*使用synchronized同步代码块 */ public void testSyncBlock() { synchronized ("lock") { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":testSyncBlock:" + i); } } } }public class ExecuteDemo { public static void main(String[] args) { final SyncLockTest LockTest=new SyncLockTest(); /*使用synchronized方法 */ new Thread(new Runnable() { public void run() { /*休眠三秒可以更好的观测错乱效果*/ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } /*执行*/ LockTest.testSyncMethod(); } }).start(); /*使用synchronized代码块 */ new Thread(new Runnable() { public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } /*执行*/ LockTest.testSyncBlock(); } }).start(); }}
输出结果摘取一段:
Thread-1:testSyncBlock:0
Thread-0:testSyncMethod:0
Thread-1:testSyncBlock:1
Thread-0:testSyncMethod:1
Thread-1:testSyncBlock:2
Thread-0:testSyncMethod:2
Thread-1:testSyncBlock:3
Thread-1:testSyncBlock:4
Thread-1:testSyncBlock:5
Thread-0:testSyncMethod:3
Thread-1:testSyncBlock:6
结果显示两个线程并发执行,交替输出。说明两个方法之间不存在同步。
因为它们锁的对像不同,也就是使用的监视器对象不同。
修改synchronized同步代码块的代码,将synchronized ("lock")
换成synchronized (this)
重新运行结果:
Thread-0:testSyncMethod:97
Thread-0:testSyncMethod:98
Thread-0:testSyncMethod:99
Thread-1:testSyncBlock:0
Thread-1:testSyncBlock:1
Thread-1:testSyncBlock:2
Thread-1:testSyncBlock:3
Thread-1:testSyncBlock:4
Thread-1:testSyncBlock:5
Thread-1:testSyncBlock:6
Thread-1:testSyncBlock:7
可见一个线程完成0-99打印后,另一个线程才从0开始,并非之前的交替进行打印。
由此可知,synchronized 同步方法采用的锁对像,就是实例化对像本身,即this。
现在看看静态方法。我们在testSyncMethod方法前加上static
/*使用synchronized静态方法 */ public static synchronized void testSyncMethod() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":testSyncMethod:" + i); } }
Main方法中原来的LockTest.testSyncMethod()改为静态调用SyncLockTest.testSyncMethod()
执行结果如下:
Thread-1:testSyncBlock:15
Thread-1:testSyncBlock:16
Thread-0:testSyncMethod:0
Thread-1:testSyncBlock:17
Thread-0:testSyncMethod:1
Thread-0:testSyncMethod:2
Thread-0:testSyncMethod:3
Thread-0:testSyncMethod:4
Thread-0:testSyncMethod:5
结果显示两个线程并发执行,交替输出。说明两个方法之间不存在同步。
修改synchronized同步代码块的代码,将synchronized (this)
换成synchronized (this.getClass())
或者synchronized (SyncLockTest.class)
(个人建议用this.getClass(),与类名解耦)
重新运行结果摘取:
Thread-0:testSyncMethod:97
Thread-0:testSyncMethod:98
Thread-0:testSyncMethod:99
Thread-1:testSyncBlock:0
Thread-1:testSyncBlock:1
Thread-1:testSyncBlock:2
Thread-1:testSyncBlock:3
Thread-1:testSyncBlock:4
Thread-1:testSyncBlock:5
可见一个线程完成0-99打印后,另一个线程才从0开始,并非之前的交替进行打印。
由此可知,synchronized 静态同步方法采用的锁对象就是类本身(字节码文件对象)。
归纳起来:
1、synchronized代码块的锁可以是任意类型的变量,如string,int,object等,但需要保证任何需要同步执行的线程访问时都是同一个值。
2、synchronized 同步方法的锁是对象(Instance)自己,即this。
3、synchronized 静态(static)同步方法的锁是类本身(字节码文件对象),即this.getClass()或 SyncLockTest.class
再归纳线程争夺锁的规则:
首先,我们把synchronized同步方法(包括static方法)理解成指定了特殊锁(如this,this.getClass)的synchronized{}代码块。
这样理解后,总结起来有三点:
1、多个线程访问同一个对象(如上面的LockTest)内部的synchronized(lockA){}块中代码时,同一时间内,只可能有一个线程能够访问lockA锁住的代码块。这个很好懂,不然还叫什么同步。
2、在对像中有多个拥有同样锁的synchronized( lockA ){}代码块时(如上面的testSyncMethod()和testSyncMethod()都拥有this锁),当一个线程访问任意其中一个synchronized(lockA){}代码块时,其他线程不可以访问所有拥有lockA锁的其它synchronized(lockA){}代码块内容。
3、在对像中有多个拥有不同锁的代码块时,如:synchronized(lockA){},synchronized(lockB){},synchronized(this){},当一个线程访问object中的synchronized(lockA){}代码块时,其它线程仍可以访问synchronized(lockB){},synchronized(this){}。
- Java多线程/并发04、synchronized同步
- Java并发编程:synchronized多线程同步详解
- java多线程、并发系列之 (synchronized)同步与加锁机制
- java Synchronized 多线程同步
- java多线程同步,Synchronized
- Java 多线程同步--synchronized
- Java 多线程 synchronized同步
- java多线程同步 synchronized
- 【Java多线程与并发库】04 传统的线程同步通信技术-synchronized/wait/notify/notifyAll
- Java多线程同步Synchronized详解
- Java多线程同步 synchronized关键字
- Java多线程同步与synchronized
- Java多线程同步 synchronized使用
- Java多线程,线程同步synchronized,线程死锁【线程池常规用法】多线程并发处理
- Java 多线程:synchronized 多线程同步关键字
- Java 多线程:synchronized 多线程同步关键字
- 多线程并发编程(三):多线程同步互斥Synchronized
- java多线程之-----对象及变量的并发访问1(synchronized同步方法)
- Hi3531a+adv7611 driver 调试过程
- android(体验一个项目天气预报开发)-3
- 浅谈react受控组件与非受控组件
- Python AttributeError: ‘module’ object has no attribute ‘A’
- solr常用命令总结
- Java多线程/并发04、synchronized同步
- BZOJ 3105: [cqoi2013]新Nim游戏
- 编写java程序151条建议读书笔记(4)
- c 调用gtk剪贴板
- jquery extend 解析
- hadoop 2.7.3 单机模式
- JavaScript学习笔记20-while循环
- gitHub使用入门和github for windows的安装教程
- 百度地图定位