360、Java中級15 -【多線程 - LOCK對象】 2020.06.26

1、回憶 synchronized 同步的方式

首先回憶一下 synchronized 同步對象的方式

當一個線程佔用 synchronized 同步對象,其他線程就不能佔用了,直到釋放這個同步對象爲止

與synchronized類似的,lock也能夠達到同步的效果
在這裏插入圖片描述

package multiplethread;
   
import java.text.SimpleDateFormat;
import java.util.Date;
    
public class TestThread {
      
    public static String now(){
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }
      
    public static void main(String[] args) {
        final Object someObject = new Object();
           
        Thread t1 = new Thread(){
            public void run(){
                try {
                    System.out.println( now()+" t1 線程已經運行");
                    System.out.println( now()+this.getName()+ " 試圖佔有對象:someObject");
                    synchronized (someObject) {
                           
                        System.out.println( now()+this.getName()+ " 佔有對象:someObject");
                        Thread.sleep(5000);
                        System.out.println( now()+this.getName()+ " 釋放對象:someObject");
                    }
                    System.out.println(now()+" t1 線程結束");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        };
        t1.setName(" t1");
        t1.start();
        Thread t2 = new Thread(){
   
            public void run(){
                try {
                    System.out.println( now()+" t2 線程已經運行");
                    System.out.println( now()+this.getName()+ " 試圖佔有對象:someObject");
                    synchronized (someObject) {
                        System.out.println( now()+this.getName()+ " 佔有對象:someObject");
                        Thread.sleep(5000);
                        System.out.println( now()+this.getName()+ " 釋放對象:someObject");
                    }
                    System.out.println(now()+" t2 線程結束");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        };
        t2.setName(" t2");
        t2.start();
    }
        
}

2、使用Lock對象實現同步效果

Lock是一個接口,爲了使用一個Lock對象,需要用到

Lock lock = new ReentrantLock();

在這裏插入圖片描述
與 synchronized (someObject) 類似的,lock()方法,表示當前線程佔用lock對象,一旦佔用,其他線程就不能佔用了。
與 synchronized 不同的是,一旦synchronized 快結束,就會自動釋放對someObject的佔用。 lock卻必須調用unlock方法進行手動釋放,爲了保證釋放的執行,往往會把unlock() 放在finally中進行。

package multiplethread;
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class TestThread {
 
    public static String now() {
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }
 
    public static void log(String msg) {
        System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg);
    }
 
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
 
        Thread t1 = new Thread() {
            public void run() {
                try {
                    log("線程啓動");
                    log("試圖佔有對象:lock");
 
                    lock.lock();
 
                    log("佔有對象:lock");
                    log("進行5秒的業務操作");
                    Thread.sleep(5000);
 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    log("釋放對象:lock");
                    lock.unlock();
                }
                log("線程結束");
            }
        };
        t1.setName("t1");
        t1.start();
        try {
            //先讓t1飛2秒
            Thread.sleep(2000);
        } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        Thread t2 = new Thread() {
 
            public void run() {
                try {
                    log("線程啓動");
                    log("試圖佔有對象:lock");
 
                    lock.lock();
 
                    log("佔有對象:lock");
                    log("進行5秒的業務操作");
                    Thread.sleep(5000);
 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    log("釋放對象:lock");
                    lock.unlock();
                }
                log("線程結束");
            }
        };
        t2.setName("t2");
        t2.start();
    }
 
}

3、trylock方法

synchronized 是不佔用到手不罷休的,會一直試圖佔用下去。
與 synchronized 的鑽牛角尖不一樣,Lock接口還提供了一個trylock方法。
trylock會在指定時間範圍內試圖佔用,佔成功了,就啪啪啪。 如果時間到了,還佔用不成功,扭頭就走~

注意: 因爲使用trylock有可能成功,有可能失敗,所以後面unlock釋放鎖的時候,需要判斷是否佔用成功了,如果沒佔用成功也unlock,就會拋出異常

package multiplethread;
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class TestThread {
 
    public static String now() {
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }
 
    public static void log(String msg) {
        System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg);
    }
 
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
 
        Thread t1 = new Thread() {
            public void run() {
                boolean locked = false;
                try {
                    log("線程啓動");
                    log("試圖佔有對象:lock");
 
                    locked = lock.tryLock(1,TimeUnit.SECONDS);
                    if(locked){
                        log("佔有對象:lock");
                        log("進行5秒的業務操作");
                        Thread.sleep(5000);
                    }
                    else{
                        log("經過1秒鐘的努力,還沒有佔有對象,放棄佔有");
                    }
 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                     
                    if(locked){
                        log("釋放對象:lock");
                        lock.unlock();
                    }
                }
                log("線程結束");
            }
        };
        t1.setName("t1");
        t1.start();
        try {
            //先讓t1飛2秒
            Thread.sleep(2000);
        } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        Thread t2 = new Thread() {
 
            public void run() {
                boolean locked = false;
                try {
                    log("線程啓動");
                    log("試圖佔有對象:lock");
 
                    locked = lock.tryLock(1,TimeUnit.SECONDS);
                    if(locked){
                        log("佔有對象:lock");
                        log("進行5秒的業務操作");
                        Thread.sleep(5000);
                    }
                    else{
                        log("經過1秒鐘的努力,還沒有佔有對象,放棄佔有");
                    }
 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                     
                    if(locked){
                        log("釋放對象:lock");
                        lock.unlock();
                    }
                }
                log("線程結束");
            }
        };
        t2.setName("t2");
        t2.start();
    }
 
}

4、總結Lock和synchronized的區別

  1. Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現,Lock是代碼層面的實現。

  2. Lock可以選擇性的獲取鎖,如果一段時間獲取不到,可以放棄。synchronized不行,會一根筋一直獲取下去。 藉助Lock的這個特性,就能夠規避死鎖,synchronized必須通過謹慎和良好的設計,才能減少死鎖的發生。

  3. synchronized在發生異常和同步快結束的時候,會自動釋放鎖。而Lock必須手動釋放, 所以如果忘記了釋放鎖,一樣會造成死鎖。

5、參考鏈接

[01] How2j - 多線程 - LOCK對象

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