【Java线程安全】 synchronized同步方法、同步块:模拟抢票、模拟取款
synchronized的使用
大佬之所以叫大佬,就是因為他們即使一次看不懂,看二十遍也要看懂,再對萌新說:這個方法不是挺簡單的嘛
1、同步方法
要注意的是,synchronized鎖的不是方法,而是對象的資源。
33行,成員方法鎖的是this,也就是p,方法中使用的資源必須都是對象p的資源,才能正確的鎖住。
package cn.hanquan.test;public class Test {public static void main(String[] args) throws InterruptedException {Person p = new Person();new Thread(p, "程序猿").start();new Thread(p, "傻子").start();new Thread(p, "世紀帥男").start();} }class Person implements Runnable {private int ticket = 100;@Overridepublic void run() {while (ticket > 0) {try {Thread.sleep(100);grab();} catch (InterruptedException e) {e.printStackTrace();}}}// 同步方法:搶票private synchronized void grab() throws InterruptedException {if (ticket <= 0) {return;}ticket--;System.out.println(Thread.currentThread().getName() + "\t" + ticket);} }在以上代碼中,
// 同步方法:搶票private synchronized void grab() throws InterruptedException {if (ticket <= 0) {return;}ticket--;System.out.println(Thread.currentThread().getName() + "\t" + ticket);}等同于:
// 同步方法:搶票private void grab() throws InterruptedException {synchronized (this) {if (ticket <= 0) {return;}ticket--;System.out.println(Thread.currentThread().getName() + "\t" + ticket);}}說明:synchronized對方法的同步,其實鎖的就是this對象。
而下面這種方式不可以,因為ticket的對象是變動的:
// 同步方法:搶票private void grab() throws InterruptedException {synchronized ((Integer) ticket) {// 這里要的是引用類型,ticket是int類型,需要強轉一下。if (ticket <= 0) {return;}ticket--;System.out.println(Thread.currentThread().getName() + "\t" + ticket);}}鎖this的時候,雖然this的屬性在變,但是this這個大的對象不變。但是 ticket 這個數是一直在變的,把它轉化成對象(Integer) ticket之后,對象一直在變。這樣不可以。synchronized用于鎖不變的對象才可以,變化的對象鎖不住。
- 這里的變指的不是內容,而是地址
性能的再優化:double checking
看似代碼變多了,實際上減少了鎖定的范圍(在鎖定之前過濾一下,如果票數小于0,就不鎖了,直接返回)
2、同步塊
(1)線程不安全示例
package cn.hanquan.test;/** 線程安全----使用同步塊*/public class Account {public int money = 100;// 賬戶余額public static void main(String[] args) throws InterruptedException {// 賬戶Account account = new Account();ATM you = new ATM(account, 80, "你");ATM wife = new ATM(account, 90, "她");you.start();wife.start();} }// 模擬取款機 class ATM extends Thread {Account account;int drawMoney;int packetTotal;// constructorpublic ATM(Account account, int drawMoney, String name) {super(name);this.account = account;this.drawMoney = drawMoney;}@Overridepublic void run() {try {test();} catch (InterruptedException e) {e.printStackTrace();}}// 取錢操作public synchronized void test() throws InterruptedException {// 這里的account是作為參數傳進來的if (account.money - drawMoney < 0) {return;}Thread.sleep(1000);account.money -= drawMoney;packetTotal += drawMoney;System.out.println("【" + this.getName() + "】 " + "賬戶余額:" + account.money + " 口袋的錢:" + packetTotal);} }運行結果:
【她】 賬戶余額:-70 口袋的錢:90
【你】 賬戶余額:-70 口袋的錢:80
看運行結果,發現:賬戶余額成了負數。
也就是說,當余額不足以取出時,仍然進行了取款操作。
這樣的結果說明,synchronized沒有鎖住正確的對象。我們發現,實際上,public synchronized void test()鎖住的,是this所指向的ATM本身的資源;而取款操作,改變的是外部的account對象的資源。
也就是說,應該鎖住的是銀行賬戶,而不是ATM取款機本身。
因此,做出以下改進:
(2)線程安全示例
下面是上例中代碼的改進,實現了線程安全。注意44行使用的同步塊。
package cn.hanquan.test;/** 線程安全----使用同步塊*/public class Account {public int money = 100;// 賬戶余額public static void main(String[] args) throws InterruptedException {// 賬戶Account account = new Account();ATM you = new ATM(account, 80, "你");ATM wife = new ATM(account, 90, "她");you.start();wife.start();} }// 模擬取款機 class ATM extends Thread {Account account;int drawMoney;int packetTotal;// constructorpublic ATM(Account account, int drawMoney, String name) {super(name);this.account = account;this.drawMoney = drawMoney;}@Overridepublic void run() {try {test();} catch (InterruptedException e) {e.printStackTrace();}}// 取錢操作public void test() throws InterruptedException {synchronized (account) {// account本身是獨立的類,作為參數才能傳進來的if (account.money - drawMoney < 0) {return;}Thread.sleep(1000);account.money -= drawMoney;packetTotal += drawMoney;System.out.println("【" + this.getName() + "】 " + "賬戶余額:" + account.money + " 口袋的錢:" + packetTotal);}} }運行結果:
【你】 賬戶余額:20 口袋的錢:80
3、synchronized在容器中的使用
22行前面要加一個sleep(100),因為要避免還沒數完數就打印了的情況。
總結
以上是生活随笔為你收集整理的【Java线程安全】 synchronized同步方法、同步块:模拟抢票、模拟取款的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Java多线程】线程优先级:优先级高,
- 下一篇: 【Java多线程】并发时的线程安全:快乐