Java線程---鎖機制:synchronized、Lock、Condition

1.synchronized—對象加鎖

synchronized方法包括兩種,一是標註了synchronized關鍵字的方法,一種是synchronized代碼塊.而不論是同步代碼塊還是同步方法都具有了原子性和可見性.

1.1 原子性

原子性指的是一個時刻,只能有一個線程執行一段同步代碼或一個同步方法,這個同步代碼段或這個同步方法會通過一個monitor object保護.
作用:防止多個線程在更新共享時相互衝突.
原理:對象監視器(鎖),只有獲取到監視器的線程才能繼續執行,否則線程會等待獲取監視器,java中每個對象或者類都有一把鎖與之對應,對於對象來說,監視的是這個對象的實例變量,對於類來說,監視的是類變量(一個類本身是類Class的對象,所以與類關聯的鎖也是對象鎖),當線程執行到同步代碼塊或同步方法時,JVM會鎖住這個引用對象,當離開時纔會釋放這個引用對象上的鎖.對象鎖是JVM內部機制,鎖的獲取黑盒釋放是JVM完成的.

1.2 可見性 

可見性會消除內存緩存和編譯器優化的各種反常行爲,也就是說它必須保證釋放鎖之前對共享數據做出的更改對於隨後另一個獲得此鎖的線程是可見的.
作用:如果沒有同步機制提供的這種可見性保證,線程看到的共享變量可能是另一個線程修改前的值或不一致的值.
原理:當對象獲取鎖時,它首先使自己的高速緩存無效,這樣就可以保證直接從主存中讀取變量的值,同樣,在對象釋放鎖之前,它會刷新告訴緩存,強制使做的任何修改都出現在主存中,這樣,就能保證在同一個鎖上同步的兩個線程在synchronized塊修改的變量的相同值.

1.3爲什麼要使用同步

  • 讀取某個變量的值可能已經被另一個線程修改過.
  • 寫入的某個變量的值可能馬上被另一個線程讀取.

1.4synchronized的限制

synchronized雖然使用簡單,但是它的功能有限制.
* 它無法中斷一個正在等待獲鎖的線程.
* 也無法通過投票得到鎖,如果不想等下去,也就沒法得到鎖;
* 同步還要求鎖的釋放只能在與獲得鎖所在的堆棧幀相同的堆棧幀中進行,多數情況下,這沒問題(而且與異常處理交互得很好),但是,確實存在一些非塊結構的鎖定更合適的情況。

1.5synchronized例子 

用synchronized實現1-9的打印,要求,線程A打印123,然後線程B打印456,然後線程A打印789. 具體代碼如下:

package thread;

/**
 * Created by yang on 16-7-10.
 */

public class WaitNotifyDemo {
    private volatile int val = 1;

    private synchronized void printAndIncrease() {
        System.out.println(Thread.currentThread().getName() + " prints " + val);
        val++;
    }

    // print 1,2,3 7,8,9
    public class PrinterA implements Runnable {
        @Override
        public void run() {
            while (val <= 3) {
                printAndIncrease();
            }

            // print 1,2,3 then notify printerB
            //WaitNotifyDemo.this代表的是WaitNotifyDemo實例
            synchronized (WaitNotifyDemo.this) {
                System.out.println("PrinterA printed 1,2,3; notify PrinterB");
              //  System.out.println("yang"+WaitNotifyDemo.this);
                WaitNotifyDemo.this.notify();
            }

            try {
                while (val <= 6) {
                    synchronized (WaitNotifyDemo.this) {
                        System.out.println("wait in printerA");
                        WaitNotifyDemo.this.wait();
                    }
                }
                System.out.println("wait end printerA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            while (val <= 9) {
                printAndIncrease();
            }
            System.out.println("PrinterA exits");
        }
    }
    // print 4,5,6 after printA print 1,2,3
    public class PrinterB implements Runnable {

