什麼是線程鎖
多線程可以同時執行多個任務,但是當多個線程同時訪問同一個數據時,可能導致數據不同步,甚至錯誤!
打個比方,你在銀行裏面存了一百萬,這個時候你需要從裏面取走九十萬,你女朋友也要從裏面取五十萬,如果沒有用線程鎖,那麼你們兩個人同時取錢,就有可能導致線程錯誤,你們總共從銀行取走一百四十萬元,那麼銀行就會虧本,所以要用線程鎖。
synchronized和Lock的區別
線程鎖分synchronized和Lock,那麼他們之間有什麼區別呢?
區別 | synchronized | Lock |
---|---|---|
鎖的釋放 | 自動釋放 | 必須使用finally中必須釋放,不然可能線程鎖死 |
鎖的狀態 | 無法判斷 | 可以判斷 |
存在層次 | java的關鍵字 | 一個類 |
性能 | 少量同步 | 多量同步 |
範圍 | 不但可以鎖代碼塊,他還可以方法 | 只有代碼塊 |
首先我們先不加鎖寫一個程序
package Thread;
public class mytest03 {
public static void main(String[] args) {
Money money=new Money(300);
new Thread(money,"t1").start();
new Thread(money,"t2").start();
}
}
class Money implements Runnable
{
int count;
public Money(int count) {
this.count = count;
}
@Override
public void run() {
while (true)
{
if(count<=0)
break;
else
{
System.out.println(Thread.currentThread().getName()+"取出了1萬元,----->還剩"+(count-1)+"萬元");
count=count-1;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
看上去好像沒有什麼問題,那麼我們來看一下結果
由於結果太多,我只截屏了部分,但是大家可以看到,數據有明顯的錯誤,這個是爲什麼呢?
原來,線程是由cpu調度時間的,每個線程每次搶到的時間都是不一樣的,這個就完全看cpu的性能和自己的造化了,當線程t1搶到時間準備對count進行計算的時候,線程t2也搶到了時間,並且在這個時候,t1還未對線程進行運算。
就在這個時候,兩個線程同時對count進行了運算,那麼打個比方,count從300開始,這個時候,兩個線程就會同時輸出299,但是,經過了兩個線程的運算,每次減一,那麼count就變成了298,所以下一次就會輸出298,由於每次搶到的時間都不一樣,所以輸出的答案就不一樣,但是無非都是錯的答案,當然,如果你運氣及其逆天,說不定可以碰對,不過我是沒有碰對過,所以,我們就要應用線程鎖。
線程鎖synchronized
我們知道synchronized不僅可以鎖代碼塊還可以鎖方法,那麼我們來看一下他是怎麼鎖方法的
package Thread;
public class mytest03 {
public static void main(String[] args) {
Money money=new Money(300);
new Thread(money,"t1").start();
new Thread(money,"t2").start();
}
}
class Money implements Runnable
{
int count;
public Money(int count) {
this.count = count;
}
@Override
public synchronized void run() {
while (true)
{
if(count<=0)
break;
else
{
System.out.println(Thread.currentThread().getName()+"取出了1萬元,----->還剩"+(count-1)+"萬元");
count=count-1;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
我們在上一個代碼的基礎上面給run()方法加了一個synchronized ,那麼我們得到理想的結果了嗎?我們不妨來看看
由於結果太長,我只截圖了部分,但是大家運行後會發現,全是對線程t1進行的調度,t2變成了沒人要的孤兒,這又是爲什麼呢?
原來,我們用synchronized把run()方法給鎖住了,這個時候,t1開始被調用,t1使用run方法由於裏面的while循環一直在持續,線程就是一直鎖着的,t2無法被調用,那麼就是t1在不斷的被調用直至線程結束,打個比方,a和b要去衛生間上廁所,但是a先進去了,並且把門反鎖了,這個時候b怎麼樣也進不去,除非a上完廁所自己出來。
那麼我們要怎麼寫呢?請看代碼
package Thread;
public class mytest03 {
public static void main(String[] args) {
Money money=new Money(300);
new Thread(money,"t1").start();
new Thread(money,"t2").start();
}
}
class Money implements Runnable
{
int count;
public Money(int count) {
this.count = count;
}
@Override
public void run() {
while (true)
{
synchronized (this)
{
if(count<=0)
break;
else
{
System.out.println(Thread.currentThread().getName()+"取出了1萬元,----->還剩"+(count-1)+"萬元");
count=count-1;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
這一次結果就對了,這是爲什麼呢?我們可以看到,我們在while裏面給代碼塊加了一個鎖,這樣當某一個線程使用完裏面的方法後,synchronized 就會解鎖,這個時候另一個線程就可以進去了,同時線程就會鎖死,那麼這樣就是真確的了,所以在我們上鎖的時候,要考慮到是什麼使結果發生了變化,就鎖哪裏。
線程鎖Lock
package Thread;
import java.util.concurrent.locks.ReentrantLock;
public class testLock02 {
public static void main(String[] args) {
lock1 ll=new lock1();
new Thread(ll,"小斌").start();
new Thread(ll,"小龍").start();
new Thread(ll,"小宇").start();
new Thread(ll,"小俊").start();
}
}
class lock1 implements Runnable
{
int a=600;
private final ReentrantLock locking=new ReentrantLock();
@Override
public void run() {
while (true)
{
try {
locking.lock();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(a>0)
System.out.println(Thread.currentThread().getName()+"獲的第"+(a--)+"張票");
}finally {
locking.unlock();
}
if(a<=0)
break;
}
}
}
我們可以看到,大致和synchronized 差不多,只不過是需要用 ReentrantLock來new一個鎖,然後還要自己手動解鎖。我們要寫一個try/cath,在finally處寫“鎖名.unlock”其餘情況和synchronized 差不多,這裏大家就自行思考比較好