進(jìn)程間通信有哪些方法?
(1)管道(Pipe):管道可用于具有親緣關(guān)系進(jìn)程間的通信,允許一個(gè)進(jìn)程和另一個(gè)與它有共同祖先的進(jìn)程之間進(jìn)行通信。
(2)命名管道(named pipe):命名管道克服了管道沒(méi)有名字的限制,因此,除具有管道所具有的功能外,它還允許無(wú)親緣關(guān) 系 進(jìn)程間的通信。
(3)信號(hào)(Signal):信號(hào)是比較復(fù)雜的通信方式,用于通知接受進(jìn)程有某種事件發(fā)生,除了用于進(jìn)程間通信外,進(jìn)程還可以發(fā)送 信號(hào)給進(jìn)程本身
(4) 消息(Message)隊(duì)列:消息隊(duì)列是消息的鏈接表,包括Posix消息隊(duì)列system V消息隊(duì)列。
(5)共享內(nèi)存:使得多個(gè)進(jìn)程可以訪(fǎng)問(wèn)同一塊內(nèi)存空間,是最快的可用IPC形式。
(6)內(nèi)存映射(mapped memory):內(nèi)存映射允許任何多個(gè)進(jìn)程間通信,每一個(gè)使用該機(jī)制的進(jìn)程通過(guò)把一個(gè)共享的文件映射到自己的進(jìn)程地址空間來(lái)實(shí)現(xiàn)它。
(7)信號(hào)量(semaphore):主要作為進(jìn)程間以及同一進(jìn)程不同線(xiàn)程之間的同步手段。
(8)套接口(Socket):更為一般的進(jìn)程間通信機(jī)制,可用于不同機(jī)器之間的進(jìn)程間通信。Linux和System V的變種都支持套接字
線(xiàn)程間通信有哪些方法? 給出一些代碼例子
共享變量
共享變量是使用一個(gè)volatile關(guān)鍵字的變量作為標(biāo)記,來(lái)進(jìn)行線(xiàn)程間的通信。
下面使用一個(gè)例子來(lái)說(shuō)明:
package com.Thread.Communication;
//使用volatile變量 來(lái)設(shè)置標(biāo)記位 達(dá)到線(xiàn)程通信的目的
public class T1 {
private static volatile boolean flag = false;
public void setFlag() {
flag = true;
}
private void exe() {
System.out.println("執(zhí)行主業(yè)務(wù)程序....");
}
public void execute() {
while (!flag) {
System.out.println("等待放行信號(hào)....");
}
exe();
}
}
class Thead1 implements Runnable {
private T1 t1;;
public Thead1(T1 t1) {
// TODO Auto-generated constructor stub
this.t1 = t1;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("set flag ready");
t1.setFlag();
System.out.println("set flag done");
}
}
class Thread2 implements Runnable {
private T1 t2;
public Thread2(T1 t1) {
// TODO Auto-generated constructor stub
t2 = t1;
}
@Override
public void run() {
// TODO Auto-generated method stub
t2.execute();
}
}
class Client {
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
Thead1 thead1 = new Thead1(t1);
Thread2 thread2 = new Thread2(t1);
Thread tt1 = new Thread(thead1);
Thread tt2 = new Thread(thread2);
tt2.start();
Thread.sleep(3000);
tt1.start();
}
}
等待放行信號(hào)....
等待放行信號(hào)....
等待放行信號(hào)....
set flag ready
等待放行信號(hào)....
set flag done
執(zhí)行主業(yè)務(wù)程序....
在T1的方法中,我們可以看到,有一個(gè)標(biāo)記量flag,然后還有一個(gè)主業(yè)務(wù)的執(zhí)行程序方法,要實(shí)現(xiàn)的功能就是,當(dāng)flag為真時(shí)才執(zhí)行主程序的方法。使用了兩個(gè)線(xiàn)程,一個(gè)去改變狀態(tài)一個(gè)去執(zhí)行方法。通過(guò)flag達(dá)到線(xiàn)程通信的目的。
同步方法
直接使用synchronize關(guān)鍵字加在方法上,使得要執(zhí)行方法必須獲得對(duì)象鎖。一次只能一個(gè)線(xiàn)程執(zhí)行。
下面是代碼例子:
public class MyObject {
synchronized public void methodA() {
//do something....
}
synchronized public void methodB() {
//do some other thing
}
}
public class ThreadA extends Thread {
private MyObject object;
//省略構(gòu)造方法
@Override
public void run() {
super.run();
object.methodA();
}
}
public class ThreadB extends Thread {
private MyObject object;
//省略構(gòu)造方法
@Override
public void run() {
super.run();
object.methodB();
}
}
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
//線(xiàn)程A與線(xiàn)程B 持有的是同一個(gè)對(duì)象:object
ThreadA a = new ThreadA(object);
ThreadB b = new ThreadB(object);
a.start();
b.start();
}
}
我們看到 當(dāng)兩個(gè)線(xiàn)程想要去訪(fǎng)問(wèn)一個(gè)類(lèi)的同步方法的時(shí)候,線(xiàn)程B需要等待線(xiàn)程A執(zhí)行完了methodA()方法之后,它才能執(zhí)行methodB()方法。這樣,線(xiàn)程A和線(xiàn)程B就實(shí)現(xiàn)了通信。
wait/notify機(jī)制
使用object類(lèi)中的wait和notify方法能夠更高效率的進(jìn)行線(xiàn)程間通信,比第一種方法好的地方在于不用一直在程序中循環(huán)判斷條件,當(dāng)具備可執(zhí)行條件的時(shí)候,會(huì)由另一個(gè)線(xiàn)程發(fā)出通知來(lái)告訴你。這段時(shí)間你可以做自己有用的事情。
下面是一個(gè)代碼例子:
package com.Thread.Communication;
import java.util.ArrayList;
//使用wait notify 機(jī)制進(jìn)行線(xiàn)程間通信
public class T2 {
public static void main(String[] args) {
}
}
class Thread1 implements Runnable {
private ArrayList<Integer> list;
public Thread1(ArrayList<Integer> list) {
// TODO Auto-generated constructor stub
this.list = list;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (list) {
if (list.size() != 5) {
System.out.println("ready in to wait()");
try {
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("back to life");
}
System.out.println("execute after list size turn to 5");
}
}
}
class Thread3 implements Runnable {
private ArrayList<Integer> List;
public Thread3(ArrayList<Integer> list) {
// TODO Auto-generated constructor stub
this.List = list;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (List) {
for (int i = 0; i < 10; i++) {
List.add(i);
System.out.println("put element into list" + i);
if (List.size() == 5) {
System.out.println("ready to notify()");
List.notify();
System.out.println("notify done..");
}
}
}
}
}
class Client1 {
public static void main(String[] args) throws InterruptedException {
ArrayList<Integer> list = new ArrayList<>();
Thread1 thead1 = new Thread1(list);
Thread3 thread3 = new Thread3(list);
Thread tt1 = new Thread(thead1);
Thread tt2 = new Thread(thread3);
tt1.start();
Thread.sleep(1000);
tt2.start();
}
}
ready in to wait()
put element into list0
put element into list1
put element into list2
put element into list3
put element into list4
ready to notify()
notify done..
put element into list5
put element into list6
put element into list7
這個(gè)例子里面我們可以看到 Thread1 的功能是等list的數(shù)量大于5時(shí),才執(zhí)行相應(yīng)的自己的方法。而Thread2則是一直不斷的去改變list的大小,當(dāng)集合中的大小達(dá)到指定的數(shù)量,就通過(guò)notify方法去喚醒正在等待的線(xiàn)程。也就是Thread1,Thread1在一開(kāi)始就先去判斷size的大小,發(fā)現(xiàn)不符合則使用wait方法進(jìn)入阻塞。把cpu讓給Thread2去執(zhí)行。
Lock鎖
lock實(shí)際上和notify和wait的進(jìn)程通信情況很像,但是lock比wait notify更加高效和靈活。不需要同步synchronize塊,而且不同的方法之間也會(huì)因?yàn)閟ynchronize鎖導(dǎo)致沖突。
下面是一個(gè)代碼例子:
package com.Thread.Communication;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//使用Condition 來(lái)實(shí)現(xiàn)生產(chǎn)者消費(fèi)者
public class T5 {
public static Lock lock = new ReentrantLock();
public static Condition notFull = lock.newCondition();
public static Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
Storge2 storge2 = new Storge2(new ArrayList<>());
Consumer2 consumer2 = new Consumer2("李四", storge2);
Producer2 producer1 = new Producer2("王五", storge2);
new Thread(consumer2).start();
new Thread(producer1).start();
}
}
class Storge2 {
private ArrayList<Integer> list;
public Storge2(ArrayList<Integer> list) {
// TODO Auto-generated constructor stub
this.list = list;
}
public void put(int val) {
list.add(val);
}
public int take() {
return list.remove(0);
}
public int size() {
return list.size();
}
}
class Consumer2 implements Runnable {
private String name;
private Storge2 s;
public Consumer2(String name, Storge2 s) {
// TODO Auto-generated constructor stub
this.name = name;
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
T5.lock.lock();
try {
while (s.size() == 0) {
System.out.println(name + " no product notify producer to procude");
T5.notEmpty.await();
}
System.out.println(name + " i am ready consumer...");
int v = s.take();
T5.notFull.signal();
System.out.println(name + " consumer done " + v);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// TODO: handle finally clause
T5.lock.unlock();
}
}
}
}
class Producer2 implements Runnable {
private String name;
private Storge2 s;
public Producer2(String name, Storge2 s) {
// TODO Auto-generated constructor stub
this.name = name;
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
T5.lock.lock();
try {
while (s.size() == 5) {
System.out.println(name + " full can't produce notify to consumer");
T5.notFull.await();
}
int val = (int) (Math.random() * 1000);
System.out.println(name + " i am ready to produce id: " + val);
s.put(val);
T5.notEmpty.signal();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// TODO: handle finally clause
T5.lock.unlock();
}
}
}
}
王五 i am ready to produce id: 989
王五 i am ready to produce id: 546
王五 i am ready to produce id: 528
王五 i am ready to produce id: 559
王五 full can't produce notify to consumer
李四 i am ready consumer...
李四 consumer done 279
李四 i am ready consumer...
李四 consumer done 989
李四 i am ready consumer...
李四 consumer done 546
李四 i am ready consumer...
李四 consumer done 528
這個(gè)例子也是下面的生產(chǎn)和消費(fèi)者會(huì)用到的例子,我們看到,當(dāng)產(chǎn)品生成滿(mǎn)了之后就會(huì)發(fā)出通知去通知消費(fèi)者進(jìn)行消費(fèi),消費(fèi)產(chǎn)品到空時(shí)就會(huì)去通知生產(chǎn)者繼續(xù)生產(chǎn)。
生產(chǎn)者-消費(fèi)者模式 有哪些實(shí)現(xiàn)方法 給出例子
wait()/notify()機(jī)制
package com.Thread.Communication;
import java.util.ArrayList;
//使用notify wait 實(shí)現(xiàn)生產(chǎn)者 消費(fèi)者
public class T4 {
public static void main(String[] args) {
Storge1 storge = new Storge1(new ArrayList<>());
Consumer1 consumer1 = new Consumer1("張三", storge);
Consumer1 consumer2 = new Consumer1("李四", storge);
Producer1 producer1 = new Producer1("王五", storge);
Producer1 producer2 = new Producer1("孫六", storge);
new Thread(consumer2).start();
new Thread(producer1).start();
}
}
class Storge1 {
private ArrayList<Integer> list;
public Storge1(ArrayList<Integer> list) {
// TODO Auto-generated constructor stub
this.list = list;
}
public void put(int val) {
list.add(val);
}
public int take() {
return list.remove(0);
}
public int size() {
return list.size();
}
}
class Consumer1 implements Runnable {
private String name;
private Storge1 s;
public Consumer1(String name, Storge1 s) {
// TODO Auto-generated constructor stub
this.name = name;
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (s) {
while (s.size() == 0) {
System.out.println(name + " no product notify producer to procude");
try {
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(name + " i am ready consumer...");
int v = s.take();
s.notifyAll();
System.out.println(name + " consumer done " + v);
}
}
}
}
class Producer1 implements Runnable {
private String name;
private Storge1 s;
public Producer1(String name, Storge1 s) {
// TODO Auto-generated constructor stub
this.name = name;
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (s) {
while (s.size() == 5) {
System.out.println(name + " full can't produce notify to consumer");
try {
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
int val = (int) (Math.random() * 1000);
System.out.println(name + " i am ready to produce id: " + val);
s.put(val);
s.notifyAll();
}
}
}
}
通過(guò)這兩個(gè)方法去在適當(dāng)?shù)臅r(shí)候告訴生產(chǎn)者或者消費(fèi)者該做什么事情了。
BlockingQueue實(shí)現(xiàn)
同步包下的阻塞隊(duì)列是一個(gè)可以實(shí)現(xiàn)同步功能的隊(duì)列,它的特點(diǎn)是在加入和刪除操作中加入了鎖機(jī)制,其實(shí)底層原理也是使用ReentrantLock 和Condition來(lái)實(shí)現(xiàn)的。當(dāng)隊(duì)列滿(mǎn)時(shí),執(zhí)行加入操作會(huì)阻塞。并喚醒擁有刪除操作Condition的線(xiàn)程去執(zhí)行。當(dāng)隊(duì)列空時(shí),執(zhí)行刪除操作會(huì)阻塞。并喚醒擁有加入操作的線(xiàn)程Condition去執(zhí)行。
下面就是代碼例子:
package com.Thread.Communication;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//使用BlockingQueue來(lái)實(shí)現(xiàn)生產(chǎn)者消費(fèi)者
public class T3 {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);
Storge storge = new Storge(queue);
Consumer consumer1 = new Consumer("張三", storge);
Consumer consumer2 = new Consumer("李四", storge);
Producer producer1 = new Producer("王五", storge);
Producer producer2 = new Producer("孫六", storge);
ExecutorService service = Executors.newCachedThreadPool();
service.submit(producer2);
service.submit(producer1);
service.submit(consumer1);
service.submit(consumer2);
service.shutdown();
}
}
class Storge {
private BlockingQueue<Integer> queue;
public Storge(BlockingQueue<Integer> queue) {
// TODO Auto-generated constructor stub
this.queue = queue;
}
public void queue_put(int val) throws InterruptedException {
queue.put(val);
}
public int queue_take() throws InterruptedException {
return queue.take();
}
}
class Consumer implements Runnable {
private String name;
private Storge s;
public Consumer(String name, Storge s) {
// TODO Auto-generated constructor stub
this.name = name;
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
// while (true) {
System.out.println(name + " i am ready consumer...");
try {
int val = s.queue_take();
System.out.println(name + " i consumer product id : " + val);
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// }
}
}
class Producer implements Runnable {
private String name;
private Storge s;
public Producer(String name, Storge s) {
// TODO Auto-generated constructor stub
this.name = name;
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
// while (true) {
int val = (int) (Math.random() * 1000);
System.out.println(name + " i am ready to produce id: " + val);
try {
s.queue_put(val);
// System.out.println(name + " produce done id: " + val);
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// }
}
}
張三 i am ready consumer...
李四 i am ready consumer...
王五 i am ready to produce id: 778
孫六 i am ready to produce id: 930
張三 i consumer product id : 778
李四 i consumer product id : 930
使用阻塞隊(duì)列很容易就實(shí)現(xiàn)了生產(chǎn)者消費(fèi)者模式。不需要再單獨(dú)考慮同步和線(xiàn)程間通信的問(wèn)題;
在并發(fā)編程中,一般推薦使用阻塞隊(duì)列,這樣實(shí)現(xiàn)可以盡量地避免程序出現(xiàn)意外的錯(cuò)誤。
阻塞隊(duì)列使用最經(jīng)典的場(chǎng)景就是socket客戶(hù)端數(shù)據(jù)的讀取和解析,讀取數(shù)據(jù)的線(xiàn)程不斷將數(shù)據(jù)放入隊(duì)列,然后解析線(xiàn)程不斷從隊(duì)列取數(shù)據(jù)解析。還有其他類(lèi)似的場(chǎng)景,只要符合生產(chǎn)者-消費(fèi)者模型的都可以使用阻塞隊(duì)列。
Condition實(shí)現(xiàn)
這個(gè)和上面的阻塞隊(duì)列有異曲同工之處
下面是代碼例子:
package com.Thread.Communication;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//使用Condition 來(lái)實(shí)現(xiàn)生產(chǎn)者消費(fèi)者
public class T5 {
public static Lock lock = new ReentrantLock();
public static Condition notFull = lock.newCondition();
public static Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
Storge2 storge2 = new Storge2(new ArrayList<>());
Consumer2 consumer2 = new Consumer2("李四", storge2);
Producer2 producer1 = new Producer2("王五", storge2);
new Thread(consumer2).start();
new Thread(producer1).start();
}
}
class Storge2 {
private ArrayList<Integer> list;
public Storge2(ArrayList<Integer> list) {
// TODO Auto-generated constructor stub
this.list = list;
}
public void put(int val) {
list.add(val);
}
public int take() {
return list.remove(0);
}
public int size() {
return list.size();
}
}
class Consumer2 implements Runnable {
private String name;
private Storge2 s;
public Consumer2(String name, Storge2 s) {
// TODO Auto-generated constructor stub
this.name = name;
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
T5.lock.lock();
try {
while (s.size() == 0) {
System.out.println(name + " no product notify producer to procude");
T5.notEmpty.await();
}
System.out.println(name + " i am ready consumer...");
int v = s.take();
T5.notFull.signal();
System.out.println(name + " consumer done " + v);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// TODO: handle finally clause
T5.lock.unlock();
}
}
}
}
class Producer2 implements Runnable {
private String name;
private Storge2 s;
public Producer2(String name, Storge2 s) {
// TODO Auto-generated constructor stub
this.name = name;
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
T5.lock.lock();
try {
while (s.size() == 5) {
System.out.println(name + " full can't produce notify to consumer");
T5.notFull.await();
}
int val = (int) (Math.random() * 1000);
System.out.println(name + " i am ready to produce id: " + val);
s.put(val);
T5.notEmpty.signal();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// TODO: handle finally clause
T5.lock.unlock();
}
}
}
}
王五 i am ready to produce id: 632
王五 i am ready to produce id: 93
王五 i am ready to produce id: 479
王五 i am ready to produce id: 874
王五 full can't produce notify to consumer
李四 i am ready consumer...
李四 consumer done 325
李四 i am ready consumer...
李四 consumer done 632
李四 i am ready consumer...
李四 consumer done 93
李四 i am ready consumer...
李四 consumer done 479
李四 i am ready consumer...
李四 consumer done 874
李四 no product notify producer to procude
synchronize和lock區(qū)別
1)Lock是一個(gè)接口,而synchronized是Java中的關(guān)鍵字,synchronized是內(nèi)置的語(yǔ)言實(shí)現(xiàn);
2)synchronized在發(fā)生異常時(shí),會(huì)自動(dòng)釋放線(xiàn)程占有的鎖,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時(shí),如果沒(méi)有主動(dòng)通過(guò)unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用Lock時(shí)需要在finally塊中釋放鎖;
3)Lock可以讓等待鎖的線(xiàn)程響應(yīng)中斷,而synchronized卻不行,使用synchronized時(shí),等待的線(xiàn)程會(huì)一直等待下去,不能夠響應(yīng)中斷;
4)通過(guò)Lock可以知道有沒(méi)有成功獲取鎖,而synchronized卻無(wú)法辦到。
5)Lock可以提高多個(gè)線(xiàn)程進(jìn)行讀操作的效率。
阻塞隊(duì)列介紹
ArrayBlockingQueue:基于數(shù)組實(shí)現(xiàn)的一個(gè)阻塞隊(duì)列,在創(chuàng)建ArrayBlockingQueue對(duì)象時(shí)必須制定容量大小。并且可以指定公平性與非公平性,默認(rèn)情況下為非公平的,即不保證等待時(shí)間最長(zhǎng)的隊(duì)列最優(yōu)先能夠訪(fǎng)問(wèn)隊(duì)列。
LinkedBlockingQueue:基于鏈表實(shí)現(xiàn)的一個(gè)阻塞隊(duì)列,在創(chuàng)建LinkedBlockingQueue對(duì)象時(shí)如果不指定容量大小,則默認(rèn)大小為Integer.MAX_VALUE。
PriorityBlockingQueue:以上2種隊(duì)列都是先進(jìn)先出隊(duì)列,而PriorityBlockingQueue卻不是,它會(huì)按照元素的優(yōu)先級(jí)對(duì)元素進(jìn)行排序,按照優(yōu)先級(jí)順序出隊(duì),每次出隊(duì)的元素都是優(yōu)先級(jí)最高的元素。注意,此阻塞隊(duì)列為無(wú)界阻塞隊(duì)列,即容量沒(méi)有上限(通過(guò)源碼就可以知道,它沒(méi)有容器滿(mǎn)的信號(hào)標(biāo)志),前面2種都是有界隊(duì)列。
鎖的相關(guān)概念介紹
可重入鎖
基于線(xiàn)程的分配,而不是基于方法調(diào)用的分配。
舉一個(gè)例子來(lái)說(shuō)明:
class MyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
上述代碼中的兩個(gè)方法method1和method2都用synchronized修飾了,假如某一時(shí)刻,線(xiàn)程A執(zhí)行到了method1,此時(shí)線(xiàn)程A獲取了這個(gè)對(duì)象的鎖,而由于method2也是synchronized方法,假如synchronized不具備可重入性,此時(shí)線(xiàn)程A需要重新申請(qǐng)鎖。但是這就會(huì)造成一個(gè)問(wèn)題,因?yàn)榫€(xiàn)程A已經(jīng)持有了該對(duì)象的鎖,而又在申請(qǐng)獲取該對(duì)象的鎖,這樣就會(huì)線(xiàn)程A一直等待永遠(yuǎn)不會(huì)獲取到的鎖。
而由于synchronized和Lock都具備可重入性,所以不會(huì)發(fā)生上述現(xiàn)象。
可中斷鎖
可中斷鎖:顧名思義,就是可以相應(yīng)中斷的鎖。
在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
注意,當(dāng)一個(gè)線(xiàn)程獲取了鎖之后,是不會(huì)被interrupt()方法中斷的。只能中斷阻塞過(guò)程中的線(xiàn)程。
因此當(dāng)通過(guò)lockInterruptibly()方法獲取某個(gè)鎖時(shí),如果不能獲取到,只有進(jìn)行等待的情況下,是可以響應(yīng)中斷的。
而用synchronized修飾的話(huà),當(dāng)一個(gè)線(xiàn)程處于等待某個(gè)鎖的狀態(tài),是無(wú)法被中斷的,只有一直等待下去。
如果某一線(xiàn)程A正在執(zhí)行鎖中的代碼,另一線(xiàn)程B正在等待獲取該鎖,可能由于等待時(shí)間過(guò)長(zhǎng),線(xiàn)程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線(xiàn)程中中斷它,這種就是可中斷鎖。
下面看個(gè)例子:
package com.Thread.Communication;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T6 {
public static Lock lock = new ReentrantLock();
public static void main(String[] args) {
Resorce resorce = new Resorce();
MyThread thread = new MyThread(resorce);
MyThread thread2 = new MyThread(resorce);
thread.start();
try {
Thread.sleep(2000);
} catch (Exception e) {
// TODO: handle exception
}
thread2.start();
try {
System.out.println("等待2秒 如果拿不到鎖我就自己中斷");
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
thread2.interrupt();
}
}
class Resorce {
public void dosomething() throws InterruptedException {
T6.lock.lockInterruptibly();// 注意,如果需要正確中斷等待鎖的線(xiàn)程,必須將獲取鎖放在外面,然后將InterruptedException拋出
try {
// T6.lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + " get the lock");
System.out.println("exe 5seconds..");
long startTime = System.currentTimeMillis();
Thread.sleep(10000);
System.out.println("exe done...");
} finally {
System.out.println("我準(zhǔn)備釋放鎖");
T6.lock.unlock();
System.out.println(Thread.currentThread().getName() + " release lock");
}
}
}
class MyThread extends Thread {
private Resorce r;
public MyThread(Resorce r) {
// TODO Auto-generated constructor stub
this.r = r;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
r.dosomething();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println(Thread.currentThread().getName() + " 收到中斷信號(hào)。");
}
}
}
這里需要非常注意的一點(diǎn)是 在注釋中說(shuō)的 如果要正確中斷等待鎖的線(xiàn)程,必須將鎖放在try外面 然后方法要拋出InterruptedException異常。
為什么要這樣做?
因?yàn)槿绻旁趖ry語(yǔ)句塊里面。你會(huì)發(fā)現(xiàn)當(dāng)lockInterruptibly()方法要拋出異常時(shí),無(wú)論是catch還是throws。都會(huì)進(jìn)入到finally語(yǔ)句塊中去執(zhí)行,此時(shí)finally語(yǔ)句塊里面的unlock方法就會(huì)報(bào)錯(cuò)java.lang.IllegalMonitorStateException,原因就是線(xiàn)程并沒(méi)有獲取到鎖,而你卻要去釋放鎖。如果放在try語(yǔ)句塊外面 ,那么它馬上就會(huì)拋出異常,而不進(jìn)入try語(yǔ)句塊中去。
出錯(cuò)時(shí)的輸出
Thread-0 get the lock
exe 5seconds..
等待2秒 如果拿不到鎖我就自己中斷
我準(zhǔn)備釋放鎖
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
at com.Thread.Communication.Resorce.dosomething(T6.java:48)
at com.Thread.Communication.MyThread.run(T6.java:67)
exe done...
我準(zhǔn)備釋放鎖
Thread-0 release lock
正確輸出
Thread-0 get the lock
exe 5seconds..
等待2秒 如果拿不到鎖我就自己中斷
Thread-1 收到中斷信號(hào)。
exe done...
我準(zhǔn)備釋放鎖
Thread-0 release lock
公平、非公平鎖
公平鎖即盡量以請(qǐng)求鎖的順序來(lái)獲取鎖。比如同是有多個(gè)線(xiàn)程在等待一個(gè)鎖,當(dāng)這個(gè)鎖被釋放時(shí),等待時(shí)間最久的線(xiàn)程(最先請(qǐng)求的線(xiàn)程)會(huì)獲得該所,這種就是公平鎖。
非公平鎖即無(wú)法保證鎖的獲取是按照請(qǐng)求鎖的順序進(jìn)行的。這樣就可能導(dǎo)致某個(gè)或者一些線(xiàn)程永遠(yuǎn)獲取不到鎖。
在Java中,synchronized就是非公平鎖,它無(wú)法保證等待的線(xiàn)程獲取鎖的順序。
而對(duì)于ReentrantLock和ReentrantReadWriteLock,它默認(rèn)情況下是非公平鎖,但是可以設(shè)置為公平鎖。
讀寫(xiě)鎖
讀寫(xiě)鎖將對(duì)一個(gè)資源(比如文件)的訪(fǎng)問(wèn)分成了2個(gè)鎖,一個(gè)讀鎖和一個(gè)寫(xiě)鎖。ReadWriteLock就是讀寫(xiě)鎖,它是一個(gè)接口,ReentrantReadWriteLock實(shí)現(xiàn)了這個(gè)接口。
可以通過(guò)readLock()獲取讀鎖,通過(guò)writeLock()獲取寫(xiě)鎖。
下面的例子會(huì)說(shuō)。
java.util.concurrent.locks包下常用的類(lèi)
Lock
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
lock():獲取鎖,如果鎖已被其他線(xiàn)程獲取,則進(jìn)行等待
unlock():釋放鎖 記得要在finally中釋放 不手動(dòng)釋放會(huì)死鎖
tryLock()方法是有返回值的,它表示用來(lái)嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失?。存i已被其他線(xiàn)程獲取),則返回false,也就說(shuō)這個(gè)方法無(wú)論如何都會(huì)立即返回。在拿不到鎖時(shí)不會(huì)一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是類(lèi)似的,只不過(guò)區(qū)別在于這個(gè)方法在拿不到鎖時(shí)會(huì)等待一定的時(shí)間,在時(shí)間期限之內(nèi)如果還拿不到鎖,就返回false。如果如果一開(kāi)始拿到鎖或者在等待期間內(nèi)拿到了鎖,則返回true。
ReentrantLock
ReentrantLock,意思是“可重入鎖”。ReentrantLock是唯一實(shí)現(xiàn)了Lock接口的類(lèi),在上面的例子中我們經(jīng)??吹竭@個(gè)鎖的身影。
ReadWriteLock
ReadWriteLock也是一個(gè)接口,在它里面只定義了兩個(gè)方法:
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();
}
一個(gè)用來(lái)獲取讀鎖,一個(gè)用來(lái)獲取寫(xiě)鎖。也就是說(shuō)將文件的讀寫(xiě)操作分開(kāi),分成2個(gè)鎖來(lái)分配給線(xiàn)程,從而使得多個(gè)線(xiàn)程可以同時(shí)進(jìn)行讀操作。下面的ReentrantReadWriteLock實(shí)現(xiàn)了ReadWriteLock接口。
ReentrantReadWriteLock
ReentrantReadWriteLock里面提供了很多豐富的方法,不過(guò)最主要的有兩個(gè)方法:readLock()和writeLock()用來(lái)獲取讀鎖和寫(xiě)鎖。
下面是一個(gè)讀寫(xiě)鎖的使用例子:
public class Test {
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
final Test test = new Test();
new Thread(){
public void run() {
test.get(Thread.currentThread());
};
}.start();
new Thread(){
public void run() {
test.get(Thread.currentThread());
};
}.start();
}
public void get(Thread thread) {
rwl.readLock().lock();
try {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在進(jìn)行讀操作");
}
System.out.println(thread.getName()+"讀操作完畢");
} finally {
rwl.readLock().unlock();
}
}
}
Thread-0正在進(jìn)行讀操作
Thread-0正在進(jìn)行讀操作
Thread-1正在進(jìn)行讀操作
Thread-0正在進(jìn)行讀操作
Thread-1正在進(jìn)行讀操作
Thread-0正在進(jìn)行讀操作
Thread-1正在進(jìn)行讀操作
Thread-1正在進(jìn)行讀操作
發(fā)現(xiàn)多個(gè)線(xiàn)程同步進(jìn)行讀操作,而如果是synchronize的話(huà)則只能一個(gè)線(xiàn)程讀,直到它釋放鎖為止。
如果一個(gè)線(xiàn)程占用讀鎖,另一個(gè)線(xiàn)程申請(qǐng)寫(xiě)鎖,則申請(qǐng)寫(xiě)鎖的線(xiàn)程會(huì)一直等待釋放讀鎖。
如果一個(gè)線(xiàn)程占用寫(xiě)鎖,另一個(gè)線(xiàn)程申請(qǐng)讀鎖或者寫(xiě)鎖,則這個(gè)線(xiàn)程會(huì)一直等待釋放寫(xiě)鎖
死鎖產(chǎn)生的原因和四個(gè)必要條件,預(yù)防死鎖的經(jīng)典算法
四個(gè)必要條件
(1) 互斥條件:一個(gè)資源每次只能被一個(gè)進(jìn)程使用。
(2) 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。
(3) 不剝奪條件:進(jìn)程已獲得的資源,在末使用完之前,不能強(qiáng)行剝奪。
(4) 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
這四個(gè)條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立,而只要上述條件之
一不滿(mǎn)足,就不會(huì)發(fā)生死鎖。
產(chǎn)生死鎖的原因主要是:
(1) 因?yàn)橄到y(tǒng)資源不足。
(2) 進(jìn)程運(yùn)行推進(jìn)的順序不合適。
(3) 資源分配不當(dāng)?shù)取?/p>
預(yù)防死鎖算法
銀行家算法
銀行家算法的基本思想是分配資源之前,判斷系統(tǒng)是否是安全的;若是,才分配。它是最具有代表性的避免死鎖的算法。
設(shè)進(jìn)程i提出請(qǐng)求Request[j],則銀行家算法按如下規(guī)則進(jìn)行判斷。
(1) 如果Request[j]≤Need[i,j],則轉(zhuǎn)向(2),否則認(rèn)為出錯(cuò),因?yàn)樗枰馁Y源數(shù)已超過(guò)它所宣布的最大值。
(2) 如果Request[j]≤Available[j],則轉(zhuǎn)向(3);否則表示尚無(wú)足夠資源,Pi需等待。
(3) 假設(shè)進(jìn)程i的申請(qǐng)已獲批準(zhǔn),于是修改系統(tǒng)狀態(tài):
Available[j]=Available[j]-Request[i]
Allocation[i,j]=Allocation[i,j]+Request[j]
Need[i,j]=Need[i,j]-Request[j]
(4)系統(tǒng)執(zhí)行安全性檢查,如安全,則分配成立;否則試探險(xiǎn)性分配作廢,系統(tǒng)恢復(fù)原狀,進(jìn)程等待。
安全性檢查算法
(1) 設(shè)置兩個(gè)工作向量Work=Available;Finish[i]=False
(2) 從進(jìn)程集合中找到一個(gè)滿(mǎn)足下述條件的進(jìn)程,
Finish [i]=False;
Need[i,j]≤Work[j];
如找到,執(zhí)行(3);否則,執(zhí)行(4)
(3) 設(shè)進(jìn)程獲得資源,可順利執(zhí)行,直至完成,從而釋放資源。
Work[j]=Work[j]+Allocation[i,j];
Finish[i]=True;
go to step 2;
(4) 如所有的進(jìn)程Finish[i]=true,則表示安全;否則系統(tǒng)不安全。
參考文章:
JAVA多線(xiàn)程之線(xiàn)程間的通信方式
Java多線(xiàn)程-并發(fā)協(xié)作(生產(chǎn)者消費(fèi)者模型)
Java并發(fā)編程:線(xiàn)程間協(xié)作的兩種方式:wait、notify、notifyAll和Condition
Java并發(fā)編程:阻塞隊(duì)列
Java并發(fā)編程:Lock
死鎖的定義、產(chǎn)生原因、必要條件、避免死鎖和解除死鎖的方法