1. 程式人生 > >java並發編程基礎——線程同步

java並發編程基礎——線程同步

pre static java並發編程 可重入 重入 java並發 更強 所有 避免

線程同步

一、線程安全問題

如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

線程安全問題往往發生在多個線程調用同一方法或者操作同一變量,但是我們要知道其本質就是CPU對線程的隨機調度,CPU無法保證一個線程執行完其邏輯才去調用另一個線程執行。

package threadtest;
 
public class ThreadTest  implements Runnable{
 
    static int i = 0;
    public
void incre() { i++; } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new
Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }

由於 i++不是原子操作,先讀取值,再加1 賦值,所以當在讀取i值的時候線程切換了,導致兩個線程讀取的i相同,導致線程安全問題。

1363390  //結果小於2000000

二、線程同步

java多線程支持引入了同步監視器來解決線程同步問題,通過synchronized關鍵字,主要有同步方法和同步代碼塊

執行同步代碼前必須先獲得對同步監視器的鎖定(任何時刻都只有一個線程可以獲得同步監視器的鎖定)

java5開始提供了更強大的同步機制,同步鎖Lock

1、同步方法: synchronized修飾方法

package threadtest;
 
public class ThreadTest  implements Runnable{
 
    static int i = 0;
    public synchronized void incre() {
        i++;
    }
     
    @Override
    public   void run() {
        for(int j=0;j<1000000;j++) {
            incre();
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        ThreadTest tt = new ThreadTest();
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
         
}

同步方法的同步監視器就是方法所屬的對象本身

2000000 //結果正常

synchronized修飾靜態方法

package threadtest;
 
public class ThreadTest  implements Runnable{
 
    static int i = 0;
    /**
     * 同步靜態方法的同步監視器是該類對應的class對象
     */
    public static synchronized  void incre() {
        i++;
    }
     
    @Override
    public   void run() {
        for(int j=0;j<1000000;j++) {
            incre();
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new ThreadTest());
        Thread t2 = new Thread(new ThreadTest());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
         
}

上面類中synchronized修飾的靜態方法,同步監視器是該類對應的class對象,i是類屬性,多個線程調用不同實例,i也是線程安全的。

2000000

2、同步代碼塊

除了使用關鍵字修飾實例方法和靜態方法外,還可以使用同步代碼塊,在某些情況下,我們編寫的方法體可能比較大,同時存在一些比較耗時的操作,而需要同步的代碼又只有一小部分,如果直接對整個方法進行同步操作,可能會得不償失,此時我們可以使用同步代碼塊的方式對需要同步的代碼進行包裹,這樣就無需對整個方法進行同步操作了

package threadtest;
 
public class ThreadTest  implements Runnable{
 
    static int i = 0;
    /**
     * 同步代碼塊,synchronized(obj),obj就是同步監視器
     */
    public void incre() {
        synchronized(this) {
            i++;
        }
    }
     
    @Override
    public   void run() {
        for(int j=0;j<1000000;j++) {
            incre();
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        ThreadTest tt = new ThreadTest();
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
         
}

同步代碼塊修飾靜態方法

package threadtest;
 
public class ThreadTest  implements Runnable{
 
    static int i = 0;
    /**
     * 同步代碼塊,synchronized(obj),obj就是同步監視器
     */
    public static void incre() {
        synchronized(ThreadTest.class) {
            i++;
        }
    }
     
    @Override
    public   void run() {
        for(int j=0;j<1000000;j++) {
            incre();
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        //ThreadTest tt = new ThreadTest();
        Thread t1 = new Thread(new ThreadTest());
        Thread t2 = new Thread(new ThreadTest());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
         
}

3、同步鎖Lock

java5開始提供了通過顯示定義同步鎖對象來實現同步。

在實現線程安全中,比較常用的是ReentrantLock(可重入鎖),它是Lock接口的實現類。

package threadtest;
 
import java.util.concurrent.locks.ReentrantLock;
 
public class ThreadTest  implements Runnable{
 
    private final ReentrantLock lock = new ReentrantLock();
    static int i = 0;
    public void incre() {
        lock.lock();//加鎖
        try {
            i++;
        } finally {
            lock.unlock();
        }
             
    }
     
    @Override
    public   void run() {
        for(int j=0;j<1000000;j++) {
            incre();
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        ThreadTest tt = new ThreadTest();
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
         
}
2000000//線程安全

4、死鎖

當兩個線程同時等待對方釋放同步監視器就會發生死鎖,java虛擬機沒有檢測,也沒有采取措施來處理死鎖情況,所以多線程編程時應該采取措施避免死鎖出現。

一旦出現死鎖,程序不會發生任何異常情況,也沒有任何提示,只是所有線程處於阻塞狀態,無法繼續

下面程序就是發生死鎖

package threadtest;
/**
 * 一個簡單的死鎖例子,大概的思路:兩個線程A和B,兩把鎖X和Y,現在A先拿到鎖X,然後sleep()一段時間,我們知道sleep()是不會釋放鎖資源的。然後如果這段時間線程B拿到鎖Y,也sleep()一段時間的話,那麽等到兩個線程都醒過來的話,那麽將互相等待對方釋放鎖資源而僵持下去,陷入死鎖。flag的作用就是讓A和B獲得不同的鎖。
 * @author rdb
 *
 */
public class ThreadTest  implements Runnable{
 
    Object o1 = new Object();
    Object o2 = new Object();
    private boolean flag = true ;
     
    @Override
    public   void run() {
        if(flag) {
            flag = false;
            synchronized (o1) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("**************");
                }
            }
        }else {
            flag = true;
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("**************");
                }
            }
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        ThreadTest tt = new ThreadTest();
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        t1.start();
        t2.start();
         
         
    }
         
}

java並發編程基礎——線程同步