Java線程安全和對象共享關鍵字

Synchronized關鍵字

靜態的synchronized方法以class對象作爲鎖,又稱Intrinsic Lock或Monitor鎖;

Synchronized (lock) {

//訪問或修改由鎖保護的共享狀態

}

同步代碼塊:對象的Monitor鎖底層有monitorenter和monitorexit指令,monitorenter指令將計數器值加1,monitorexit將計數器值減1;當計數器值爲0時monitor鎖才能由線程獲取,且計數器值只能由持有Monitor鎖的線程修改。並且,無論方法正常結束還是異常終止,monitorexit指令都會執行,以確保線程釋放monitor鎖。

同步方法:使用的是運行時常量池中方法的 ACC_SYNCHRONIZED 標誌,原理與以上類似。

示例代碼:

public class CountSync implements Runnable {
    private int i = 0;

    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
    }

    public synchronized int getI() {
        return i;
    }

    public static void main(String[] args) throws InterruptedException {
        CountSync sync = new CountSync();
        Thread t1 = new Thread(sync);
        Thread t2 = new Thread(sync);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sync.getI());
    }
}

注:synchronized關鍵字用於靜態方法時使用的是類對象的鎖


Volatile變量

變量的更新操作會被及時通知到其他線程,只確保可見性不保證原子性但volatile 關鍵宇能夠保障對 long/double 型變量的寫操作具有原子性。

加入volatile會禁止指令重排,強制對緩存的修改操作立即寫入主內存;其他線程的讀操作直接從主內存讀取最新值。

使用場景:1. 對變量的寫操作不依賴當前值;

                  2. 該變量沒有包含在具有其他變量的不變式中。

示例代碼:

public class VolaThread implements Runnable {
    public static volatile int n = 0;

    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                increment();
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void increment() {
        n++;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new VolaThread());
        Thread t2 = new Thread(new VolaThread());
        Thread t3 = new Thread(new VolaThread());
        Thread t4 = new Thread(new VolaThread());
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t1.join();
        t2.join();
        t3.join();
        t4.join();
        System.out.println("n is :" + n);

    }
}

ThreadLocal類

ThreadLocal用於保存某個線程共享變量:對於同一個static ThreadLocal,不同線程只能從中get,set,remove自己的變量,而不會影響其他線程的變量。使線程中的某個值與保存值的對象關聯起來,ThreadLocal對象通常用於防止對可變的單實例變量或全局變量進行共享

ThreadLocal使用ThreadLocalMap內部靜態類,實際調用ThreadLocalMap的set和get方法設置和獲取值,key爲當前ThreadLocal對象,value爲變量副本。

示例代碼:

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyThreadLocal {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss SSS");
    private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>() {
        @Override
        protected Object initialValue() {
            System.out.println("[" + DATE_FORMAT.format(new Date()) +"] " + Thread.currentThread().getName() + " invokes method initialValue, return Default value");
            return null;
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new MyIntegerTask("IntegerTask1"));
        Thread t2 = new Thread(new MyStringTask("StringTask1"));
        Thread t3 = new Thread(new MyIntegerTask("IntegerTask2"));
        Thread t4 = new Thread(new MyStringTask("StringTask2"));
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        //等待t1,t2,t3,t4死掉
        t1.join();
        t2.join();
        t3.join();
        t4.join();
        System.out.println("******************All done*****************");
        System.out.println("線程" + Thread.currentThread().getName() + " get variable, result " + MyThreadLocal.threadLocal.get());
        MyThreadLocal.threadLocal.remove();
    }

    public static class MyIntegerTask implements Runnable {
        private String name;

        MyIntegerTask(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                if (null == MyThreadLocal.threadLocal.get()) {
                    MyThreadLocal.threadLocal.set(0);
                    System.out.println("[" + DATE_FORMAT.format(new Date()) + "]" + " 線程" + name + ": 0");
                } else {
                    int num = (int) MyThreadLocal.threadLocal.get(); //沒有拋ClassCastException
                    MyThreadLocal.threadLocal.set(num + 1);
                    System.out.println("[" + DATE_FORMAT.format(new Date()) + "]" + " 線程" + name + ":" + MyThreadLocal.threadLocal.get());
                    if (i == 3) {
                        MyThreadLocal.threadLocal.remove();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static class MyStringTask implements Runnable {
        private String name;

        MyStringTask(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                if (null == MyThreadLocal.threadLocal.get()) {
                    MyThreadLocal.threadLocal.set("a");
                    System.out.println("[" + DATE_FORMAT.format(new Date()) + "]" + " 線程" + name + ":a");
                } else {
                    String string = (String) MyThreadLocal.threadLocal.get();
                    MyThreadLocal.threadLocal.set(string + "a");
                    System.out.println("[" + DATE_FORMAT.format(new Date()) + "]" + " 線程" + name + ":" + MyThreadLocal.threadLocal.get());
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

注:1. 使用完線程共享變量後,顯示調用ThreadLocalMap.remove方法清除線程共享變量;

    2. JDK建議ThreadLocal定義爲private static,這樣ThreadLocal弱引用的問題則不存在了


Final域

用於構造不可變對象,能確保初始化過程的安全性

示例代碼:

 

參考:《Java併發編程實戰》、深入理解Java併發之synchronized實現原理正確使用 Volatile 變量ThreadLocal用法詳解和原理

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