淺析Java中synchronized與static synchronized


synchronized關鍵字

synchronized是進行同步處理而保證線程安全。在一個方法中,如果是方法內的私有變量,那個這個變量是線程安全的,但是類中的實例變量是可能會出現線程安全問題的,當多個線程對這個實例變量進行修改,就可能會出現結果並不是我們期望的結果。

線程安全問題

下面一段代碼就出現了線程安全問題。
本來當username為a的時候,num應該為100,但是由於設置讓t1休眠了2秒,導致num被刷新成了200,導致最后輸出時a和b的num都是200。

public class Service {
    private int num = 0;
    public void add(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                Thread.sleep(2000);
            } else {
                num = 200;
            }
            System.out.println(username + " " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.add("a");
    }
}

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        this.service = service;
    }

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

public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA t1 = new ThreadA(service);
        ThreadB t2 = new ThreadB(service);
        t1.start();
        t2.start();
    }
}

運行結果:

synchronized鎖

下面給add方法加個synchronized關鍵字。
可以看到輸出正確。
synchronized進行了同步,使得線程按照順序進行訪問,由於線程t1和t2的監視器都是同一個實例,相當於擁有同一個鎖對象,所以可以進行同步訪問。

public class Service {
    private int num = 0;
    public synchronized void add(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                Thread.sleep(2000);
            } else {
                num = 200;
            }
            System.out.println(username + " " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果:

synchronized同步代碼塊

上面的代碼中是使用synchronized將整個方法進行上鎖,只有當一個方法執行完畢后,另一個線程才可以執行這個方法,這樣會導致性能損耗很大。
返回到線程安全問題,目的其實是為了解決對臨界資源訪問的問題,所以其實只需要將臨界資源進行上鎖就可以了,其他部分其實是可以異步進行的。

在下面代碼中,doSomeTask方法里前半部分沒有進行同步,后面使用了同步代碼塊進行加鎖。
從輸出結果可以看到,前面部分A、B兩個線程是異步進行訪問的,后部分是同步進行訪問的。

public class Service {
    public  void doSomeTask(String username) {
        for (int i = 0; i < 5; i++) {
            System.out.println("沒有同步 " + Thread.currentThread().getName() + " " + i);
        }
        System.out.println();
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println("同步了" + Thread.currentThread().getName() + " " + i);
            }
        }
    }
}

public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.doSomeTask("a");
    }
}

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.doSomeTask("b");
    }
}

public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA t1 = new ThreadA(service);
        ThreadB t2 = new ThreadB(service);
        t1.setName("A");
        t2.setName("B");
        t1.start();
        t2.start();
    }
}

運行結果:

所以使用synchronized同步代碼塊可以將需要加鎖的部分進行上鎖就行了,這樣可以提高性能。
可以使用synchronized同步代碼塊加鎖臨界資源,這樣就可以避免出現線程安全問題。
在JDK1.8中的ConcurrentHashMap也使用synchronized鎖,synchronized鎖的性能已經有了很大的提高。

static synchronized

static方法為類方法,那么對static方法加上synchronized鎖后呢?加鎖的究竟是什么呢?
其實這時候監視器上鎖的對象為這個類對象,而不是一個具體的實例對象,就是所有該類的實例訪問這個方法都會進行加鎖。

從下面實例可以看出,雖然是兩個實例四個線程訪問該方法,但是還是進行了同步,因為所有實例訪問的是同一把鎖,也就是Service類的對象鎖,只要是監視器鎖對象是同一個,那么都是會進行上鎖同步的。

public class Service {
    public static synchronized void doSomeTask(String username) {
        for (int i = 0; i < 5; i++) {
            System.out.println("同步了" + Thread.currentThread().getName() + " " + i);
        }
    }
}

public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.doSomeTask("a");
    }
}

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.doSomeTask("b");
    }
}


public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        Service service1 = new Service();
        ThreadA t1 = new ThreadA(service);
        ThreadB t2 = new ThreadB(service);
        ThreadA t3 = new ThreadA(service1);
        ThreadB t4 = new ThreadB(service1);
        t1.setName("A");
        t2.setName("B");
        t3.setName("C");
        t4.setName("D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

運行結果:

使用同步代碼塊也可以實現上述功能。

在同步代碼塊中,將監視的鎖對象設置為Service.class,代表監視的是這個類鎖,所以也對這個類進行加鎖同步。

public class Service {
    public void doSomeTask(String username) {
        synchronized (Service.class) {
            for (int i = 0; i < 5; i++) {
                System.out.println("同步了" + Thread.currentThread().getName() + " " + i);
            }
        }
    }
}

synchronized與static synchronized一起使用

四個線程訪問兩個方法,線程A和C訪問task1,是一個普通的synchronized方法,線程B和D訪問task2,是一個static synchronized方法。

從輸出結果中可以看出,A、C進行了同步,B、D也進行同步,但是B和C在一開始是交替出現輸出,代表B和C其實沒有同步,就證明它們的鎖不是同一把鎖

A和C線程獲取的實例的對象鎖,而B和D線程獲取的是這個類的鎖。

public class Service {
    public synchronized void task1() {
        for (int i = 0; i < 5; i++) {
            System.out.println("task1 " + Thread.currentThread().getName() + " " + i);
        }
    }

    public static synchronized void task2() {
        for (int i = 0; i < 5; i++) {
            System.out.println("task2 " + Thread.currentThread().getName() + " " + i);
        }
    }
}

public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.task1();
    }
}

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.task2();
    }
}

public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA t1 = new ThreadA(service);
        ThreadB t2 = new ThreadB(service);
        ThreadA t3 = new ThreadA(service);
        ThreadB t4 = new ThreadB(service);
        t1.setName("A");
        t2.setName("B");
        t3.setName("C");
        t4.setName("D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

運行結果:

綜上:

  • synchronized同步代碼塊的性能比synchronized同步方法性能好。
  • 線程間的同步主要是看的獲取的是什么鎖,只有需要獲取同一把鎖的線程才會進行同步。
  • static synchronized獲取的是類對象鎖,而普通的synchronized獲取的是實例對象的鎖,所以其實他們不是同一把鎖。
  • static synchronized的類對象鎖可以對所有該類實例進行同步,因為所有該類實例獲取的類鎖都是同一把鎖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM