版权声明:本文由吴仙杰创做整理,转载请注明出处:http://www.javashuo.com/article/p-flychtvo-gh.htmljava
在 Java 多线程编程中,咱们常须要考虑线程安全问题,其中关键字 synchronized
在线程同步中就扮演了很是重要的做用。shell
下面就对 synchronized
进行详细的示例讲解,其中本文构建 thread
的写法是采用 Java 8 新增的 Lambda 表达式。若是你对 Lambda 表达式还不了解,能够查看我以前的文章《Java 8 Lambda 表达式详解》。编程
首先咱们明确一点,synchronized
锁的不是代码,锁的都是对象。segmentfault
锁的对象有如下几种:安全
同步非静态方法(synchronized method
),锁是当前对象的实例对象。多线程
同步静态方法(synchronized static method
),锁是当前对象的类对象(Class 对象)。异步
同步代码块一(synchronized (this)
,synchronized (类实例对象)
),锁是小括号 ()
中的实例对象。性能
同步代码块二(synchronized (类.class)
),锁是小括号 ()
中的类对象(Class 对象)。优化
1)实例对象锁,不一样的实例拥有不一样的实例对象锁,因此对于同一个实例对象,在同一时刻只有一个线程能够访问这个实例对象的同步方法;不一样的实例对象,不能保证多线程的同步操做。this
2)类对象锁(全局锁),在 JVM 中一个类只有一个与之对应的类对象,因此在同一时刻只有一个线程能够访问这个类的同步方法。
同步非静态方法,实际上锁定的是当前对象的实例对象。在同一时刻只有一个线程能够访问该实例的同步方法,但对于多个实例的同步方法,不一样实例之间对同步方法的访问是不受同步影响(synchronized
同步失效)。
首先咱们尝试下,synchronized
同步失败的状况:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Bank xGBank = new Bank(); Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚"); Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private int money = 1000; public synchronized void deposit(Bank bank, int money) { // synchronized (this) { // 同步方法块(实例对象) // synchronized (bank) { // 同步方法块(实例对象) String threadName = Thread.currentThread().getName(); System.out.println(threadName + "--当前银行余额为:" + this.money); this.money += money; System.out.println(threadName + "--存入后银行余额为:" + this.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // } } }
运行结果:
小明--当前银行余额为:1000 小刚--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1000 小刚--存入后银行余额为:1200 小红--存入后银行余额为:1200
从上面的运行结果,咱们发现对 Bank
中 money
的操做并无同步,synchronized
失效了?
这是由于实例对象锁,只对同一个实例生效,对同一个对象的不一样实例不保证同步。
修改上述代码,实现同步操做。这里将有两种方案:只实例化一个或在方法块中的锁定类对象。
方案1、多个线程只对同一个实例对象操做:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); // Bank xGBank = new Bank(); // Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小刚"); Thread xHThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小红"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private int money = 1000; public synchronized void deposit(Bank bank, int money) { // synchronized (this) { // 同步方法块(实例对象) // synchronized (bank) { // 同步方法块(实例对象) String threadName = Thread.currentThread().getName(); System.out.println(threadName + "--当前银行余额为:" + this.money); this.money += money; System.out.println(threadName + "--存入后银行余额为:" + this.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // } } }
运行结果:
小明--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1200 小红--存入后银行余额为:1400 小刚--当前银行余额为:1400 小刚--存入后银行余额为:1600 ...
能够看到,结果正确执行。由于对于同一个实例对象,各线程之间访问其中的同步方法是互斥的。
方案2、在方法块中锁定类对象:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Bank xGBank = new Bank(); Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚"); Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private int money = 1000; public void deposit(Bank bank, int money) { synchronized (Bank.class) { // 全局锁 String threadName = Thread.currentThread().getName(); System.out.println(threadName + "--当前银行余额为:" + this.money); this.money += money; System.out.println(threadName + "--存入后银行余额为:" + this.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
小明--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1000 小红--存入后银行余额为:1200 小刚--当前银行余额为:1000 小刚--存入后银行余额为:1200
思考:从结果中咱们发现,线程是同步操做了,但为何在咱们的 money
怎么才 1200 啊?
要回答上面问题也很简单,首先线程是同步操做了,这个没有疑问,说明咱们的全局锁生效了,那为何钱少了,由于咱们这里 mew
了三个对象,三个对象都有各自的 money
,他们并不共享,因此最后都是 1200,最终一共仍是增长了 6000,钱一点没有少喔。
那有没有办法,让这些线程间共享 money
呢?方法很简单,只要设置 money
为 static
便可。
对于一个方法,可能包含多个操做部分,而每一个操做部分的消耗各不相同,并且并非全部的操做都是须要同步控制的,那么,是否能够将那些影响效率,又不须要同步操做的内容,提取到同步代码块外呢?
请看如下示例:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Bank xGBank = new Bank(); Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚"); Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private int money = 1000; public void deposit(Bank bank, int money) { String threadName = Thread.currentThread().getName(); synchronized (Bank.class) { // 同步方法块(实例对象) System.out.println(threadName + "--当前银行余额为:" + this.money); this.money += money; System.out.println(threadName + "--存入后银行余额为:" + this.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } try { // 假设这里是很是耗时,而且不须要同步控制的操做 Thread.sleep(2000); System.out.println(threadName + "--和钱无关,不须要同步控制的操做"); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果:
小明--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1000 小红--存入后银行余额为:1200 小刚--当前银行余额为:1000 小刚--存入后银行余额为:1200 小明--和钱无关,不须要同步控制的操做 小红--和钱无关,不须要同步控制的操做 小刚--和钱无关,不须要同步控制的操做
这时发现,各线程虽然都有本身的实例化对象,但其中操做 money
的部分是同步的,对于与 money
无关的操做则又是异步的。
结论:能够经过减小同步区域来优化同步代码块。
咱们知道同步的对象不是实例对象就是类对象。如今假设一个类有多个同步方法,那么当某个线程进入其中一个同步方法时,这个类的其它同步方法也会被锁住,形成其它与当前锁定操做的同步方法毫无关系的同步方法也被锁住,最后的结果就是影响了整个多线程执行的性能,使本来不须要互斥的方法也都进行了互斥操做。好比:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(xMBank::showInfo, "小刚"); xMThread.start(); xGThread.start(); } } class Bank { private int money = 1000; public void deposit(Bank bank, int money) { long begin = System.currentTimeMillis(); String threadName = Thread.currentThread().getName(); synchronized (this) { // 同步方法块(实例对象) this.money += money; try { System.out.println(threadName + "--当前银行余额为:" + this.money); // 模拟一个很是耗时的操做 Thread.sleep(5000); System.out.println(threadName + "--存入后银行余额为:" + this.money); long end = System.currentTimeMillis(); System.out.println(threadName + "--存入耗时:" + (end - begin)); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 一个与资金操做没有任务关系的同步方法 */ public void showInfo() { long begin = System.currentTimeMillis(); String threadName = Thread.currentThread().getName(); synchronized (this) { try { System.out.println(threadName + "--开始查看银行信息"); Thread.sleep(5000); System.out.println(threadName + "--银行详细信息..."); long end = System.currentTimeMillis(); System.out.println(threadName + "--查看耗时:" + (end - begin)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
小明--当前银行余额为:1200 小明--存入后银行余额为:1200 小明--存入耗时:5000 小刚--开始查看银行信息 小刚--银行详细信息... 小刚--查看耗时:10000
从运行结果中,咱们看到小刚这个线程无缘无故多等了 5 秒钟,严重影响了线程性能。
针对上面的状况,咱们能够采用多个实例对象锁的方案解决,好比:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(xMBank::showInfo, "小刚"); xMThread.start(); xGThread.start(); } } class Bank { private int money = 1000; private final Object syncDeposit = new Object(); // 同步锁 private final Object syncShowInfo = new Object(); // 同步锁 public void deposit(Bank bank, int money) { long begin = System.currentTimeMillis(); String threadName = Thread.currentThread().getName(); synchronized (this.syncDeposit) { // 同步方法块(实例对象) this.money += money; try { System.out.println(threadName + "--当前银行余额为:" + this.money); // 模拟一个很是耗时的操做 Thread.sleep(5000); System.out.println(threadName + "--存入后银行余额为:" + this.money); long end = System.currentTimeMillis(); System.out.println(threadName + "--存入耗时:" + (end - begin)); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 一个与资金操做没有任务关系的同步方法 */ public void showInfo() { long begin = System.currentTimeMillis(); String threadName = Thread.currentThread().getName(); synchronized (this.syncShowInfo) { try { System.out.println(threadName + "--开始查看银行信息"); Thread.sleep(5000); System.out.println(threadName + "--银行详细信息..."); long end = System.currentTimeMillis(); System.out.println(threadName + "--查看耗时:" + (end - begin)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:
小刚--开始查看银行信息 小明--当前银行余额为:1200 小刚--银行详细信息... 小明--存入后银行余额为:1200 小明--存入耗时:5000 小刚--查看耗时:5000
咱们发现,两个线程间同步被取消了,性能问题也解决了。
总结:能够建立不一样同步方法的不一样同步锁(减少锁的范围)来优化同步代码块。
同步静态方法,锁的是类对象而不是某个实例对象,因此能够理解为对于静态方法的锁是全局的锁,同步也是全局的同步。
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Bank xGBank = new Bank(); Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小刚"); Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小红"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private static int money = 1000; public synchronized static void deposit(Bank bank, int money) { // synchronized (Bank.class) { // 全局锁 String threadName = Thread.currentThread().getName(); System.out.println(threadName + "--当前银行余额为:" + Bank.money); Bank.money += money; System.out.println(threadName + "--存入后银行余额为:" + Bank.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // } } }
运行结果:
小明--当前银行余额为:1000 小明--存入后银行余额为:1200 小红--当前银行余额为:1200 小红--存入后银行余额为:1400 小刚--当前银行余额为:1400 小刚--存入后银行余额为:1600
同步锁 synchronized
要点:
synchronized
锁的不是代码,锁的都是对象。
实例对象锁:同步非静态方法(synchronized method
),同步代码块(synchronized (this)
,synchronized (类实例对象)
)。
类对象(Class 对象)锁:同步静态方法(synchronized static method
),同步代码块(synchronized (类.class)
)。
相同对象的不一样的实例拥有不一样的实例对象锁,但类对象锁(全局锁)有仅只有一个。
优化同步代码块的方式有,减小同步区域或减少锁的范围。