1. 程式人生 > >深入理解Java多執行緒(四)

深入理解Java多執行緒(四)

關於java多執行緒的概念以及基本用法:java多執行緒基礎

4,Lock的使用

  • ReentrantLook類的使用
  • ReentrantReadWriteLock類的使用
4.1,ReentrantLook類的使用

新建MyService類:

public class MyService {

    private Lock lock = new ReentrantLock();
    public void testMethod() {
        lock.lock();//獲取鎖
        for(int
i=0;i<5;i++) { System.out.println("ThreadName="+Thread.currentThread().getName()+(" "+(i+1))); } lock.unlock();//解鎖 } }

MyThread類:

public class MyThread extends Thread{

    private MyService service;
    public MyThread(MyService service) {
        super();
        this
.service = service; } @Override public void run() { service.testMethod(); } }

測試類:

public class Run {

    public static void main(String[] args) {
        MyService service = new MyService();
        MyThread a1 = new MyThread(service);
        MyThread a2 = new MyThread(service);
        MyThread a3 = new
MyThread(service); MyThread a4 = new MyThread(service); MyThread a5 = new MyThread(service); a1.start(); a2.start(); a3.start(); a4.start(); a5.start(); } }

結果:

ThreadName=Thread-1 1
ThreadName=Thread-1 2
ThreadName=Thread-1 3
ThreadName=Thread-1 4
ThreadName=Thread-1 5
ThreadName=Thread-3 1
ThreadName=Thread-3 2
ThreadName=Thread-3 3
ThreadName=Thread-3 4
ThreadName=Thread-3 5
ThreadName=Thread-2 1
ThreadName=Thread-2 2
ThreadName=Thread-2 3
ThreadName=Thread-2 4
ThreadName=Thread-2 5
ThreadName=Thread-0 1
ThreadName=Thread-0 2
ThreadName=Thread-0 3
ThreadName=Thread-0 4
ThreadName=Thread-0 5
ThreadName=Thread-4 1
ThreadName=Thread-4 2
ThreadName=Thread-4 3
ThreadName=Thread-4 4
ThreadName=Thread-4 5

由結果可以看到一個執行緒列印完將鎖釋放,其他執行緒才可以繼續列印,但是執行緒間的順序是隨機的

4.2,Condition實現等待/通知

關鍵字synchronized與wait()和notify()/notifyAll()方法結合可以實現等待/通知模式,ReentrantLock藉助Condition物件可以實現相同的功能。一個Lock物件裡面可以建立多個Condition(即物件監視器)例項,執行緒物件可以註冊在指定的Condition中,從而可以自由選擇執行緒通知,使用notify()/notifyAll()方法進行通知時,被通知的執行緒是由JVM隨機選擇的

新建MyService類:

public class MyService {

    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();

    public void await() {
        try {
            lock.lock();
            System.out.println("wait 時間為:"+System.currentTimeMillis());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void signal() {
        try {
            lock.lock();
            System.out.println("signal時間為:"+System.currentTimeMillis());
            condition.signal();
        } finally{
            lock.unlock();
        }
    }
}

ThreadA類:

public class ThreadA extends Thread{

    private MyService service;

    public ThreadA(MyService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.await();
    }

}

測試類:

public class Run {

    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.start();
        Thread.sleep(3000);
        service.signal();
    }
}

結果:

wait 時間為:1534645646731
signal時間為:1534645649731

Objectl類中wait()方法相當於Condition類中的await()方法
Objectl類中notify()方法相當於Condition類中的signal()方法

4.3,多個Condition實現通知部分執行緒

新建MyService類:

public class MyService {

    private Lock lock = new ReentrantLock();
    public Condition conditionA = lock.newCondition();
    public Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println("begin awaitA時間為:"+System.currentTimeMillis()
            +"ThreadName="+Thread.currentThread().getName());
            conditionA.await();
            System.out.println("end awaitA時間為:"+System.currentTimeMillis()
            +"ThreadName="+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println("begin awaitB時間為:"+System.currentTimeMillis()
            +"ThreadName="+Thread.currentThread().getName());
            conditionB.await();
            System.out.println("end awaitB時間為:"+System.currentTimeMillis()
            +"ThreadName="+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void signalAll_A() {
        try {
            lock.lock();
            System.out.println("signalAll_A時間為:"+System.currentTimeMillis()
            +"ThreadName="+Thread.currentThread().getName());
            conditionA.signalAll();
        } finally{
            lock.unlock();
        }
    }

    public void signalAll_B() {
        try {
            lock.lock();
            System.out.println("signalAll_B時間為:"+System.currentTimeMillis()
            +"ThreadName="+Thread.currentThread().getName());
            conditionB.signalAll();
        } finally{
            lock.unlock();
        }
    }

}

兩個執行緒類:

public class ThreadA extends Thread{

    private MyService service;

    public ThreadA(MyService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.awaitA();
    }

}
public class ThreadB extends Thread {
    private MyService service;

    public ThreadB(MyService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.awaitB();
    }
}

測試類:

public class Run {

    public static void main(String[] args) throws InterruptedException{
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b= new ThreadB(service);
        b.setName("B");
        b.start();
        Thread.sleep(3000);
        service.signalAll_A();
    }
}

結果:

begin awaitA時間為:1534648259058ThreadName=A
begin awaitB時間為:1534648259058ThreadName=B
signalAll_A時間為:1534648262059ThreadName=main
end awaitA時間為:1534648262059ThreadName=A
4.4,公平鎖與非公平鎖

鎖Lock分為公平鎖與非公平鎖,公平鎖表示執行緒獲取鎖的順序是按照執行緒加鎖的順序來分配的,非公平鎖是一種獲取鎖的搶佔機制,是隨機獲得鎖的

新建Service類:

public class Service {

    private ReentrantLock lock;

    public Service(boolean isFair) {
        super();
        lock = new ReentrantLock(isFair);
    }
    public void serviceMethod() {
        try {
            lock.lock();
            System.out.println("ThreadName="+Thread.currentThread().getName()+"獲得鎖定");
        }finally{
            lock.unlock();
        }
    }

}

RunFair類:

public class RunFair {

    public static void main(String[] args) throws InterruptedException{
        final Service service = new Service(true);
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                System.out.println("*執行緒"+Thread.currentThread().getName()+"運行了");
                service.serviceMethod();

            }
        };
        Thread[] threadArray = new Thread[10];
        for(int i=0;i<10;i++) {
            threadArray[i] = new Thread(runnable);
        }
        for(int i=0;i<10;i++) {
            threadArray[i].start();
        }
    }
}

RunNoFair類:

public class RunNoFair {

    public static void main(String[] args) throws InterruptedException{
        final Service service = new Service(false);
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                System.out.println("*執行緒"+Thread.currentThread().getName()+"運行了");
                service.serviceMethod();

            }
        };
        Thread[] threadArray = new Thread[10];
        for(int i=0;i<10;i++) {
            threadArray[i] = new Thread(runnable);
        }
        for(int i=0;i<10;i++) {
            threadArray[i].start();
        }
    }
}

執行結果:

RunFair類:基本有序
RunNoFair類:基本無序
4.5,lock的一些方法
  • 方法int getHoldCount()的作用是查詢當前執行緒保持此鎖定的個數,也就是鎖的個數
  • 方法int getQueueLength()的作用是返回正等待獲取此鎖定的執行緒估計數,也就是等待鎖的執行緒
  • 方法int getWaitQueueLength(Condition condition)的作用是返回等待與此鎖定相關的給定條件condition的執行緒估計數,也就是執行了同一個condition物件的await()方法的執行緒數

