多線程之 線程安全
一.線程安全出現原因:
原因: 原本不應該拆開的兩個步驟中間,被其他線程插足。
解決方案:(java中的同步機制 [synchronized] 來解決),具體有下面三種
a. 同步代碼塊
b. 同步方法
c. Lock接口
創建:Lock lock = new ReentrantLock();
霸占鎖:lock();
釋放鎖:unlock();
代碼演示線程安全問題:
public class Ticket implements Runnable{ //定義一個成員變量,表示還有多少張票 int num = 100; //要在run方法中定義賣票的任務 @Override public void run() { //因為要一直賣票,所以使用死循環 while (true) { //如果還有票,那麽就賣票 if(num > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "正在賣票:" + num); num--; } } } }
public class Demo01TicketTest { public static void main(String[] args) { //創建三個線程,執行這個賣票任務 Ticket t = new Ticket(); //等同於創建了3個窗口,每個窗口都執行一樣的任務:售票 new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
上面代碼Demo01TicketTest,運行後可在控制臺查看:
1.出現了票 0 ,-1
2.還有售出重復的票
這就是多個線程會出現的線程安全問題,而在現實中這是不允許的
二.解決線程安全的同步代碼塊方法
同步代碼塊: synchronized關鍵字可以用於方法中的某個區塊中,表示支隊這個區塊的資源實行互斥訪問
格式: synchronized( 同步鎖 ){
需要同步操作的代碼
}
同步鎖: 對象的同步鎖知識一個概念,就是個標記,(就好像獅子占地盤,撒了尿這就是我的地盤)
1. 鎖對象,可以是任意類型
2. 多個線程對象,要使用同一把鎖
註意: 一個同步鎖最多被一個線程鎖擁有,誰拿到鎖就可以進入代碼塊,其他的線程只能外面等待,等待同步鎖被上一個線程釋放後,再進行搶占cpu
使用此方法解決,上面多線程售票安全問題
public class Ticket implements Runnable{ int num = 100; //定義一個對象,這個對象表示鎖對象 //鎖對象沒有特殊的含義,只是做一個標記 //多個線程必須使用同一個鎖對象,否則不能保證線程安全。 Object lock = new Object(); @Override public void run() { while (true) { //當線程執行到synchronized這句代碼的時候,會看一下這個上面還有沒有鎖 //如果同步代碼塊上面還有鎖,那麽線程就獲取到這個鎖, 然後進入到同步代碼塊中。 //如果同步代碼塊上面沒有鎖, 那麽這個線程就一直在這個位置等著獲取鎖, 如果鎖一直不來,那麽就一直等著. synchronized (lock) { if(num > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "正在賣票:" + num); num--; } } //當一個線程離開同步代碼塊,那麽他會釋放鎖。 //之後其他線程就可以去競爭這個鎖,誰搶到了,那麽誰就去執行這個同步代碼塊。 } } }
三.解決線程安全的同步方法方法
同步方法: 使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外面等著
格式: public synchronized void method(){
可能會產生線程安全問題的代碼
}
註意: 同步方法中也有同步鎖
對於非static方法,同步鎖就是this,
對於static方法,同步鎖是類名.class (我們使用當前方法所在類的字節碼對象)
使用同步方法代碼解決售票的線程安全問題:
package cn.itcast.demo02; @SuppressWarnings("all") public class Ticket implements Runnable{ int num = 100; @Override public void run() { while (true) { sell(); } } //同步方法,表示對整個方法體都加上了同步代碼塊 public synchronized void sell() { if(num > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "正在賣票:" + num); num--; } } }
四.解決線程安全的Lock接口方法
Lock鎖: lock機制提供了比synchronized代碼塊和synchronized方法更加廣泛的操作
Lock,更加強大,更加體現面向對象
Lock鎖也成為同步鎖,將加鎖和釋放所拆分兩個方法,如下:
public void lock(): 加同步鎖
public void unlock(): 釋放同步鎖
使用該方法解決多線程售票安全問題
public class Ticket4 implements Runnable{ int num = 100; //創建Lock對象 Lock lock = new ReentrantLock(); @Override public void run() { while (true) { //調用lock對象的lock()方法,手動的獲取鎖 lock.lock(); if(num > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "正在賣票:" + num); num--; } //調用lock對象的unlock方法,手動的釋放鎖 lock.unlock(); } } }
多線程之 線程安全