        @Override
        public void run() {
            while (val < 3) {
                synchronized (WaitNotifyDemo.this) {
                    try {
                        System.out.println("printerB wait for printerA printed 1,2,3");
                        WaitNotifyDemo.this.wait();
                        System.out
                                .println("printerB waited for printerA printed 1,2,3");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            while (val <= 6) {
                printAndIncrease();
            }

            System.out.println("notify in printerB");
            synchronized (WaitNotifyDemo.this) {
                WaitNotifyDemo.this.notify();
            }
            System.out.println("notify end printerB");
            System.out.println("PrinterB exits.");
        }
    }
    public static void main(String[] args) {
        WaitNotifyDemo demo = new WaitNotifyDemo();
        demo.doPrint();
    }

    private void doPrint() {
        PrinterA pa = new PrinterA();
        PrinterB pb = new PrinterB();
        Thread a = new Thread(pa);
        a.setName("printerA");
        Thread b = new Thread(pb);
        b.setName("printerB");
        // 必須讓b線程先執行,否則b線程有可能得不到鎖,執行不了wait,而a線程一直持有鎖,會先notify;
        b.start();
        a.start();

    }
}

執行過程如下:
線程b首先執行,得到WaitNotifyDemo類對象的鎖,由於此時val<3,線程B執行wait(),釋放了鎖,線程A獲得鎖執行,輸出123,線程A通知線程B,線程A執行wait(),釋放鎖.線程B獲得鎖,輸出456,通知線程A,然後線程B退出,釋放鎖,線程A獲得鎖,輸出789,最後退出.

2.ReentrantLock

java.util.concurrent.lock 中的 Lock 框架是鎖定的一個抽象,它允許把鎖定的實現作爲 Java 類,而不是作爲語言的特性來實現。這就爲 Lock 的多種實現留下了空間,各種實現可能有不同的調度算法、性能特性或者鎖定語義。 ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的併發性和內存語義,但是添加了類似輪詢鎖、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。(換句話說,當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上.)

3.線程件通信Condition

Condition可以替代傳統的線程間通信,用await()替換wait(),用signal()替換notify(),用signalAll()替換notify().
傳統線程的通信方式,Condition都可以實現.Condition是被綁定到Lock上的,要創建一個Lock的Condition必須用newCondition()方法.
Condition的強大之處在於它可以爲多個線程間建立不同的Condition

3.1用ReentrantLock和Condition實現1-9輸出(要求如上) 

代碼如下:

package thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by yang on 16-7-10.
 */
public class APP{
    static class NumberWrapper{
        public int value=1;
    }

    public static void main(String[] args) {
        //初始化可重入鎖
        final Lock lock=new ReentrantLock();
        //第一條件當屏幕上輸出到3
        final Condition reachThreeCondition=lock.newCondition();
        //第二個條件當屏幕上輸出到6
        final Condition reachSixCondition=lock.newCondition();
        //NumberWrapper只是封裝了一個數字,一邊可以將數字對象共享,並可以設置程final
        final NumberWrapper num=new NumberWrapper();
        //初始化A線程
        Thread threadA=new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try{
                    System.out.print("ThreadA start write:");
                    //A線程先輸出3個數
                    while(num.value<=3){
                        System.out.print(num.value);
                        num.value++;
                    }
                    System.out.println();
                    reachThreeCondition.signal();
                }finally {
                    lock.unlock();
                }
                lock.lock();
                try{
                    //等待輸出條件6的條件
                    reachSixCondition.await(); //會釋放鎖.
                    System.out.print("ThreadA start write:");
                    //輸出剩餘數字
                    while(num.value<=9){
                        System.out.print(num.value);
                        num.value++;
                    }
                    System.out.println();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        Thread threadB=new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    lock.lock();
                    while(num.value<=3){
                        reachThreeCondition.await();
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
                try{
                    lock.lock();
                    System.out.print("threadB start write:");
                    while(num.value<=6){
                        System.out.print(num.value);
                        num.value++;
                    }
                    System.out.println();
                    reachSixCondition.signal();
                }finally {
                    lock.unlock();
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

執行過程如下:
線程A首先加鎖lock(),輸出123,然後通知線程B num.value已經到達3了,線程A釋放鎖,然後加鎖,等待輸出條件6的條件,釋放鎖,線程B獲得鎖,輸出456,然後通知線程Anum.value已經到達6了,線程B釋放鎖,退出,線程A獲得鎖,輸出789,釋放鎖,退出.

4.Synchronized和Lock的區別 

  • ReentrantLock擁有Synchronized相同的併發性和內存語義,此外還多了鎖投票,定時鎖等候和中斷鎖等候.
  • 線程A和B都要獲取對象O的鎖定,假設A獲取了對象O鎖,B將等待A釋放對O的鎖定
  • 如果使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間後,中斷等待,而幹別的事,

4.1 ReentrantLock獲取鎖定與三種方式:

  • Lock():如果獲取了鎖立即返回,如果別的線程持有鎖,當前線程則一直處於休眠狀態,直到獲取鎖.
  • tryLock():如果獲取了鎖立即返回true,如果別的線程正持有鎖,立即返回false;
  • tryLock(long timeout,TimeUnit unit),如果獲取了鎖立即返回true,如果別的線程正持有鎖,會等待參數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false.
  • lockInterruptibly:如果獲取了鎖立即返回,如果沒有獲取鎖,當前線程處於休眠狀態,直到獲得鎖或者當前線程被別的線程中斷.

4.2ReentranLock和Synchronized應用場景  

在併發量比較小的情況下,使用synchronized.
在併發量比較高的情況下,其性能下降嚴重,使用ReentrantLock.

發佈了109 篇原創文章 · 獲贊 238 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章