java多線程(4)synchronized的做用

在多線程併發的狀況下,有時就涉及到對於同一資源的讀寫,若是不進行一些處理,容易出現數據混亂,結果和實際不一致等問題。java中能夠使用synchronized關鍵字對資源鎖定。java

synchronized的用法

synchronized有2種用法:
1.修飾代碼塊,以某個對象爲鎖,鎖的範圍是指定的代碼塊。
2.修飾方法,其實也能夠等價於修飾代碼塊,好比修飾普通方法:多線程

synchronized void doxx(){
//.........
}

等價於併發

void doxx(){
    synchronized (this){
    //.........
    }
}

以當前實體爲鎖對象,整個方法都是在代碼塊中。也能修飾靜態方法:dom

public class Test{
    static synchronized void doxx(){
    //.........
    }
}

等價於ide

public class Test{
    static void doxx(){
        synchronized (Test.class){
        //.........
        }
    }
}

synchronized 修飾代碼塊

先舉一個反例demo:this

import java.util.List;
import java.util.Random;

public    class Thread1 extends Thread{
    private List list;
    public Thread1(List list) {
        this.list=list;
    }
    @Override
    public void run() {
        try {
            for(int i=0;i<100;i++){
                Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題
                int randon = new Random().nextInt();
                list.add(randon);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public static void main(String[] args) throws InterruptedException {
        List list = new ArrayList<>(1000);
        Thread1 t1 = new Thread1(list);
        t1.start();
        Thread1 t2 = new Thread1(list);
        t2.start();
        t1.join();
        t2.join();
        System.out.println("size="+list.size());
    }

執行結果:線程

size=162

這個結果是不肯定,每次執行均可能不同。demo裏啓動了2個線程,每一個執行100次,按理list的數據量應該會是200個。這個就是本文開始提到的,多個線程讀寫同一個對象時,發生了數據異常。那麼咱們再用synchronized對demo進行小小的改造。code

import java.util.List;
import java.util.Random;

public    class Thread1 extends Thread{
    private List list;
    public Thread1(List list) {
        this.list=list;
    }
    @Override
    public void run() {
        try {
            for(int i=0;i<100;i++){
                Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題
                int randon = new Random().nextInt();
                synchronized (list) {//就只改這個地方
                    list.add(randon);
                }
            
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

main方法保持不變,結果以下:對象

size=200

可見使用synchronized關鍵字後,代碼的執行結果恢復了正常。資源

synchronized修飾方法

import java.util.List;
import java.util.Random;

public    class Thread1 extends Thread{
    private List list;
    public Thread1(List list) {
        this.list=list;
    }
    public synchronized void  run() {
        try {
            for(int i=0;i<100;i++){
                Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題
                int randon = new Random().nextInt();
                list.add(randon);
            
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

main方法不變,執行結果:

size=150

這是不少人開始接觸多線程時,會出現的錯誤,明明對run方法用了synchronized 關鍵字怎麼出來的結果是不對的。根據上面提到的咱們把代碼轉變一下:

import java.util.List;
import java.util.Random;

public    class Thread1 extends Thread{
    private List list;
    public Thread1(List list) {
        this.list=list;
    }
    public  void  run() {
        try {
            synchronized(this){//把synchronized改到這個地方
                for(int i=0;i<100;i++){
                    Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題
                    int randon = new Random().nextInt();
                    list.add(randon);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

synchronized用在this上,因此t1.start()鎖定的是t1,t2.start()鎖定的是t2,2者鎖定的對象不一樣天然就沒有相應的效果。

那是否是synchronized用在方法上就沒有做用呢?固然不會,先看下面的例子:

import java.util.Random;

public    class Thread1 extends Thread{
    private SyncList list;
    public Thread1(SyncList list) {
        this.list=list;
    }
    public  void  run() {
        try {
            for(int i=0;i<100;i++){
                Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題
                int randon = new Random().nextInt();
                list.addList(randon);//注意這裏
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
import java.util.ArrayList;
import java.util.List;

public class SyncList<E>{
    
    private List<E> list;
    
    public SyncList(List list) {
        this.list = list;
    }
    
    public void addList(E obj){
        list.add(obj);
    }
    public List<E> getList() {
        return list;
    }
}
public static void main(String[] args) throws InterruptedException {
        SyncList list = new SyncList(new ArrayList<>(1000));
        Thread1 t1 = new Thread1(list);
        t1.start();
        Thread1 t2 = new Thread1(list);
        t2.start();
        t1.join();
        t2.join();
        System.out.println("size="+list.getList().size());
}

執行結果:

size=161

修改一下SyncList:

import java.util.ArrayList;
import java.util.List;

public class SyncList<E>{
    
    private List<E> list;
    
    public SyncList(List list) {
        this.list = list;
    }
    
    public synchronized void addList(E obj){//僅在這裏加上synchronized 
        list.add(obj);
    }
    public List<E> getList() {
        return list;
    }
}

執行結果:

size=200

這個就是synchronized用在方法上的一個例子,鎖定的對象都是同一個SyncList,因此最終結果是正確的。所以synchronized使用上很重要的一點,是保障多個線程鎖定的對象要一致

異常釋放鎖

public    class Thread1 extends Thread{
    private Object lock;
    public Thread1(Object lock) {
        this.lock=lock;
    }
    public  void  run() {
        try {
            synchronized (lock) {
                for(int i=5;i>-1;i--){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                    Thread.sleep(200);
                    int j= 100/i;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        for(int i=0;i<2;i++){
            Thread1 t1 = new Thread1(lock);
            t1.start();
            t1.join();
            Thread.sleep(10);
        }
}

執行結果:

Thread-0:5
Thread-0:4
Thread-0:3
Thread-0:2
Thread-0:1
Thread-0:0
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
    at Thread1.run(Thread1.java:12)
Thread-1:5
Thread-1:4
Thread-1:3
Thread-1:2
Thread-1:1
Thread-1:0
Exception in thread "Thread-1" java.lang.ArithmeticException: / by zero
    at Thread1.run(Thread1.java:12)

能夠看到因爲Thread-0先獲取鎖,Thread-1一直處於等待狀態,Thread-0一直執行到i=0時,程序發生異常,鎖被釋放。Thread-1就得到了鎖開始執行。

總結

1.synchronized能夠修飾方法或者代碼塊。2.儘可能使用代碼塊以及減小代碼塊的範圍,有利於提升程序運行效率。3.要注意鎖定的對象是否一致。

相關文章
相關標籤/搜索