首頁 Java Java入門 什麼是java線程同步

什麼是java線程同步

Dec 09, 2019 pm 04:27 PM
java 執行緒同步

什麼是java線程同步

#執行緒同步

多執行緒之間呼叫同一物件時,為了運行的安全性和準確性,需要對該物件進行同步,確保每一個執行緒用到的時候該物件的結果都是正確的,該物件的狀態都是合理的,這部分涉及同步、線程鎖等知識點。這部分的只是就牽涉到了synchronized、同步鎖(Lock)的概念。

synchronized

synchronized關鍵字可以修飾物件、方法,通常用法如下:

//同步代码块
synchronized(Object object){
...
}
//或者
//同步方法
public synchronized void test(){
...
}
登入後複製

其中有一個同步監視器的概念,例如上面同步程式碼區塊的object物件以及同步方法的this物件就會同步監視,多個執行緒同時呼叫一個同步的程式碼區塊或方法時,在任何時刻只能夠一個執行緒能夠獲得該同步監視的物件鎖,執行完程式碼之後才會釋放該鎖,在此期間其他呼叫的執行緒只能等待該鎖釋放後才能呼叫。

上文中提到的SellRunnable類別中的sell方法也用到了synchronized,上文中程式碼執行太快,所以感知不到,如果修改一下就能明白有沒有synchronized的區別了。

