深入理解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
綜上:只有讀讀操作是非同步且非互斥的,其他的都是互斥的