Java多線程——安全與死鎖問題

一、實現多線程的方式:
方式一:繼承Thread類
              a.自定義類繼承Thread類
              b.在自定義類中重寫run()方法
              c.創建自定義類的對象
              d.啓動線程的對象
方式二:實現Runnable接口(大多數使用)
              a.自定義類實現Runnable接口
              b.在自定義類中重寫run()方法
              c.創建自定義類的對象
              d.創建Thread類的對象,並把c步驟創建的對象作爲構造參數傳遞
              優點:避免由於單繼承帶來的侷限性
                        適合多個程序的代碼處理同一個資源的情況,把線程同程序的代碼、數據有效分離,較好的體現了面向對象的設計思想。

public class SellTicket implements Runnable {
private int tickets = 100;
public void run() {
while(true) {
if(tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"張票");
  }
}    
}
}  
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
t1.start();
t2.start();
t3.start();
}
}

出現多線程安全問題的原因:
            1.是否是多線程環境
            2.是否共享數據
            3.是否有多條語句操作共享數據
二、解決線程安全問題——
      方式一、同步代碼塊
            同步機制:把多條語句操作共享數據的代碼給包成一個整體,讓某個線程在執行的時候,別人不能來執行
       格式:synchronized(對象) {需要同步的代碼;}
            同步可以解決安全問題的根本原因就在這個對象上。該對象如同鎖的功能。多個線程,需要共同擁有一個對象
            用法
                    在run()方法外創建一個對象 Object obj = new Object(); 對象是任意創建的
                    .......      
                        while(true)  {
                                synchronized(obj)  {      //相當於一把鎖,t1搶到CPU執行權後就鎖上了          
                                        if(tickets > 0)  { 
                    .......      //t1睡眠時t2,t3也會搶CPU執行權,但由於鎖上進不去。當t1執行完後 t1,t2,t3還會再次搶CPU執行權
          同步的特點:多個線程使用的是同一個鎖對象
          同步的弊端:當線程相當多時,因爲每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程序的運行效率。
        同步方法的格式以及鎖對象問題
              直接將synchronized加到方法上
                private synchronized void 方法名() {...}  
                run()方法中直接調用 但是需要注意鎖對象的問題 
        同步方法的鎖對象是this
        靜態方法的鎖對象是類的字節碼文件對象 即當前類名.class (靜態方法是隨着類的加載而加載)
        方式二、Lock鎖的使用(Lock屬於接口)
                1.明確看到在哪加鎖,在哪釋放鎖
                2.方法:void lock();獲取鎖  void unlock();釋放鎖
                3.ReentrantLock是Lock的實現類。
              用法: 
                       在自定義類中定義鎖對象 如:private Lock lock = new ReentrantLock();
                  .......      
                        while(true)  {                      
                                try {                           //加入try是爲了防止裏面的走錯,這樣可以直接釋放
                                       lock.lock();
                                       if...
                                }finally {
                                     lock.unlock();
                                }...
三、死鎖問題
            是指兩個或兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待的現象
            同步弊端:效率低
                             如果出現同步嵌套就容易產生死鎖問題
            出現死鎖問題例子:
public class MyLock {
//定義兩個鎖對象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
public class DieLock extends Thread {
public boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
 
public void run() {
if(flag) {
synchronized(MyLock.objA) {
System.out.rpintln("if objA");
synchronized(MyLock.objB) {
System.out.rpintln("else objA");
}
}
}else {
synchronized(MyLock.objB) {
System.out.rpintln("else objB");
synchronized(MyLock.objA) {
System.out.rpintln("else objA");
    }
}
}
}
}
public class Demo {
public static void main(String[] args) {
DieLock d1 = new DieLock(true);
DieLock d2 = new DieLock(false);
d1.start();
d2.start();
}
}
    有兩把鎖objA、objB,兩個線程d1、d2, d1爲true會進入第一部分代碼塊,d2進入第二部分代碼塊,理想狀態下,d1和d2正常交叉走完代碼塊,但是由於兩個線程搶CPU的執行權時,有可能出現d1走完第一部分的第一個鎖後進入下一個鎖時,d2在第二部分代碼塊還沒有執行完第一個鎖,所以會等待d2完成,然後兩個線程進入不到對方的代碼塊中以至於互相等待,因此出現死鎖現象。
四、線程通信
          不同種類的線程針對同一個資源的操作。
      若想共享數據源,可以在外界把這個數據源對象創建出來,通過構造方法傳遞給其他的類。

持續更新~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章