實現線程同步的所有方法分析

一、介紹

當我們寫併發編程時,多個線程可同時訪問一個共享資源,比如變量或對象,如果多個線程同時讀寫該資源,會導致該資源狀態混亂,數據不準確,相互之間產生衝突。

因此加入同步鎖,使資源同一時間只能有一個線程訪問,從而保證資源不發生衝突。

二、線程同步具體實現

1、Synchronized

使用Synchronized關鍵字,分爲同步方法和同步代碼塊,具體關於Synchronized的介紹和使用請移步《Synchronized簡單介紹(原理、使用、對比分析)

2、volatile

volatile的中文意思是不穩定的、反覆無常的、異變的。

a、volatile是輕量級同步機制,在訪問volatile修飾的變量時,不會執行加鎖操作,所以也就不能使線程阻塞,是一種比synchronized關鍵字更輕量級的同步機制。

b、volatile只能保證可見性,不能保證原子性;加鎖機制即可以保證可見性又可以保證原子性,所以votatile並不能替代synchronized。

volatile的中文意思是不穩定的,反覆無常的,異變的。當使用volatile聲明變量後,系統總是重新從它所在的內存中讀取數據,而非從緩存中讀取,從而保證了數據在內存中的可見性,這就保證了同步。(通過這裏也可以看出,volatile修飾的變量都是可變的,不能修飾final類型的變量)

c、volatile會禁止指令重排,屏蔽代碼優化,進而造成效率降低,所以只有在必要時纔可用該關鍵字。

3、使用原子變量

原子變量保證了原子操作(所謂原子操作,就是數據的讀取、修改、保存作爲一個整體行爲,這個整體行爲要麼都執行,要麼都不執行,是不可被分割、不可中斷的,不能僅僅執行其中的一部分動作)

需要線程同步的根本原因就是因爲普通變量的操作不是原子的,而原子變量保證了原子性,所以能保證同步。

在Java的util.concurrent.atomic包中提供了創建原子類型變量的工具類,使用這些原子類可以簡化線程同步。

常用的原子變量類型有:

基本類型對應:AtomicBoolean、AtomicInteger、AtomicLong

數組類型對應:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

引用類型對應:AtomicReference

4、使用ThreadLocal類

ThreadLocal通過字面直接翻譯,就是線程本地 ,也就是線程本地變量、線程局部變量。

ThreadLocal的作用域僅僅是本線程內,即ThreadLocal爲每個線程提供了一個特定的變量,以保存該線程所獨享的數據,所以其提供了一種隔離線程,防止線程間數據共享的方法。

詳細介紹請移步《ThreadLocal解析》。

5、使用阻塞隊列

阻塞隊列(BlockingQueue)和普通隊列的區別就是阻塞,所謂阻塞,即當隊列是空時,從隊列中取數據(take())的操作會被阻塞,或當隊列已經滿時,向隊列存數據(put(E, e))的操作將會被阻塞。當存取數據隊列被阻塞時,即表示當前線程阻塞。

阻塞隊列的常見實現類如ArrayBlockingQueue,LinkedBlockingQueeu,PriorityBlockingQueue等。

阻塞隊列的使用場景典型的是生產者消費者模式,生產者線程向阻塞隊列中存放數據,消費者線程從該阻塞隊列中取出數據。

public class MainActivity extends AppCompatActivity {
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);
    Button mBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBtn = (Button)  findViewById(R.id.mBtn);
        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Producer(queue).start();
                new Consumer(queue).start();
            }
        });
    }

    //生產者
    class Producer extends Thread {
        ArrayBlockingQueue<String> mQueue;
        public Producer(ArrayBlockingQueue<String> queue) {
            mQueue = queue;
        }

        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(50);
                    mQueue.put("Production" + i);
                    System.out.println("Producer:Production" + i );
                } catch (Exception e) {
                    e.getMessage();
                }
            }
        }
    }

    //消費者
    class Consumer extends Thread {
        ArrayBlockingQueue<String> mQueue;

        public Consumer(ArrayBlockingQueue<String> queue) {
            mQueue = queue;
        }

        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(50);
                    String str = mQueue.take();
                    System.out.println("Consumer: " + str );
                } catch (Exception e) {
                    e.getMessage();
                }
            }
        }
    }
}

執行結果如下:

6、使用Lock

主要就是使用重入鎖ReentrantLock類 ,其實現了Lock接口。

常用的方法:

創建ReentrantLock()實例:ReentrantLock lock = new ReentrantLock();

獲得鎖:lock.lock();

釋放鎖:lock.unlock();

代碼 :

//創建實例
ReentrantLock lock = new  ReentrantLock();
lock.lock();
try{
    //doSomething
}finally{
    lock.unlock();
}

通過代碼可以看到,使用ReentrantLock,必須自己實現獲得/釋放鎖,從而帶來了風險,顯然不如直接使用synchronized.

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