  • 方法boolean hasQueuedThread(Thread thread)的作用是查詢指定的執行緒是否正在等待獲取此執行緒
  • 方法 boolean hasQueuedThreads()的作用是查詢是否有執行緒正在等待獲取此鎖定
  • 方法boolean hasWaiters(Condition condition)的作用是查詢是否有執行緒正在等待與此鎖定有關的condition條件

  • 方法boolean isFair()的作用是判斷是不是公平鎖
  • 方法boolean isHeldByCurrentThread()的作用是查詢當前執行緒是否保持此鎖定
  • 方法boolean isLocked()的作用是查詢此鎖定是否由任意執行緒保持

  • 方法 void lockInterruptibly()的作用是:如果當前執行緒未被中斷,則獲取鎖定,否則出現異常
  • 方法boolean tryLock()的作用是:僅在呼叫時鎖定未被另一個執行緒保持的情況下,才獲取該鎖定
  • 方法 boolean tryLock(long timeout,TimeUnit unit)的作用是,如果鎖定在給定等待時間內沒有被另一個執行緒保持,且當前執行緒未被中斷,則獲取該鎖定

使用Lock+Condition實現執行緒順序執行:點這裡

4.6,ReentranReadWriteLock類

同一時間只有一個執行緒在執行ReentranLock.lock()方法後面的任務,這樣確實保證了執行緒的安全,但是效率很低,ReentranReadWriteLock類可以提高效率
讀寫鎖有兩個鎖,一個讀操作相關的鎖,也稱為共享鎖,一個寫操作相關的鎖,也稱為排他鎖。讀鎖與寫鎖互斥,寫鎖與寫鎖互斥,但是讀鎖與讀鎖不互斥,也就是沒寫操作的情況下可以進行很多的讀操作

1,讀讀共享
Service類:

public class Service {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read() {
        try {
            try {
                lock.readLock().lock();
                System.out.println("獲得鎖"+Thread.currentThread().getName()
                        +" "+System.currentTimeMillis());
                Thread.sleep(10000);
            } finally {
                lock.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

兩個執行緒類:

public class ThreadA extends Thread {

    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    public void run() {
        service.read();
    }
}
public class ThreadB extends Thread {

    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    public void run() {
        service.read();
    }
}

測試類:

public class Run {

    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b= new ThreadB(service);
        b.setName("B");
        a.start();
        b.start();
    }
}

結果:

獲得鎖B 1534681602545
獲得鎖A 1534681602545

由結果可以看到,兩個執行緒幾乎同時進入lock()後的程式碼

2,寫寫互斥

保持上面程式碼不變,更改Service類:

public class Service {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void write() {
        try {
            try {
                lock.writeLock().lock();
                System.out.println("獲得寫鎖"+Thread.currentThread().getName()
                        +" "+System.currentTimeMillis());
                Thread.sleep(10000);
            } finally {
                lock.writeLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

結果:

獲得寫鎖A 1534681950514
獲得寫鎖B 1534681960516

顯然寫操作同一時間只允許一個執行緒執行

3,讀寫互斥
更改Service類和測試類:

public class Service {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read() {
        try {
            try {
                lock.readLock().lock();
                System.out.println("獲得鎖"+Thread.currentThread().getName()
                        +" "+System.currentTimeMillis());
                Thread.sleep(10000);
            } finally {
                lock.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void write() {
        try {
            try {
                lock.writeLock().lock();
                System.out.println("獲得寫鎖"+Thread.currentThread().getName()
                        +" "+System.currentTimeMillis());
                Thread.sleep(10000);
            } finally {
                lock.writeLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        Thread.sleep(1000);
        ThreadB b= new ThreadB(service);
        b.setName("B");
        b.start();
    }
}

結果:

獲得鎖A 1534682422325
獲得鎖B 1534682423326

4,寫讀互斥
更改測試類;

public class Run {

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();

        ThreadB b= new ThreadB(service);
        b.setName("B");
        b.start();
        Thread.sleep(1000);
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

    }
}

結果:

獲得鎖B 1534682527683
獲得鎖A 1534682528683

綜上:只有讀讀操作是非同步且非互斥的,其他的都是互斥的