Java多線程學(xué)習(xí)二 synchronized

前面講過昼弟,線程共享變量是非線程安全的属瓣,synchronized關(guān)鍵字可使方法變?yōu)榫€程安全的方法

一员舵、線程安全問題

    private CountNum countNum;

    public MyThread(CountNum countNum) {
        this.countNum = countNum;
    }

    @Override
    public void run() {
        countNum.add("a");
    }
public class MyThread2 extends Thread {
    private CountNum countNum;

    public MyThread2(CountNum countNum) {
        this.countNum = countNum;
    }

    @Override
    public void run() {
       countNum.add("b");
    }
}

public class TestMain {
    public static void main(String[] args)  {
        CountNum countNum=new CountNum();
        MyThread myThread=new MyThread(countNum);
        MyThread2 myThread2=new MyThread2(countNum);
        myThread.start();
        myThread2.start();
    }
}

輸出

a set over
b set over
name:b   num:200
name:a   num:200

兩個(gè)線程同時(shí)訪問一個(gè)沒有同步的方法扯躺,a修改完num的值睡眠時(shí)捉兴,b這時(shí)又修改了num的值,a打印時(shí)的num值其實(shí)已經(jīng)被b給修改了录语,這就是共享變量被多線程同事訪問時(shí)候的問題倍啥。

二、synchronized

解決上述問題很簡單澎埠,只需要在方法前加上synchronized關(guān)鍵字即可虽缕,即使a睡眠了,b也不會進(jìn)入方法執(zhí)行蒲稳,此時(shí)方法是同步順序執(zhí)行的氮趋,輸出如下

a set over
name:a   num:100
b set over
name:b   num:200

三、多個(gè)對象多個(gè)鎖

把TestMain代碼改成如下

public class TestMain {
    public static void main(String[] args)  {
        CountNum countNum=new CountNum();
        CountNum countNum2=new CountNum();
        MyThread myThread=new MyThread(countNum);
        MyThread2 myThread2=new MyThread2(countNum2);
        myThread.start();
        myThread2.start();
    }
}
a set over
b set over
name:b   num:200
name:a   num:100

每個(gè)線程用不同的對象江耀,此時(shí)代碼又變?yōu)楫惒綀?zhí)行的剩胁,因?yàn)榇藭r(shí)synchronized的鎖是不同的對象。synchronized關(guān)鍵字取得的鎖都是對象鎖祥国。
注意:
A線程獲得某對象的鎖時(shí)昵观,其他線程可以以異步的方式調(diào)用該對象非synchronized類型的方法,但是其他線程調(diào)用其他synchronized的方法時(shí)需要等待

四舌稀、臟讀

public class PublicVar {
    private String name="b";
    private String passwd="bb";

