目錄
以性能換安全?
1.synchronized 同步
(1)不同的對象競爭同一個資源(鎖得住)
(2)不同的對象競爭不同的資源(鎖不住)
(3)單例模式加鎖
synchronized 同步塊
2.JUC并發包
明鎖機制
3.volatile關鍵字
可見性:
以空間(內存)換安全
1.線程本地變量:ThreadLocal
以性能換安全?
1.synchronized 同步
????????synchronized 同步,也叫做暗鎖,自動釋放鎖。
????????鎖得住的條件是:同一個對象(同一個資源)
????????如果synchronized 修飾方法,就是給這個方法加鎖。這時候如果有個線程被JVM調度執行,只有等這個線程執行完畢以后,這個線程才會自動釋放鎖,其它的線程才有機會執行。
(1)不同的對象競爭同一個資源(鎖得住)
兩個線程對象t1和t2競爭同一個UserRunnable對象
如下面的代碼創建了兩個線程,run()方法被synchronized修飾,只有等一個線程執行完run()方法并自動釋放鎖,另一個線程才能執行:
package com.sync;public class UserRunnable implements Runnable{@Overridepublic synchronized void run() {// TODO Auto-generated method stubfor(int i=0;i<=10;i++){System.out.println(Thread.currentThread().getName()+",執行"+i);}}// 自動釋放鎖}
package com.sync;public class Test {public static void main(String[] args) {UserRunnable ur = new UserRunnable(); // 只有一個Runnable對象Thread t1 = new Thread(ur);Thread t2 = new Thread(ur);t1.start();t2.start();}}
運行結果:
(2)不同的對象競爭不同的資源(鎖不住)
兩個線程使用不同的UserThread
對象,鎖的是不同的對象
?????????兩個線程使用不同的鎖,沒有競爭關系,所以無法實現同步機制。
package com.sync1;public class UserThread extends Thread{//synchronized是一個加鎖操作//synchronized首先是對同一個資源(同一個對象)的加鎖//鎖得住的條件是:同一個對象public synchronized void run(){for(int i=0;i<=10;i++){System.out.println(Thread.currentThread().getName()+",執行"+i);}}}
package com.sync1;public class Test {public static void main(String[] args) {UserThread u1 = new UserThread();UserThread u2 = new UserThread();u1.start();u2.start();}}
運行結果:
解決方法:
package com.sync1;public class Test {public static void main(String[] args) {// UserThread u1 = new UserThread();
// UserThread u2 = new UserThread();
//
// u1.start();
// u2.start();UserThread u1 = new UserThread();Thread t1 = new Thread(u1);Thread t2 = new Thread(u1);t1.start();t2.start();}}
(3)單例模式加鎖
????????使用synchronized給getInstance()方法加鎖后,一個線程進入后立刻鎖住,對象new完后自動解鎖,此時對象已經創建完成,別的線程進入不用重新創建對象,所以兩個線程返回的地址一樣。
package com.sync2;public class User {private static User u ;private User(){}public synchronized static User getInstance(){if(null == u){System.out.println(Thread.currentThread().getName()+"創建對象");u = new User();}return u;}}
package com.sync2;public class UserThread1 extends Thread{public void run(){User u1 = User.getInstance();System.out.println(u1);}}
package com.sync2;public class UserThread2 extends Thread{public void run(){User u2 = User.getInstance();System.out.println(u2);}}
package com.sync2;public class Test {public static void main(String[] args) {UserThread1 u1 = new UserThread1();UserThread2 u2 = new UserThread2();u1.start();u2.start();}}
運行結果:
synchronized 同步塊
synchronized(),()里面一定是引用類型對象,必須是同一個對象。
對需要競爭的代碼進行鎖定,降低鎖定的范圍,優化性能。
? ? ? ? 下面是一個多線程銀行賬戶操作模擬系統,包含:
- 1個銀行賬戶(Bank)
- 3個支付平臺線程(支付寶、微信、京東)
- 使用同步機制保證賬戶操作的線程安全
1. Bank 類(共享資源)
package com.sync3;//銀行類
public class Bank {// 卡號private String bankNumber = "";// 賬戶的金額private double money = 0.0;public Bank(String bankNumber, double money) {this.bankNumber = bankNumber;this.money = money;}//操作銀行賬戶的方法 synchronized 修飾方法,會給整個方法加鎖,導致整個方法被鎖定,導致鎖定的范圍過大//synchronized 同步塊,對需要競爭的代碼進行鎖定,降低鎖定的范圍,優化性能public void operatorBank(double operatorMoney){System.out.println("歡迎您到銀行辦理具體的業務");synchronized(Bank.class)// ()里面一定是引用類型對象,必須是同一個對象{this.money += operatorMoney;System.out.println(Thread.currentThread().getName()+",操作的金額是:"+operatorMoney+",現在賬戶剩余的金額是:"+this.money);}System.out.println("謝謝您,歡迎下次光臨");}}
2. 支付線程類
package com.sync3;public class JindongThread extends Thread{double opMoney;Bank bank;public JindongThread(String threadName,double opMoney,Bank bank){super(threadName);this.opMoney = opMoney;this.bank = bank;}public void run(){this.bank.operatorBank(this.opMoney);}
}
package com.sync3;public class WeixinThread extends Thread{double opMoney;Bank bank;public WeixinThread(String threadName,double opMoney,Bank bank){super(threadName);this.opMoney = opMoney;this.bank = bank;}public void run(){this.bank.operatorBank(this.opMoney);}}
package com.sync3;public class ZhifubaoThread extends Thread{double opMoney;Bank bank;public ZhifubaoThread(String threadName,double opMoney,Bank bank){super(threadName);this.opMoney = opMoney;this.bank = bank;}public void run(){this.bank.operatorBank(this.opMoney);}}
3.Test類
package com.sync3;/*** synchronized同步,就是加鎖的操作,保證多線程競爭同一個資源時的安全。*/
public class Test {public static void main(String[] args) {Bank bank = new Bank("10086", 1000.0);ZhifubaoThread z = new ZhifubaoThread("支付寶", 300, bank);WeixinThread w = new WeixinThread("微信", -400, bank);JindongThread j = new JindongThread("京東", 600, bank);z.start();w.start();j.start();}}
運行結果:
2.JUC并發包
? ? ? ? JUC 是 java.util.concurrent 包及其子包(如 java.util.concurrent.atomic 和 java.util.concurrent.locks)的非官方但廣為流傳的縮寫,全稱是 Java Util Concurrent。它是 Java 標準庫中為并發編程提供強大、高性能、線程安全的工具類的核心包。
明鎖機制
-
公平鎖 (Fair Lock):new ReentrantLock(true);
-
原則:遵循“先來后到”的公平原則。
-
行為:當鎖被釋放時,會優先分配給等待時間最長的線程。就像現實中排隊一樣,先來的先獲得服務。
-
優點:所有線程都能得到執行機會,不會產生“饑餓”現象。
-
缺點:性能開銷較大。因為需要維護一個有序隊列來管理線程,上下文切換更頻繁。
-
-
非公平鎖 (Non-fair Lock):new ReentrantLock(false);?
-
原則:允許“插隊”。
-
行為:當鎖被釋放時,所有正在嘗試獲取鎖的線程(包括剛來和已經等待的)都會去競爭,誰搶到就是誰的。如果沒搶到,才會被加入到等待隊列的末尾。
-
優點:吞吐量高,性能更好。減少了線程切換的開銷,充分利用了CPU時間片。
-
缺點:可能導致某些線程長時間等待,永遠拿不到鎖(饑餓)。
-
package com.demo2;import java.util.concurrent.locks.Lock;public class Buy implements Runnable {Lock lock;private boolean flag = true;private int sum = 10;public Buy(Lock lock) {this.lock = lock;}@Overridepublic void run() {// TODO Auto-generated method stubwhile (flag) {lock.lock(); // 明鎖try {Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + ",買到了票,是第" + this.sum-- + "張票");if (this.sum <= 1) {this.flag = false;}lock.unlock(); // 一定要手動釋放鎖} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}
package com.demo2;//JUC并發包
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Test {public static void main(String[] args) {//明鎖機制 //new ReentrantLock(true); 是true是公平鎖,公平的意思就是大家都有機會執行//new ReentrantLock(false); 是false是非公平鎖,公非平的意思就是會有一個線程獨占執行Lock lock = new ReentrantLock(true);Buy buy = new Buy(lock);new Thread(buy,"張三線程").start();new Thread(buy,"李四線程").start();new Thread(buy,"王五線程").start();}}
公平鎖運行結果:
非公平鎖運行結果:
3.volatile關鍵字
? volatile
是Java提供的一種輕量級的同步機制,用于確保變量的可見性和一致性。
可見性:
-
當一個線程修改了volatile變量時,新值會立即被刷新到主內存
-
其他線程讀取該變量時,會強制從主內存重新讀取最新值
-
解決了線程間數據不可見的問題
不保證原子性:
-
volatile不能保證復合操作的原子性
-
比如
count++
這樣的操作(讀取-修改-寫入)不是原子性的
下面這段代碼展示了volatile
最經典的用法——作為狀態標志位:
package com.volatiledemo;//volatile不能保證非原子操作的可見性和一致性
public class Test {public static void main(String[] args) {UserThread u = new UserThread();u.start();try {Thread.sleep(5000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}u.setFlag(false);}}
package com.volatiledemo;public class UserThread extends Thread{// 當flag聲明為volatile時,主線程修改flag = false后,UserThread立即能看到這個變化private volatile boolean flag =true;private int a = 0;//private boolean flag =true;public void run(){System.out.println(Thread.currentThread().getName()+",線程開始運行");while(flag){a =10;a++;}System.out.println(Thread.currentThread().getName()+",線程結束運行"+"a的值為:"+a);}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}}
運行結果:
以空間(內存)換安全
1.線程本地變量:ThreadLocal
????????ThreadLocal是Java提供的線程局部變量,它為每個使用該變量的線程提供獨立的變量副本,實現了線程間的數據隔離。
工作原理:
- ThreadLocal內部使用ThreadLocalMap存儲數據
- 以當前線程作為key來存儲和檢索值
- 每個線程都有自己獨立的ThreadLocalMap
下面代碼中雖然四個線程共享同一個UserRunnable實例,但由于使用了ThreadLocal:
-
每個線程都有自己獨立的User對象
-
茉莉1線程設置的年齡不會影響梔子1線程的年齡值
-
各線程的年齡值保持獨立,互不干擾
package com.threadlocal;import java.util.Random;public class UserRunnable implements Runnable {// 線程本地變量 key-valueThreadLocal<User> userLocal = new ThreadLocal<User>();private User getUser() {// key:對象hascode() value:對應這個對象 // 首先嘗試從ThreadLocal獲取User對象,每個User對象就是一個鍵值對User u = userLocal.get();// 如果不存在則創建新的User對象并存入ThreadLocal,確保每個線程有自己獨立的User實例if (null == u) {u = new User();System.out.println(u);userLocal.set(u);}return u;}@Overridepublic void run() {// TODO Auto-generated method stubSystem.out.println(Thread.currentThread().getName() + ",執行run方法");Random r = new Random();int age = r.nextInt(100);System.out.println(Thread.currentThread().getName() + ",產生的隨機數的年齡為:" + age);// 從ThreadLocal獲取當前線程的User對象User u = this.getUser();u.setAge(age);try {Thread.sleep(2000);} catch (InterruptedException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}System.out.println(Thread.currentThread().getName() + ",before設置年齡的值為:" + u.getAge());try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + ",after設置年齡的值為:" + u.getAge());} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public static void main(String[] args) {UserRunnable u = new UserRunnable();Thread s1 = new Thread(u, "茉莉1");Thread s2 = new Thread(u, "梔子1");Thread s3 = new Thread(u, "茉莉2");Thread s4 = new Thread(u, "梔子2");s1.start();s2.start();s3.start();s4.start();}}
package com.threadlocal;public class User {private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
運行結果: