synchronized的用法以及常見的使用錯誤

synchronized的作用是實現線程之間的同步,既能保證可見性,又能保證原子性。

用法也有很多種,如下所示


用一個線程類來演示下這三種:


指定加鎖對象:

package com.bckj.Thread;

/**
 * Created by Admin on 2017/6/23.
 */
public class SynchronizedTest {
    static int x;
    static class AddThread implements Runnable{
        Object o = new Object();
        @Override
        public synchronized void run() {
            for(int i=1;i<=1000;i++){
                synchronized (o){
                    x++;
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Runnable run = new AddThread();
        Thread addThread1 = new Thread(run);
        Thread addThread2 = new Thread(run);
        addThread1.start();
        addThread2.start();
        //讓主線程等待上面兩個線程執行完畢
        addThread1.join();
        addThread2.join();
        System.out.println(x);
    }
}


直接作用於實例方法:

package com.bckj.Thread;

/**
 * Created by Admin on 2017/6/23.
 */
public class SynchronizedTest {
    static int x;
    static class AddThread implements Runnable{
        public synchronized void increase(){
            x++;
        }
        @Override
        public synchronized void run() {
            for(int i=1;i<=1000;i++){
                increase();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Runnable run = new AddThread();
        Thread addThread1 = new Thread(run);
        Thread addThread2 = new Thread(run);
        addThread1.start();
        addThread2.start();
        //讓主線程等待上面兩個線程執行完畢
        addThread1.join();
        addThread2.join();
        System.out.println(x);
    }
}

直接作用於靜態方法:

package com.bckj.Thread;

/**
 * Created by Admin on 2017/6/23.
 */
public class SynchronizedTest {
    static int x;
    static class AddThread implements Runnable{
        public synchronized static void increase(){
            x++;
        }
        @Override
        public synchronized void run() {
            for(int i=1;i<=1000;i++){
                increase();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Runnable run = new AddThread();
        Runnable run2 = new AddThread();
        Thread addThread1 = new Thread(run);
        Thread addThread2 = new Thread(run2);
        addThread1.start();
        addThread2.start();
        //讓主線程等待上面兩個線程執行完畢
        addThread1.join();
        addThread2.join();
        System.out.println(x);
    }
}

因爲要體現靜態方法,所以實例化了兩個不同的AddThread對象。


新手使用synchronized的時候可能會犯下面的錯誤(不久前我也剛犯過)

1、

public class SynchronizedTest {
    static int x;
    static class AddThread implements Runnable{
        public synchronized void increase(){
            x++;
        }
        @Override
        public void run() {
            for(int i=1;i<=1000;i++){
                increase();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Runnable run = new AddThread();
        Runnable run2 = new AddThread();
        Thread addThread1 = new Thread(run);
        Thread addThread2 = new Thread(run2);
        addThread1.start();
        addThread2.start();
        //讓主線程等待上面兩個線程執行完畢
        addThread1.join();
        addThread2.join();
        System.out.println(x);
    }
}


public class SynchronizedTest {
    static int x;
    static class AddThread implements Runnable{
        Object o = new Object();
        @Override
        public void run() {
            for(int i=1;i<=1000;i++){
                synchronized (o){
                    x++;
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread addThread1 = new Thread(new AddThread());
        Thread addThread2 = new Thread(new AddThread());
        addThread1.start();
        addThread2.start();
        //讓主線程等待上面兩個線程執行完畢
        addThread1.join();
        addThread2.join();
        System.out.println(x);
    }
}

這兩種都是對線程對象中的方法或局部變量進行加鎖,但是又實例化了兩個不同的Runnable實例,也就是說這兩個線程使用的是兩把不同的鎖,因此不能保證線程安全,最後x的結果也會<=2000。

2、

第二種錯誤就比較隱晦

public class AddThread implements Runnable{
    static Integer x = 0;
    @Override
    public void run() {
        for(int i=1;i<=1000;i++){
            synchronized (x){
                x++;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Runnable run = new AddThread();
        Thread addThread1 = new Thread(run);
        Thread addThread2 = new Thread(run);
        addThread1.start();
        addThread2.start();
        //讓主線程等待上面兩個線程執行完畢
        addThread1.join();
        addThread2.join();
        System.out.println(x);
    }
}
這個程序看似沒有錯誤,但是執行結果會得到比2000小的數值,主要是由於,Integer是不可變對象,對象一旦創建就不可能被修改。
通過javap反編譯run方法可以看到:


可以看到i++在執行的時候變成了 i=Integer.valueOf(i.intValue()+1);

Integer.valueOf():


他會傾向於返回一個代表指定數值的Integer實例。因此,i++的本質是,創建一個新的Integer對象,並將它的引用賦值給i。

這樣的話,就可以知道了,因爲i這個對象可能一直在變,所以鎖的對象一直在變,因此不能起到線程同步的作用,只要改成上面提到第一種用法的例子那樣就可以解決。









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