    public synchronized void setValue(String name,String passwd){
        try {
            this.name=name;
            Thread.sleep(5000);
            this.passwd=passwd;
            System.out.println("setName "+Thread.currentThread().getName()+" name="+name+" passwd="+passwd);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String getValue() {
        System.out.println("getName "+Thread.currentThread().getName()+" name="+name+" passwd="+passwd);
        return name;
    }
}

public class MyThread extends Thread {

    private PublicVar publicVar;

    public MyThread(PublicVar publicVar) {
        this.publicVar = publicVar;
    }

    @Override
    public void run() {
        publicVar.setValue("a", "aa");
    }
}

    public static void main(String[] args) throws InterruptedException {
        PublicVar publicVar=new PublicVar();
        MyThread myThread=new MyThread(publicVar);
        myThread.start();
        Thread.sleep(2000);
        publicVar.getValue();
    }
getName main name=a passwd=bb
setName Thread-0 name=a passwd=aa

此處出現(xiàn)臟讀的原因是getValue方法并不是同步的啊犬,主線程啟動完thread線程后休眠了2秒,thread線程在設(shè)置完name的值后休眠了5秒扩借,主線程醒來后繼續(xù)執(zhí)行g(shù)etValue方法椒惨,在休眠的兩秒時(shí)間里,name已經(jīng)被thread線程設(shè)置新值潮罪,但passwd還沒有康谆,所以出現(xiàn)了這種情況。
解決此問題的方法當(dāng)然是給getValue方法也加上同步synchronized關(guān)鍵字

總結(jié):
當(dāng)A線程調(diào)用anyObject對象加入synchronized關(guān)鍵字的X方法時(shí)嫉到,A線程就獲得了X方法鎖沃暗,更準(zhǔn)確的將,是獲得了對象的鎖何恶,所以其他線程必須等A線程執(zhí)行完畢才可以調(diào)用X方法孽锥,但其他線程可以隨意調(diào)用其他非synchronized的方法,而其他線程調(diào)用聲明synchronized關(guān)鍵字的非X方法時(shí),也必須等A線程將X方法執(zhí)行完惜辑,也就是釋放對象鎖后才可以調(diào)用唬涧。

五、synchronized鎖重入

可重入鎖:比如A線程獲得了某對象的鎖盛撑,此時(shí)這個(gè)對象鎖還沒有釋放碎节,當(dāng)其再次想獲取這個(gè)對象的鎖時(shí)還說可以獲取的,一個(gè)synchronized方法/塊的內(nèi)部調(diào)用本類或父類的其他synchronized方法/塊時(shí)抵卫,是永遠(yuǎn)可以得到鎖的狮荔。如果不可鎖重入的話,就會造成死鎖介粘。
當(dāng)存在父子類繼承關(guān)系時(shí)殖氏,子類是完全可以通過“可重入鎖”調(diào)用父類的同步方法的。
注意:

重寫父類的synchronized方法不具有同步效果姻采,如需同步仍要手動加上synchronized關(guān)鍵字

六雅采、死鎖

public class DealThread implements Runnable {

    public String name;
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        if ("a".equals(name)) {
            synchronized (lock1) {
                try {
                    System.out.println("name=" + name);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("按lock1->lock2代碼順序執(zhí)行了");
                }
            }
        }
        if ("b".equals(name)) {
            synchronized (lock2) {
                try {
                    System.out.println("name=" + name);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("按lock2->lock1代碼順序執(zhí)行了");
                }
            }
        }
    }
}

