[java多線程]的同步問題

指的是多個線程同時修改一個過程時,可能導致的問題

引入:

以賣票問題爲例:假設總共有十張票,三個黃牛賣票(代表三個子線程),預測最後一個黃牛賣完票,還剩下0張票

package se.SE.practice;
class MyThread implements Runnable {
    private int ticket=10;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (this.ticket > 0) {
                try {
                    // 模擬網絡延遲
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "還剩下:" + this.ticket-- + "票");
            }
        }
    }
}
public class Thread1{
    public static void main(String[] args) {
        MyThread mt=new MyThread();
        Thread thread1=new Thread(mt,"黃牛1");
        Thread thread2=new Thread(mt,"黃牛2");
        Thread thread3=new Thread(mt,"黃牛3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

執行結果:

我們發現最後一個黃牛賣票還剩下1張票,和預期不符

爲什麼會出現這個問題呢?是因爲在多線程併發執行的時候,發生了同步的問題,也就是多個線程同時訪問同一塊資源(ticket) 

解決思路:

在黃牛線程訪問ticket時,不允許有其他線程訪問ticket

引入:synchronized關鍵字

Object someObject =new Object();
synchronized (someObject){
  //此處的代碼只有佔有了someObject後纔可以執行
}

賣完票才解鎖:在for循環後加synchronized(this){}

也就是在任何一個時刻內只有一個線程在賣票,

三個線程(黃牛1,黃牛2,黃牛3):可以同時進入for循環和run()但是不可同時進入synchroniazed鎖住的代碼塊

 public void run() {
        for (int i = 0; i < 100; i++) {

            synchronized (this) {
//---------------------------
                if (this.ticket > 0) {
                    try {
                        // 模擬網絡延遲
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "還剩下:" + this.ticket-- + "票");
                }
            }
//-------------------------------------------------
        }
    }

1同步問題:

a.同步代碼塊

在方法中使用synchronized對象,一般可以鎖定當前this

表示同一時刻只有一個線程可以進入同步代碼塊,但是多個線程可以同時進入方法

b.同步方法

在方法聲明中加入synchronized,表示此時只有一個線程可以進入方法

2synchronized對象鎖

觀察以下代碼:

1定義一個synchronized方法,鎖住當前對象this

2第一次new了一個Syn對象syn1,記線程0,那麼線程0

線程1啓動創建Syn對象syn2,各自鎖各自的對象

package se.SE.practice;
class Syn{
    public synchronized void test(int ticket){
        System.out.println(Thread.currentThread().getName()+"此方法開始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"此方法結束");
    }
}
class MyThread implements Runnable {
    private int ticket = 10;
    @Override
    public void run() {
        Syn syn=new Syn();
        syn.test(ticket);
    }
}
public class Thread1{
    public static void main(String[] args) {
                   MyThread myThread=new MyThread();
            Thread thread0=new Thread(myThread,"子線程0");
            Thread thread1=new Thread(myThread,"子線程1");
            Thread thread2=new Thread(myThread,"子線程2");
            thread0.start();
            thread1.start();
            thread2.start();
/*for(int i=0;i<3;i++){
 new Thread(myThread,"線程"+i).start();
        }
*/
    }
}

結果: 

 

總結:用synchronized(this)以及synchronized()只能防止多個線程執行同一個對象的同步段

synchronized鎖的是括號中的對象而非代碼

那麼怎樣纔可以使得可以鎖住一個對象呢,關鍵點在MyThread類實現Runnable 接口中只有一個對象syn

1定義Syn類對象syn

2通過有參構造函數實現對MyThread類對象個數的限制

package se.SE.practice;
class Syn{
    public synchronized void test(int ticket){
        System.out.println(Thread.currentThread().getName()+"此方法開始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"此方法結束");
    }
}
class MyThread implements Runnable {
    private int ticket = 10;
    private Syn syn;
    public MyThread(Syn syn){
        this.syn=syn;
    }
    @Override
    public void run() {
        this.syn.test(ticket);
    }
}
public class Thread1{
    public static void main(String[] args) {
        Syn syn=new Syn();
        MyThread myThread=new MyThread(syn);
        for(int i=0;i<3;i++){
        new Thread(myThread,"線程"+i).start();
        }
    }
}

輸出結果:

達到預期結果!

引入全局鎖,從sychronized(this)或者synchronized方法只可以鎖住當前對象,鎖住代碼段需要全局鎖

3全局鎖

鎖代碼段

1使用類的靜態方法,此時鎖住的是當前使用的類,而非對象

也就是把public synchronized void test(int ticket){}方法改爲靜態方法--->public static synchronized void test(int ticket){}

把類方法鎖住--就把代碼塊鎖住

package se.SE.practice;
class Syn{
    public synchronized void test(int ticket){
        System.out.println(Thread.currentThread().getName()+"此方法開始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"此方法結束");
    }
}
class MyThread implements Runnable {
    private int ticket = 10;
    private Syn syn;
    @Override
    public void run() {
        this.syn.test(ticket);
    }
}
public class Thread1{
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        for(int i=0;i<3;i++){
        new Thread(myThread,"線程"+i).start();
        }
    }
}

2在同步代碼塊中,鎖當前的class對象,類名稱.class

如果是鎖普通方法:將普通方法的代碼段放在synchronized(Syn.class){}包含的代碼段中

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章