synchronized,volatile與Atomic簡介

線程安全需要保證幾個基本特性:

  • 原子性:相關操作不會中途被其他線程干擾,一般通過同步實現
  • 可見性:一個線程修改了某個共享變量,其狀態(tài)能夠立即被其它線程知曉
  • 有序性:保證線程內串行語義,避免指令重排等

1. synchronized

1.1 sychronized(class)代碼塊與靜態(tài)同步synchronized方法

兩者鎖定的都是對應的class,在效果上是等價的讥电,sychronized(class)代碼塊的粒度會小一些

public class SynchronizedTask {
    // sychronized(class)代碼塊
    public static void printA() {
        synchronized (SynchronizedTask.class) {
            try{
                System.out.println("線程" + Thread.currentThread().getName() + "開始進入方法printA, 時間:" + System.currentTimeMillis());
                Thread.currentThread().sleep(3000);
                System.out.println("線程" + Thread.currentThread().getName() + "開始退出方法printA, 時間:" + System.currentTimeMillis());
            }catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
    }

    // 靜態(tài)同步synchronized方法
    synchronized public static void printB() {
        try{
            System.out.println("線程" + Thread.currentThread().getName() + "開始進入方法printB, 時間:" + System.currentTimeMillis());
            Thread.currentThread().sleep(3000);
            System.out.println("線程" + Thread.currentThread().getName() + "開始退出方法printB, 時間:" + System.currentTimeMillis());
        }catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
public class ThreadA extends Thread {
    @Override
    public void run() {
        SynchronizedTask.printA();
    }
}
public class ThreadB extends Thread {
    @Override
    public void run() {
        SynchronizedTask.printB();
    }
}
public class SynchronizedMain {
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        threadA.setName("A");
        threadA.start();

        ThreadB threadB = new ThreadB();
        threadB.setName("B");
        threadB.start();

    }
}

由于線程A先持有class鎖,因此線程A執(zhí)行完printA方法后,線程B才獲得class鎖馋贤,執(zhí)行printB方法。
image.png

1.2 synchronized方法和sychronized(this)代碼塊

兩者鎖定的都是當前對象畏陕,在效果上是等價的配乓,sychronized(this)代碼塊的粒度會小一些

package Synchronized.synchronized_current_object;

/**
 * Created by xq on 2018/7/5.
 */
public class SynchronizedTask {
    // sychronized(this)代碼塊
    public void printA() {
        synchronized (this) {
            try{
                System.out.println("線程" + java.lang.Thread.currentThread().getName() + "開始進入方法printA, 時間:" + System.currentTimeMillis());
                java.lang.Thread.currentThread().sleep(3000);
                System.out.println("線程" + java.lang.Thread.currentThread().getName() + "開始退出方法printA, 時間:" + System.currentTimeMillis());
            }catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
    }

    // synchronized方法
    synchronized public void printB() {
        try{
            System.out.println("線程" + java.lang.Thread.currentThread().getName() + "開始進入方法printB, 時間:" + System.currentTimeMillis());
            java.lang.Thread.currentThread().sleep(3000);
            System.out.println("線程" + java.lang.Thread.currentThread().getName() + "開始退出方法printB, 時間:" + System.currentTimeMillis());
        }catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
public class ThreadA extends Thread {
    private SynchronizedTask synchronizedTask;

    public ThreadA(SynchronizedTask synchronizedTask) {
        super();
        this.synchronizedTask = synchronizedTask;
    }

    @Override
    public void run() {
        synchronizedTask.printA();
    }
}
public class ThreadB extends Thread {
    private SynchronizedTask synchronizedTask;

    public ThreadB(SynchronizedTask synchronizedTask) {
        super();
        this.synchronizedTask = synchronizedTask;
    }

    @Override
    public void run() {
        synchronizedTask.printB();
    }
}
public class SynchronizedMain {
    public static void main(String[] args) {
        SynchronizedTask synchronizedTask = new SynchronizedTask();
        ThreadA threadA = new ThreadA(synchronizedTask);
        threadA.setName("A");
        threadA.start();

        ThreadB threadB = new ThreadB(synchronizedTask);
        threadB.setName("B");
        threadB.start();

    }
}

由于線程A先持有當前對象鎖(synchronizedTask),因此線程A執(zhí)行完printA方法后,線程B才獲得對象鎖犹芹,執(zhí)行printB方法崎页。
image.png

1.3 sychronized(非this對象)

java支持對“任意對象”作為對象監(jiān)視器來實現同步的功能。鎖非this對象具有一定的優(yōu)點:如果在一個類中有很多個sychronized方法腰埂。這時不同線程調用這些方法雖然能實現同步实昨,但會受到阻塞,影響運行效率盐固。如果使用同步代碼塊鎖非this對象荒给,不同對象監(jiān)視器所在的方法是異步執(zhí)行的,從而提高運行效率刁卜。

public class SynchronizedTask {
    private Object object1;
    private Object object2;

    public SynchronizedTask(Object object1, Object object2) {
        this.object1 = object1;
        this.object2 = object2;
    }

    // synchronized(object1)代碼塊
    public void printA() {
        synchronized (object1) {
            try{
                System.out.println("線程" + java.lang.Thread.currentThread().getName() + "開始進入方法printA, 時間:" + System.currentTimeMillis());
                java.lang.Thread.currentThread().sleep(3000);
                System.out.println("線程" + java.lang.Thread.currentThread().getName() + "開始退出方法printA, 時間:" + System.currentTimeMillis());
            }catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
    }

    // synchronized(object2)代碼塊
    public void printB() {
        synchronized (object2) {
            try{
                System.out.println("線程" + java.lang.Thread.currentThread().getName() + "開始進入方法printA, 時間:" + System.currentTimeMillis());
                java.lang.Thread.currentThread().sleep(3000);
                System.out.println("線程" + java.lang.Thread.currentThread().getName() + "開始退出方法printA, 時間:" + System.currentTimeMillis());
            }catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
    }
}
public class ThreadA extends Thread {
    private SynchronizedTask synchronizedTask;

    public ThreadA(SynchronizedTask synchronizedTask) {
        super();
        this.synchronizedTask = synchronizedTask;
    }

    @Override
    public void run() {
        synchronizedTask.printA();
    }
}
public class ThreadB extends Thread {
    private SynchronizedTask synchronizedTask;

    public ThreadB(SynchronizedTask synchronizedTask) {
        super();
        this.synchronizedTask = synchronizedTask;
    }

    @Override
    public void run() {
        synchronizedTask.printB();
    }
}
public class SynchronizedMain {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();

        SynchronizedTask synchronizedTask = new SynchronizedTask(object1, object2);
        ThreadA threadA = new ThreadA(synchronizedTask);
        threadA.setName("A");
        threadA.start();

        ThreadB threadB = new ThreadB(synchronizedTask);
        threadB.setName("B");
        threadB.start();

    }
}

線程A持有對象鎖object1志电,開始執(zhí)行printA方法后,線程B持有對象鎖object2蛔趴,執(zhí)行printB方法,兩個線程之間互不影響挑辆。
image.png

1.4 思考

1.4.1 synchronized最終是對對象上鎖

synchronized(class)鎖的是類對象,synchronized(object)鎖的是實例對象

1.4.2 synchronized能同時保證原子性和可見性

在Java內存模型中孝情,synchronized規(guī)定鱼蝉,線程在加鎖時,先清空工作內存→在主內存中拷貝最新變量的副本到工作內存→執(zhí)行完代碼→將更改后的共享變量的值刷新到主內存中→釋放互斥鎖

2. volatile

2.1 保證可見性

volatile可以保證變量在多個線程之間的可見性箫荡,也即每個線程都能夠自動發(fā)現 volatile 變量的最新值魁亦。

2.2 不保證原子性

public class VolatileThread extends Thread {
    volatile public static int count;

    @Override
    public void run() {
        addCount();
    }

    private static void addCount() {
        for(int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }
}
public class VolatileMain {
    public static void main(String[] args) {
        VolatileThread[] threads = new VolatileThread[100];

        for(int i = 0; i < 100; i++) {
            threads[i] = new VolatileThread();
        }

        for(int i = 0; i < 100; i++) {
            threads[i].start();
        }
    }
}

count最后的值不是10000

下面詳細解釋下上例中volatile出現非線程安全的原因:
變量在內存中的工作過程

(1)read和load階段:從主存復制變量到當前線程工作內存
(2)use和assign階段:執(zhí)行代碼,改變共享變量值
(3)store和write階段:用工作內存數據刷新主存對應變量的值

  • volatile的可見性保證羔挡,假如線程1和線程2在進行read和load操作中洁奈,發(fā)現主內存中count的值都是5,那么都會加載這個最新的值绞灼。
  • 線程1對count進行加1操作利术,最后將count=6刷新到主內存
  • 線程2執(zhí)行時,由于已經進行read和load操作低矮,會在5的基礎上進行加1操作印叁,最后將count=6刷新到主內存

2.3 思考

一般在多線程中使用volatile變量,為了安全军掂,對變量的寫入操作不能依賴當前變量的值:如Num++或者Num=Num5這些操作

3. Atomic

synchronized會導致線程的阻塞轮蜕,從而降低性能。針對i++良姆,i=i+100等操作肠虽,使用synchronized未免太重了幔戏,可通過使用原子類Atomic實現非阻塞同步玛追,它可以在沒有鎖的情況下做到線程安全。

public class AtomicLongTask {
    private AtomicLong count = new AtomicLong(0);

    public void addCount(){
        long startTime = System.currentTimeMillis();
        System.out.println(String.format("線程%s執(zhí)行開始時間為%d", Thread.currentThread().getName(), startTime ));

        for(int i = 0; i< 1000000000; i++){
            count.addAndGet(1);
        }

        long endTime = System.currentTimeMillis();
        System.out.println(String.format("線程%s執(zhí)行結束時間為%d", Thread.currentThread().getName(), endTime ));
        System.out.println(String.format("線程%s執(zhí)行時間為%d秒", Thread.currentThread().getName(), (endTime - startTime)/1000 ));
        System.out.println(String.format("線程%s, count: %d秒", Thread.currentThread().getName(), count.get() ));

    }

    public AtomicLong getCount() {
        return count;
    }

    public void setCount(AtomicLong count) {
        this.count = count;
    }
}
public class AtomicLongThread extends Thread{
    AtomicLongTask atomicLongTask;

    public AtomicLongThread(AtomicLongTask atomicLongTask) {
        this.atomicLongTask = atomicLongTask;
    }

    @Override
    public void run() {
        atomicLongTask.addCount();
    }
}
public class AtomicLongMain {

    // A線程和B線程分別執(zhí)行1000000000次++操作, 總耗時為42s左右,使用AtomicLong,性能明顯優(yōu)于Synchronized
    public static void main(String[] args){
        AtomicLongTask atomicLongTask = new AtomicLongTask();

        AtomicLongThread t1 = new AtomicLongThread(atomicLongTask);
        t1.setName("A");
        AtomicLongThread t2 = new AtomicLongThread(atomicLongTask);
        t2.setName("B");

        t1.start();
        t2.start();
    }
}

A線程和B線程分別執(zhí)行1000000000次++操作, 總耗時為42s左右,使用AtomicLong,性能明顯優(yōu)于Synchronized(讀者可自行試驗)
使用AtomicLong,性能明顯優(yōu)于Synchronized
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市痊剖,隨后出現的幾起案子韩玩,更是在濱河造成了極大的恐慌,老刑警劉巖陆馁,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件找颓,死亡現場離奇詭異,居然都是意外死亡叮贩,警方通過查閱死者的電腦和手機击狮,發(fā)現死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來益老,“玉大人彪蓬,你說我怎么就攤上這事∞嗝龋” “怎么了档冬?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長桃纯。 經常有香客問我酷誓,道長,這世上最難降的妖魔是什么态坦? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任盐数,我火速辦了婚禮,結果婚禮上伞梯,老公的妹妹穿的比我還像新娘娘扩。我一直安慰自己,他們只是感情好壮锻,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布琐旁。 她就那樣靜靜地躺著,像睡著了一般猜绣。 火紅的嫁衣襯著肌膚如雪灰殴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天掰邢,我揣著相機與錄音牺陶,去河邊找鬼。 笑死辣之,一個胖子當著我的面吹牛掰伸,可吹牛的內容都是我干的。 我是一名探鬼主播怀估,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼狮鸭,長吁一口氣:“原來是場噩夢啊……” “哼合搅!你這毒婦竟也來了?” 一聲冷哼從身側響起歧蕉,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤灾部,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后惯退,有當地人在樹林里發(fā)現了一具尸體赌髓,經...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年催跪,在試婚紗的時候發(fā)現自己被綠了锁蠕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡懊蒸,死狀恐怖匿沛,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情榛鼎,我是刑警寧澤逃呼,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站者娱,受9級特大地震影響抡笼,放射性物質發(fā)生泄漏。R本人自食惡果不足惜黄鳍,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一推姻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧框沟,春花似錦藏古、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至梅垄,卻和暖如春厂捞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背队丝。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工靡馁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人机久。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓臭墨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親膘盖。 傳聞我的和親對象是個殘疾皇子胧弛,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內容