線程安全問題的解決方法:
1、同步代碼塊
2、同步方法
3、鎖機制
第一種方法:同步代碼塊
格式:
synchronized(鎖對象) {
可能會出現線程安全問題的代碼(訪問共享數據的代碼)
}
注意:
1、通過代碼塊的鎖對象,可以是任意的對象
2、必須保證多個線程使用的鎖對象是同一個
3、鎖對象的作用是把同步代碼快鎖住,只允許一個線程在同步代碼塊執行
Runable實現類
public class RunableImpl implements Runnable {
private int ticket = 100;
// 創建一個鎖對象,鎖對象應該創建在run方法外,因為不同的線程應該使用同一個鎖對象,如果在run方法里面,就會每個線程自己創建一個鎖對象,也就是不唯一了
Object object = new Object();
@Override
public void run() {
while(true) {
synchronized (object) {// 訪問了共享數據的代碼,也就是可能出現線程安全問題的代碼,寫在同步代碼塊里面
if(ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is selling ticket number--> " + ticket);
ticket--;
}
}
}
}
}
再次開啟多個線程,發現就沒有出現線程安全問題了
package cn.zhuobo.day12.threadSafe;
public class RunableDemo {
public static void main(String[] args) {
RunableImpl run = new RunableImpl();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}
}
同步代碼塊的原理:
使用了一個鎖對象,叫同步鎖,對象鎖,也叫同步監視器,當開啟多個線程的時候,多個線程就開始搶奪CPU的執行權,比如現在t0線程首先的到執行,就會開始執行run方法,遇到同步代碼快,首先檢查是否有鎖對象,發現有,則獲取該鎖對象,執行同步代碼塊中的代碼。之后當CUP切換線程時,比如t1得到執行,也開始執行run方法,但是遇到同步代碼塊檢查是否有鎖對象時發現沒有鎖對象,t1便被阻塞,等待t0執行完畢同步代碼塊,釋放鎖對象,t1才可以獲取從而進入同步代碼塊執行。
同步中的線程,沒有執行完畢是不會釋放鎖的,這樣便實現了線程對臨界區的互斥訪問,保證了共享數據安全。
缺點:頻繁的獲取釋放鎖對象,降低程序效率
第二種方法:同步方法
使用步驟:
1、把訪問了共享數據的代碼抽取出來,放到一個方法中
2、在該方法上添加 synchronized 修飾符
格式:
修飾符 synchronized 返回值類型 方法名稱(參數列表) {
method body
}
public class RunableImpl implements Runnable {
private int ticket = 100;
// 創建一個鎖對象,鎖對象應該創建在run方法外,因為不同的線程應該使用同一個鎖對象,如果在run方法里面,就會每個線程自己創建一個鎖對象,也就是不唯一了
Object object = new Object();
@Override
public void run() {
while(true) {
sellTicket();
}
}
public synchronized void sellTicket() {// 同步方法,訪問了共享數據的代碼,可能出現線程安全問題的代碼,放到一個方法中
if(ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is selling ticket number--> " + ticket);
ticket--;
}
}
}
同步方法的也是一樣鎖住同步的代碼,但是鎖對象的是Runable實現類對象,也就是this,誰調用方法,就是誰,在這里就是創建的run對象
靜態的同步方法,添加一個靜態static修飾符,此時鎖對象就不是this了,靜態同步方法的鎖對象是本類的class屬性,class文件對象(反射)
public static synchronized void sellTicket() {
if(ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is selling ticket number--> " + ticket);
ticket--;// 訪問了變量應該定義為static的,因為靜態方法要訪問靜態變量
}
}
}
第三種方法:Lock接口
java.util.concurrent.locks.Lock
Lock接口中的方法:
void lock():獲取鎖
void unlock():釋放鎖
Lock接口的一個實現類
java.util.concurrent.locks.ReentrantLock implements Lock接口
使用方法:
1、在Runable實現類的成員變量創建一個ReentrantLock對象
2、在可能產生線程安全問題的代碼前該對象調用lock方法獲取鎖
3、在可能產生線程安全問題的代碼后該對象調用unlock方法獲取鎖
public class RunableImpl implements Runnable {
private static int ticket = 100;
// 創建一個鎖對象,鎖對象應該創建在run方法外,因為不同的線程應該使用同一個鎖對象,如果在run方法里面,就會每個線程自己創建一個鎖對象,也就是不唯一了
Object object = new Object();
ReentrantLock l = new ReentrantLock();// 1、在Runable實現類的成員變量創建一個ReentrantLock對象
@Override
public void run() {
while(true) {
l.lock();// 2、在可能產生線程安全問題的代碼前該對象調用lock方法獲取鎖
if(ticket > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + " is selling ticket number--> " + ticket);
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// unlock方法放在finally里面,無論程序是否有出現異常,該方法都會執行,也就是都會釋放鎖
l.unlock();// 3、在可能產生線程安全問題的代碼后該對象調用unlock方法獲取鎖
}
}
}
}
}