Java併發編程--2.synchronized

前言

synchronized關鍵字是互斥鎖,也稱爲內部鎖ide

每一個對象都有本身的monitor(鎖標記),它是爲了分配給線程,最多一個線程能夠擁有對象的鎖測試

使用

synchronized修飾成員方法,鎖的是當前實例對象

下面是一個例子:優化

class Thread2 implements Runnable{
    private int count;
    
    //修飾成員方法,鎖的是調用它的對象,該例中也便是調用它的線程
    public synchronized void run() {
         for (int i = 0; i < 5; i ++) {
              try {
                 System.out.println(Thread.currentThread().getName() + ":" + (count++));
                 
                 Thread.sleep(100);
              } catch (InterruptedException e) {
                 e.printStackTrace();
              }
           }
    }
}

調用:this

Thread2 t2 = new Thread2();
new Thread(t2).start();
new Thread(t2).start();

synchronized修飾靜態方法,鎖的是該類的Class對象

下面是一個例子:編碼

class Thread3 implements Runnable {
    private static int count;
    
    //修飾靜態方法, 鎖的是這個類的全部對象
    public static synchronized void getCounter() {
        for (int i = 0; i < 5; i ++) {
             try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
             } catch (InterruptedException e) {
                e.printStackTrace();
             }
          }
    }
    
    @Override
    public void run() {
        getCounter();
    }
}

調用:spa

Thread3 t3_0 = new Thread3();
Thread3 t3_1 = new Thread3();
new Thread(t3_0).start(); new Thread(t3_1).start(); 

synchronized修飾代碼塊,鎖的是()中的配置對象

 下面是一個例子:線程

public class Synchronized {
    private int count;
    
    public void getCount(){
        for (int i = 0; i < 5; i ++) {
              try {
                 System.out.println(Thread.currentThread().getName() + ":" + (count++));
                 
                 Thread.sleep(100);
              } catch (InterruptedException e) {
                 e.printStackTrace();
              }
           }
    }
    
    public static void main(String[] args) throws Exception {
        Thread1 t1 = new Thread1(new Synchronized());
        new Thread(t1).start();
        new Thread(t1).start();
    }
}

class Thread1 implements Runnable{
    private Synchronized s;
    
    public Thread1(Synchronized s) {
        this.s = s;
    }
    
    @Override
    public void run() {
         //修飾代碼塊: 鎖的是()中配置的對象
         synchronized(s) {
             s.getCount();
             
             try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
         }  
    }
}

synchronized機制

Jvm須要對兩類共享數據進行保護:code

1.堆中的實例變量

2.本地方法區類變量

對於對象來講 , 有一個監視器保護實例變量; 對於類來講, 有一個監視器保護類變量對象

 

Jvm爲每一個對象和類關聯一個鎖,若是某個線程獲取了鎖, 在它釋放鎖以前其它的線程時不能得到一樣的鎖的;blog

對於一個對象,Jvm會維護一個加鎖計數器,線程每得到一次該對象, 計數器+1, 每釋放一次, 計數器-1,當計數器=0時,鎖就徹底釋放了

死鎖

當線程須要持有多個鎖時, 就有可能發生死鎖的狀況, 好比下面這個情形:

A線程首先得到lock1,在得到lock2;   B線程首先得到lock2,在得到lock1, 
當A線程得到lock1時,B線程得到lock2, 因而A線程等待lock2, B線程等待lock1,
兩個線程會無限的等待,這就發生了死鎖現象

下面是一個例子:

public class Deadlock {
    //監視器對象
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void instanceMethod1(){
        synchronized(lock1){
            System.out.println("線程1: 得到lock1,等待lock2");
            synchronized(lock2)    {
                System.out.println("線程1:得到lock2");
            }
        }
    }
    
    public void instanceMethod2(){
        synchronized(lock2){
            System.out.println("線程2: 得到lock2,等待lock1");
            synchronized(lock1){
                System.out.println("線程2: 得到lock1");
            }
        }
    }
    
    public static void main(String[] args){
        final Deadlock dld = new Deadlock();
        
        Runnable r1 = new Runnable(){
            @Override
            public void run(){
                while(true){
                    dld.instanceMethod1();
                    try{
                        System.out.println("線程1: 睡眠");
                        Thread.sleep(1000);
                    }
                    
                    catch (InterruptedException ie){
                    }
                }
            }
        };
        
        Runnable r2 = new Runnable(){
            @Override
            public void run(){
                while(true) {
                    dld.instanceMethod2();
                    try {
                        System.out.println("線程2: 睡眠");
                        Thread.sleep(1000);
                    }
                    catch (InterruptedException ie){
                    }
                }
            }
        };
        
        Thread thdA = new Thread(r1);
        Thread thdB = new Thread(r2);
        
        thdA.start();
        thdB.start();
    }
    
}

控制檯輸出:

線程1: 得到lock1,等待lock2
線程2: 得到lock2,等待lock1

避免發生死鎖

生產中,死鎖現象一旦發生,極可能會形成災難性的後果,咱們在編碼中應該避免死鎖現象發生

、儘可能不要編寫在同一時刻須要持有多個鎖的代碼;

、建立和使用一個大鎖來代替若干小鎖,並把這個鎖用於互斥,而不是用做單個對象的對象級別鎖;

鎖的優化

synchronize採起獨佔的方式,它屬於悲觀鎖,它假設了最壞的狀況,若是持有鎖的線程延遲,其餘等待程序就會測試,程序停滯不前

鎖自旋

在等待鎖時,線程不會當即進入阻塞狀態,而是先等一段時間看鎖是否被釋放

偏向鎖

一個線程得到了鎖,若是在接下來沒有別的線程得到該鎖,這個鎖會偏向第一個得到它的線程,使一個線程屢次得到鎖的代價更低 

鎖膨脹

屢次調用粒度過小的鎖, 不如一次調用粒度大的鎖

輕量級鎖

若是存在鎖的競爭,除了互斥量的開銷外,還會發生CAS操做,在同步期間若是沒有鎖競爭,使用輕量級鎖避免互斥量的開銷

相關文章
相關標籤/搜索