public class ThreadTest {
    public static void main(String[] args) {
        SellRunnable sellRunnable = new SellRunnable();
        Thread thread1 = new Thread(sellRunnable, "1");
        Thread thread2 = new Thread(sellRunnable, "2");
        Thread thread3 = new Thread(sellRunnable, "3");
        thread2.start();
        thread1.start();
        thread3.start();
    }
}
class SellRunnable implements Runnable {
    //有十张票
    int index = 10;
    public void sell() {
        if (index >= 1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            index--;
            System.out.println("售货窗口:" + Thread.currentThread().getName() + 
            " 卖出了一张票,剩余:
            " + index);
        } else {
            System.out.println("售货窗口:" + Thread.currentThread().getName() + " 买票时没票了");
        }
    }
    @Override
    public void run() {
        while (index > 0) {
            System.out.println("售货窗口:" + Thread.currentThread().getName() + " 开始买票");
            sell();
        }
    }
}
//执行结果:
售货窗口:1 开始买票
售货窗口:2 开始买票
售货窗口:3 开始买票
售货窗口:2  卖出了一张票,剩余:9
售货窗口:2 开始买票
售货窗口:1  卖出了一张票,剩余:9
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:8
售货窗口:3 开始买票
售货窗口:1  卖出了一张票,剩余:6
售货窗口:1 开始买票
售货窗口:2  卖出了一张票,剩余:6
售货窗口:2 开始买票
售货窗口:3  卖出了一张票,剩余:5
售货窗口:3 开始买票
售货窗口:1  卖出了一张票,剩余:4
售货窗口:1 开始买票
售货窗口:2  卖出了一张票,剩余:3
售货窗口:3  卖出了一张票,剩余:2
售货窗口:3 开始买票
售货窗口:2 开始买票
售货窗口:3  卖出了一张票,剩余:1
售货窗口:2  卖出了一张票,剩余:0
售货窗口:1  卖出了一张票,剩余:1
Process finished with exit code 0  //可以看到,票数减少是错误的
//sell方法添加synchronized修饰符后 执行结果:
public synchronized void sell() {
        if (index >= 1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            index--;
            System.out.println("售货窗口:" + Thread.currentThread().getName() + 
            " 卖出了一张票,剩余:
            " + index);
        } else {
            System.out.println("售货窗口:" + Thread.currentThread().getName() + " 买票时没票了");
        }
    }
售货窗口:2 开始买票
售货窗口:3 开始买票
售货窗口:1 开始买票
售货窗口:2  卖出了一张票,剩余:9
售货窗口:2 开始买票
售货窗口:1  卖出了一张票,剩余:8
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:7
售货窗口:3 开始买票
售货窗口:1  卖出了一张票,剩余:6
售货窗口:1 开始买票
售货窗口:2  卖出了一张票,剩余:5
售货窗口:2 开始买票
售货窗口:1  卖出了一张票,剩余:4
售货窗口:1 开始买票
售货窗口:1  卖出了一张票,剩余:3
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:2
售货窗口:3 开始买票
售货窗口:1  卖出了一张票,剩余:1
售货窗口:1 开始买票
售货窗口:1  卖出了一张票,剩余:0
售货窗口:2 买票时没票了
售货窗口:3 买票时没票了
Process finished with exit code 0  // 可以看到,票数是正常减少的
登入後複製

以上對於sell方法進行同步之後,在某一瞬間,只會有一個執行緒呼叫該方法,所以裡面判斷index的時候得到的結果就是正確的結果。

以上同步的時候,是以降低運行效率的方式來保證線程安全的,為此,不要對線程使用類中沒必要的方法、對象進行同步標識,只對有競爭的資源或者代碼進行同步標識。

同步標識後,有以下幾點可以釋放該鎖定:

程式碼區塊、方法執行完畢(正常完畢、return或break、拋出例外)

調用了wait方法,使得當前執行緒暫停。

當執行緒執行到同步程式碼區塊時,sleep、yield方法不會釋放該同步鎖,掛起方法suspend也不會(在執行緒操作過程中盡量避免使用suspend、resume來操作執行緒狀態,容易導致死鎖。)

同步鎖定Lock

上文中提到的synchronized是java中的關鍵字,也提到了在sleep的時候、進行IO操作的時候該線程不會釋放線程鎖,其他線程就需要一直等待,這樣有時會降低執行的效率,所以就需要一個可以在線程阻塞時可以釋放線程鎖的替代方案,Lock就是為了解決這個問題出現的。

Lock是一個java中的類,在java.util.concurrent.locks套件中,具體的程式碼如下:

public interface Lock {
    void lock();//加锁
    void lockInterruptibly() throws InterruptedException;//加锁
    boolean tryLock();//加锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//加锁
    void unlock();//释放锁
    Condition newCondition();//线程协作中用到
}
登入後複製

Lock介面的一個實作子類別為ReentrantLock,在java. util.concurrent.locks套件下,ReentrantLock的原始碼如下:

public class ReentrantLock implements Lock, Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final ReentrantLock.Sync sync;
    public ReentrantLock() {
        this.sync = new ReentrantLock.NonfairSync();
    }
    public ReentrantLock(boolean var1) {//是否创建公平锁
        this.sync = (ReentrantLock.Sync)(var1?new ReentrantLock.FairSync():new  ReentrantLock.
        NonfairSync());
    }
    public void lock() {
        this.sync.lock();
    }
    public void lockInterruptibly() throws InterruptedException {
        this.sync.acquireInterruptibly(1);
    }
    public boolean tryLock() {
        return this.sync.nonfairTryAcquire(1);
    }
    public boolean tryLock(long var1, TimeUnit var3) throws InterruptedException {
        return this.sync.tryAcquireNanos(1, var3.toNanos(var1));
    }
    public void unlock() {
        this.sync.release(1);
    }
    public Condition newCondition() {
        return this.sync.newCondition();
    }
    public int getHoldCount() {//当前线程持有该锁的数量
        return this.sync.getHoldCount();
    }
    public boolean isHeldByCurrentThread() {//该锁是否被当前线程持有
        return this.sync.isHeldExclusively();
    }
    public boolean isLocked() {//是否被其他线程持有该锁
        return this.sync.isLocked();
    }
    public final boolean isFair() {//是否是公平锁
        return this.sync instanceof ReentrantLock.FairSync;
    }
    protected Thread getOwner() {//当前锁的持有线程
        return this.sync.getOwner();
    }
    public final boolean hasQueuedThreads() {//是否有线程在等待该锁
        return this.sync.hasQueuedThreads();
    }
    public final boolean hasQueuedThread(Thread var1) {//目标线程是否在等待该锁
        return this.sync.isQueued(var1);
    }
    public final int getQueueLength() {//等待该锁线程的数量
        return this.sync.getQueueLength();
    }
    protected Collection<Thread> getQueuedThreads() {//获取所有等待该锁的线程集合
        return this.sync.getQueuedThreads();
    }
    ...
    
}
登入後複製

Lock的使用方法

lock

lock() 用來取得鎖,如果該鎖被其他執行緒佔用,則進入等待。

public class LockTest {
    public static void main(String[] args) {
        com.test.java.SellRunnable sellRunnable = new com.test.java.SellRunnable();
        Thread thread1 = new Thread(sellRunnable, "1号窗口");
        Thread thread2 = new Thread(sellRunnable, "2号窗口");
        Thread thread3 = new Thread(sellRunnable, "3号窗口");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
登入後複製
public class SellRunnable implements Runnable {
    //有十张票
    int index = 10;
    Lock lock = new ReentrantLock();
    public void sell() {
        try {
            lock.lock();
            System.out.println("售货柜台:" + Thread.currentThread().getName() + 
            "获取了票源+++++");
            if (index >= 1) {
                index--;
                System.out.println("售货柜台:" + Thread.currentThread().getName() + 
                "卖出了一张票,剩余:
                " + index);
            } else {
                System.out.println("售货柜台:" + Thread.currentThread().getName() + 
                "买票时没票了000");
            }
        } finally {
            lock.unlock();
        }
    }
    @Override
    public void run() {
        while (index > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }
}
登入後複製

運行結果:

售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:9
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:8
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:7
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:6
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:5
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:4
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:3
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:2
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:1
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:0
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口买票时没票了000
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口买票时没票了000
Process finished with exit code 0  //每一个窗口都随机获取票源、然后卖出票
登入後複製

tryLock

tryLock()嘗試取得鎖,如果取得成功回傳true,如果失敗,則傳回false,不會進入等待狀態。

public class SellRunnable implements Runnable {
    //有十张票
    int index = 10;
    Lock lock = new ReentrantLock();
    public void sell() {
        if (lock.tryLock()) {
            try {
                System.out.println("售货柜台:" + Thread.currentThread().getName() + 
                "获取了票源+++++");
                if (index >= 1) {
                    index--;
                    System.out.println("售货柜台:" + Thread.currentThread().getName() + 
                    "卖出了一张票,剩余:" + index);
                } else {
                    System.out.println("售货柜台:" + Thread.currentThread().getName() + 
                    "买票时没票了000");
                }
            } finally {
                lock.unlock();
            }
        } else {
           System.out.println("售货柜台:" + Thread.currentThread().getName()+"没有获取票源!!!");
        }
    }
    @Override
    public void run() {
        while (index > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }
}
登入後複製

運行結果:

售货柜台:1号窗口获取了票源+++++
售货柜台:3号窗口没有获取票源!!!
售货柜台:2号窗口没有获取票源!!!
售货柜台:1号窗口卖出了一张票,剩余:9
售货柜台:2号窗口没有获取票源!!!
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:8
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:7
售货柜台:1号窗口没有获取票源!!!
售货柜台:3号窗口没有获取票源!!!
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:6
售货柜台:1号窗口获取了票源+++++
售货柜台:2号窗口没有获取票源!!!
售货柜台:3号窗口没有获取票源!!!
售货柜台:1号窗口卖出了一张票,剩余:5
售货柜台:2号窗口获取了票源+++++
售货柜台:1号窗口没有获取票源!!!
售货柜台:2号窗口卖出了一张票,剩余:4
售货柜台:3号窗口没有获取票源!!!
售货柜台:1号窗口获取了票源+++++
售货柜台:2号窗口没有获取票源!!!
售货柜台:3号窗口没有获取票源!!!
售货柜台:1号窗口卖出了一张票,剩余:3
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:2
售货柜台:2号窗口获取了票源+++++
售货柜台:3号窗口没有获取票源!!!
售货柜台:2号窗口卖出了一张票,剩余:1
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:0
售货柜台:3号窗口没有获取票源!!!
售货柜台:2号窗口没有获取票源!!!
Process finished with exit code 0//没有获取到货源的票口,就直接没有等待,进入下次买票
登入後複製

tryLock(long time, TimeUnit unit)

tryLock(long time, TimeUnit unit)可以設定拿不到鎖的時候等待一段時間。 //第一個參數時常長,第二個參數時間單位

public class SellRunnable implements Runnable {
    //有十张票
    int index = 10;
    Lock lock = new ReentrantLock();
    public void sell() {
        try {
            if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
                try {
                    System.out.println("售货柜台:" + Thread.currentThread().getName() + 
                    "获取了票源+++++");
                    if (index >= 1) {
                        index--;
                        System.out.println("售货柜台:" + Thread.currentThread().getName()
                         +"卖出了一张票,剩余:" + index);
                    } else {
                        System.out.println("售货柜台:" + Thread.currentThread().
                        getName()  + "买票时没票了000");
                    }
                    try {
                        Thread.sleep(2000);//人为加入买票时间
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println("售货柜台:" + Thread.currentThread().getName() + 
                "没有获取票源!!!");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        while (index > 0) {
            try {
                Thread.sleep(500);//要不执行太快,看不出效果
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }
}
登入後複製

執行結果:

售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:9
售货柜台:2号窗口没有获取票源!!!
售货柜台:3号窗口没有获取票源!!!
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:8
售货柜台:3号窗口没有获取票源!!!
售货柜台:1号窗口没有获取票源!!!
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:7
售货柜台:1号窗口没有获取票源!!!
售货柜台:2号窗口没有获取票源!!!
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:6
售货柜台:2号窗口没有获取票源!!!
售货柜台:3号窗口没有获取票源!!!
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:5
售货柜台:3号窗口没有获取票源!!!
售货柜台:1号窗口没有获取票源!!!
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:4
售货柜台:1号窗口没有获取票源!!!
售货柜台:2号窗口没有获取票源!!!
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:3
售货柜台:2号窗口没有获取票源!!!
售货柜台:3号窗口没有获取票源!!!
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:2
售货柜台:3号窗口没有获取票源!!!
售货柜台:1号窗口没有获取票源!!!
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:1
售货柜台:1号窗口没有获取票源!!!
售货柜台:2号窗口没有获取票源!!!
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:0
售货柜台:2号窗口没有获取票源!!!
售货柜台:3号窗口没有获取票源!!!
Process finished with exit code 0 //当买票时间大约等待时间时,则没有获取票源的窗口不买票,进入下个买票机会
登入後複製

將買票時間縮短:

try {
    Thread.sleep(500);//人为加入买票时间
} catch (InterruptedException e) {
    e.printStackTrace();
}
登入後複製

執行結果:

售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:9
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:8
售货柜台:3号窗口没有获取票源!!!
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:7
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:6
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:5
售货柜台:3号窗口没有获取票源!!!
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:4
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:3
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:2
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:1
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:0
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口买票时没票了000
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口买票时没票了000
Process finished with exit code 0 //等待时间内获取到票源了,也就卖出票了
登入後複製

lockInterruptibly

lockInterruptibly()透過該方法取得鎖定時,如果該鎖定正在被其他執行緒持有,則進入等待狀態,但是這個等待過程是可以中斷的,透過呼叫Thread物件的interrupt方法就可中斷等待,中斷時拋出異常InterruptedException,需要擷取或宣告拋出。

public class ThreadTest {
    public static void main(String[] args) {
        SellRunnable sellRunnable = new SellRunnable();
        Thread thread1 = new Thread(sellRunnable, "1号窗口");
        Thread thread2 = new Thread(sellRunnable, "2号窗口");
        Thread thread3 = new Thread(sellRunnable, "3号窗口");
        thread1.start();
        try {
            Thread.sleep(500);//确保窗口1号先获取锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
        thread3.start();
        try {
            Thread.sleep(2000);//等待两秒后,打断窗口2、3的等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
        thread3.interrupt();
    }
}
SellRunnable中等待时间加长:
try {
    Thread.sleep(5000);//人为加入买票时间
} catch (InterruptedException e) {
    e.printStackTrace();
}
登入後複製

執行結果:

售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:9
售货柜台:3号窗口被打断了      //这个地方被打断了
售货柜台:2号窗口被打断了      //这个地方被打断了
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:8
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:7
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:6
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:5
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:4
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:3
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口卖出了一张票,剩余:2
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口卖出了一张票,剩余:1
售货柜台:1号窗口获取了票源+++++
售货柜台:1号窗口卖出了一张票,剩余:0
售货柜台:2号窗口获取了票源+++++
售货柜台:2号窗口买票时没票了000
售货柜台:3号窗口获取了票源+++++
售货柜台:3号窗口买票时没票了000
Process finished with exit code 0
登入後複製

synchronized和Lock對比

透過以上程式碼,可以看出Lock和synchronized的幾點關聯與區別:

兩者都是可重入鎖定

可重入鎖定是指當一個執行緒獲得物件鎖定之後,該執行緒可以再次取得該物件的鎖而不被阻塞。例如同一個類別中有多個方法(或方法遞歸呼叫)被synchronized修飾或被Lock加持後,同一個執行緒在呼叫這兩個方法時都可以取得該物件的鎖而不被阻塞。

不可重入鎖定的範例:

public class Lock{
    private boolean isLocked = false;
    public void lock(){
        while(isLocked){    
            wait();
        }
        isLocked = true;
    }
    public void unlock(){
        isLocked = false;
        notify();
    }
}
//使用方法:
public class Test{
    Lock lock = new Lock();
    public void test1(){
        lock.lock();
        test2();
        lock.unlock();
    }
    public void test2(){
        lock.lock();
        ...
        lock.unlock();
    }
}
登入後複製

Test類別在呼叫test1方法的時候,執行完lock.lock()後呼叫test2的時候,就會一直等待,變成死鎖。

可重入鎖定設計原理:

public class Lock{
    private boolean isLocked = false;
    private Thread lockedThread = null;
    int lockedCount = 0;
    public void lock(){
        Thread thread = Thread.currentThread();
        while(isLocked && thread != lockedThread){    
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedThread = thread;
    }
    public void unlock(){
        Thread thread = Thread.currentThread();
        if(thread == lockedThread){    
            lockedCount--;
            if(lockedCount == 0){
                isLocked = false;
                lockedThread = null;
                notify();
            }
        }
    }
}
登入後複製

這樣呼叫Test類別的test1方法後,test2方法也能順利被執行。

synchronized在實作上也基本上是採用記數器的方式來實現可重入的。

Lock是可中斷鎖,synchronized不可中斷。

当一个线程B执行被锁的对象的代码时,发现线程A已经持有该锁,那么线程B就会进入等待,但是synchronized就无法中断该等待过程,而Lock就可以通过lockInterruptibly方法抛出异常从而中断等待,去处理别的事情。

Lock可创建公平锁,synchronized是非公平锁。

公平锁的意思是按照请求的顺序来获取锁,不平公锁就无法保证线程获取锁的先后次序。

Lock可以知道是否获取到锁,synchronized不可以。

synchronized在发生异常或者运行完毕,会自动释放线程占有的锁。而Lock需要主动释放锁,否则会锁死;

synchronized在阻塞时,别的线程无法获取锁,Lock可以(这也是lock设计的一个目的)。

读写锁

多个线程对同一个文件进行写操作时,会发生冲突所以需要加锁,但是对同一个文件进行读操作的时候,使用上面的方法会造成效率的降低,所以基于这种情况,产生了ReadWriteLock这个接口:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */
    Lock readLock();//读的锁
 
    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */
    Lock writeLock();//写的锁
}
登入後複製

这个接口的实现类是ReentrantReadWriteLock,其源代码如下:

public class ReentrantReadWriteLock implements ReadWriteLock, Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;
    ...
    public ReentrantReadWriteLock.WriteLock writeLock() {//获取write lock
        return this.writerLock;
    }
    public ReentrantReadWriteLock.ReadLock readLock() {//获取read lock
        return this.readerLock;
    }
    ...
}
登入後複製

使用方法和Lock一样,使用到write时调用writeLock()方法获取lock进行加锁,使用到read时调用readLock()方法进行加锁,需要注意的知识点如下:

线程A占用写锁,线程B在申请写、读的时候需要等待。

线程A占用读锁,线程B在申请写操作时,需要等待。

线程A占用读锁,线程B获取读操作时可以获取到。

总结

如果需要效率提升,则建议使用Lock,如果效率要求不高,则synchronized满足使用条件,业务逻辑写起来也简单,不需要手动释放锁。

PHP中文网,有大量免费的JAVA入门教程,欢迎大家学习!

以上是什麼是java線程同步的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Java Spring 面試題 Java Spring 面試題 Aug 30, 2024 pm 04:29 PM

在本文中,我們保留了最常被問到的 Java Spring 面試問題及其詳細答案。這樣你就可以順利通過面試。

突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

Java程序查找膠囊的體積 Java程序查找膠囊的體積 Feb 07, 2025 am 11:37 AM

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

創造未來:零基礎的 Java 編程 創造未來:零基礎的 Java 編程 Oct 13, 2024 pm 01:32 PM

Java是熱門程式語言,適合初學者和經驗豐富的開發者學習。本教學從基礎概念出發,逐步深入解說進階主題。安裝Java開發工具包後,可透過建立簡單的「Hello,World!」程式來實踐程式設計。理解程式碼後,使用命令提示字元編譯並執行程序,控制台上將輸出「Hello,World!」。學習Java開啟了程式設計之旅,隨著掌握程度加深,可創建更複雜的應用程式。

See all articles