    public static void main(String[] args) throws InterruptedException {
        DealThread dealThread=new DealThread();
        dealThread.setName("a");
        Thread thread=new Thread(dealThread);
        thread.start();
        Thread.sleep(1000);
        dealThread.setName("b");
        Thread thread2=new Thread(dealThread);
        thread2.start();
    }

查看死鎖信息,進(jìn)入jdk的bin目錄執(zhí)行jps命令偎谁,輸出如下总滩,發(fā)現(xiàn)TestMain線程的id為8196

3904 Jps
10628 Launcher
8196 TestMain
9892 RemoteMavenServer
11432

使用jstack命令查看結(jié)果jstack -l 8196

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000002f1c978 (object 0x00000000d59a1d30, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000002f188d8 (object 0x00000000d59a1d40, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.yjj.chapter02.test04_syn4.DealThread.run(DealThread.java:43)
        - waiting to lock <0x00000000d59a1d30> (a java.lang.Object)
        - locked <0x00000000d59a1d40> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at com.yjj.chapter02.test04_syn4.DealThread.run(DealThread.java:30)
        - waiting to lock <0x00000000d59a1d40> (a java.lang.Object)
        - locked <0x00000000d59a1d30> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

七、鎖對象改變

public class MyService {
    private String lock="123";

    public void testMethod(){
        synchronized (lock){
            try {
                System.out.println(Thread.currentThread().getName()+" begin "+System.currentTimeMillis());
                lock="456";
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+" end "+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadA extends Thread{
    private MyService myService;

    public ThreadA(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.testMethod();
    }
}

ThreadA同ThreadB

public class TestMain {
    public static void main(String[] args) throws InterruptedException {
        MyService service=new MyService();
        ThreadA a =new ThreadA(service);
        a.setName("A");
        ThreadB b =new ThreadB(service);
        b.setName("B");
        a.start();
        Thread.sleep(50);
        b.start();
    }
}

可以看到A線程和B線程是異步執(zhí)行的巡雨,雖然加了synchronized關(guān)鍵字闰渔,但是由于A線程競爭的是鎖是“123”,得到鎖后把鎖改為了“456”铐望,而B線程競爭的是“456”冈涧,此時(shí)并沒有線程占用“456”的鎖,于是B線程也得到了鎖正蛙,就出現(xiàn)了如下的情況督弓。

A begin 1537950658147
B begin 1537950658196
A end 1537950660147
B end 1537950660196

要注意的是:只要對象不變,即使對象的屬性被改變乒验,運(yùn)行結(jié)果仍是同步的愚隧,也就是說把一個(gè)User對象當(dāng)成鎖,如果user的name屬性發(fā)生變化锻全,但是仍然是同一個(gè)user的話狂塘,仍會同步執(zhí)行

八、volatile關(guān)鍵字

關(guān)于volatile以后寫一篇專門的文章吧鳄厌,現(xiàn)在只需要知道 volatile只保證可見性荞胡、有序性,并不保證原子性了嚎,i++并不是一個(gè)原子操作

九泪漂、原子類用不好也不完全安全

public class MyService {
    public static AtomicLong atomicLong=new AtomicLong();

    public void addNum(){
        System.out.println(Thread.currentThread().getName()+"加了100后值="+atomicLong.addAndGet(100));
        atomicLong.addAndGet(1);
    }
}
    @Override
    public void run() {
        myService.addNum();
    }
    public static void main(String[] args) {
        MyService myService=new MyService();
        AddCountThread addCountThread=new AddCountThread(myService);
        Thread t1=new Thread(addCountThread);
        Thread t2=new Thread(addCountThread);
        Thread t3=new Thread(addCountThread);
        Thread t4=new Thread(addCountThread);
        Thread t5=new Thread(addCountThread);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
Thread-1加了100后值=100
Thread-3加了100后值=300
Thread-2加了100后值=200
Thread-4加了100后值=403
Thread-5加了100后值=503

輸出結(jié)果如上廊营,發(fā)現(xiàn)并不是我們想要的結(jié)果,造成這個(gè)的原因是addAndGet()方法調(diào)用不是原子的萝勤,雖然這個(gè)方法是一個(gè)原子方法露筒,但是兩個(gè)addAndGet()方法之間的調(diào)用卻不是原子的,解決這一的問題必須要用同步敌卓,在addNum方法上synchronized或者用同步代碼塊包括在兩個(gè)addAndGet()方法外

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邀窃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子假哎,更是在濱河造成了極大的恐慌,老刑警劉巖鞍历,帶你破解...
    沈念sama閱讀 210,835評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舵抹,死亡現(xiàn)場離奇詭異,居然都是意外死亡劣砍,警方通過查閱死者的電腦和手機(jī)惧蛹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刑枝,“玉大人香嗓,你說我怎么就攤上這事∽俺” “怎么了靠娱?”我有些...
    開封第一講書人閱讀 156,481評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掠兄。 經(jīng)常有香客問我像云,道長,這世上最難降的妖魔是什么蚂夕? 我笑而不...
    開封第一講書人閱讀 56,303評論 1 282
  • 正文 為了忘掉前任迅诬,我火速辦了婚禮,結(jié)果婚禮上婿牍,老公的妹妹穿的比我還像新娘侈贷。我一直安慰自己,他們只是感情好等脂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,375評論 5 384
  • 文/花漫 我一把揭開白布俏蛮。 她就那樣靜靜地躺著,像睡著了一般慎菲。 火紅的嫁衣襯著肌膚如雪嫁蛇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,729評論 1 289
  • 那天露该,我揣著相機(jī)與錄音睬棚,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抑党,可吹牛的內(nèi)容都是我干的包警。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼底靠,長吁一口氣:“原來是場噩夢啊……” “哼害晦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起暑中,我...
    開封第一講書人閱讀 37,633評論 0 266
  • 序言:老撾萬榮一對情侶失蹤壹瘟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鳄逾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稻轨,經(jīng)...
    沈念sama閱讀 44,088評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,443評論 2 326
  • 正文 我和宋清朗相戀三年雕凹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了殴俱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,563評論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枚抵,死狀恐怖线欲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汽摹,我是刑警寧澤李丰,帶...
    沈念sama閱讀 34,251評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站逼泣,受9級特大地震影響嫌套,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜圾旨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,827評論 3 312
  • 文/蒙蒙 一踱讨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砍的,春花似錦痹筛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至床佳,卻和暖如春滋早,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砌们。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評論 1 264
  • 我被黑心中介騙來泰國打工杆麸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搁进,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,240評論 2 360
  • 正文 我出身青樓昔头,卻偏偏與公主長得像饼问,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子揭斧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,435評論 2 348

推薦閱讀更多精彩內(nèi)容