多線程 Lock對象 與synchronized類似的,lock也能夠達到同步的效果 步驟 1 : 回憶 synchronized 同步的方式 首先回憶一下 synchronized 同步對象的方式 當一個線程占用 synchronized 同步對象,其他線程就不能占用了,直到釋放這個同步對象為止 ...
多線程 Lock對象
與synchronized類似的,lock也能夠達到同步的效果
步驟 1 : 回憶 synchronized 同步的方式
首先回憶一下 synchronized 同步對象的方式
當一個線程占用 synchronized 同步對象,其他線程就不能占用了,直到釋放這個同步對象為止
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 : 線程交互
使用synchronized方式進行線程交互,用到的是同步對象的wait,notify和notifyAll方法
Lock也提供了類似的解決辦法,首先通過lock對象得到一個Condition對象,然後分別調用這個Condition對象的:await, signal,signalAll 方法
註意: 不是Condition對象的wait,nofity,notifyAll方法,是await,signal,signalAll
package multiplethread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.Condition;
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();
Condition condition = lock.newCondition();
Thread t1 = new Thread() {
public void run() {
try {
log("線程啟動");
log("試圖占有對象:lock");
lock.lock();
log("占有對象:lock");
log("進行5秒的業務操作");
Thread.sleep(5000);
log("臨時釋放對象 lock, 並等待");
condition.await();
log("重新占有對象 lock,併進行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);
log("喚醒等待中的線程");
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log("釋放對象:lock");
lock.unlock();
}
log("線程結束");
}
};
t2.setName("t2");
t2.start();
}
}
步驟 5 : 總結Lock和synchronized的區別
Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內置的語言實現,Lock是代碼層面的實現。
Lock可以選擇性的獲取鎖,如果一段時間獲取不到,可以放棄。synchronized不行,會一根筋一直獲取下去。 藉助Lock的這個特性,就能夠規避死鎖,synchronized必須通過謹慎和良好的設計,才能減少死鎖的發生。
synchronized在發生異常和同步塊結束的時候,會自動釋放鎖。而Lock必須手動釋放, 所以如果忘記了釋放鎖,一樣會造成死鎖。
練習: 多線程 Lock對象
當多個線程按照不同順序占用多個同步對象的時候,就有可能產生死鎖現象。
死鎖之所以會發生,就是因為synchronized 如果占用不到同步對象,就會苦苦的一直等待下去,藉助tryLock的有限等待時間,解決死鎖問題
答案 :
package multiplethread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread {
public static void main(String[] args) throws InterruptedException {
Lock lock_ahri = new ReentrantLock();
Lock lock_annie = new ReentrantLock();
Thread t1 = new Thread() {
public void run() {
// 占有九尾妖狐
boolean ahriLocked = false;
boolean annieLocked = false;
try {
ahriLocked = lock_ahri.tryLock(10, TimeUnit.SECONDS);
if (ahriLocked) {
System.out.println("t1 已占有九尾妖狐");
// 停頓1000秒,另一個線程有足夠的時間占有安妮
Thread.sleep(1000);
System.out.println("t1 試圖在10秒內占有安妮");
try {
annieLocked = lock_annie.tryLock(10, TimeUnit.SECONDS);
if (annieLocked)
System.out.println("t1 成功占有安妮,開始啪啪啪");
else{
System.out.println("t1 老是占用不了安妮,放棄");
}
} finally {
if (annieLocked){
System.out.println("t1 釋放安妮");
lock_annie.unlock();
}
}
}
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} finally {
if (ahriLocked){
System.out.println("t1 釋放九尾狐");
lock_ahri.unlock();
}
}
}
};
t1.start();
Thread.sleep(100);
Thread t2 = new Thread() {
public void run() {
boolean annieLocked = false;
boolean ahriLocked = false;
try {annieLocked = lock_annie.tryLock(10, TimeUnit.SECONDS);
if (annieLocked){
System.out.println("t2 已占有安妮");
// 停頓1000秒,另一個線程有足夠的時間占有安妮
Thread.sleep(1000);
System.out.println("t2 試圖在10秒內占有九尾妖狐");
try {
ahriLocked = lock_ahri.tryLock(10, TimeUnit.SECONDS);
if (ahriLocked)
System.out.println("t2 成功占有九尾妖狐,開始啪啪啪");
else{
System.out.println("t2 老是占用不了九尾妖狐,放棄");
}
}
finally {
if (ahriLocked){
System.out.println("t2 釋放九尾狐");
lock_ahri.unlock();
}
}
}
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} finally {
if (annieLocked){
System.out.println("t2 釋放安妮");
lock_annie.unlock();
}
}
}
};
t2.start();
}
}