线程的同步通信是一种避免死锁,让线程在进入阻塞状态时尽量释放其锁定的资源,以便为其他的线程提供运行的机会的有效解决措施。
所谓的的同步通信,就和线程的同步差不多,因为二者在操作的资源上都是那种临界资源,只不过线程的同步和线程的同步通信二者的执行动作(即 run()方法中存放的线程处理代码)不同。
要实现同步通信,就要用到Object类中定义的几个方法:wait()、notify()、notifyAll()。
wait():被锁定的对象可以调用wait()方法,这将导致当前线程被阻塞并释放该对象的互斥锁,即解除了wait()方法当前对象的锁定状态,其他的线程就有机会访问该对象。
notify():唤醒调用wait()方法后被阻塞的线程。每次运行该方法只能唤醒一个线程。
notifyAll():唤醒所有调用wait()方法被阻塞的线程。
线程同步通信的构架:
class Res{
}
class Input implements Runnable{
publicvoid run() {
synchronized (对象锁) {
try {
wait();//调用等待方法锁住线程
} catch(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
notify();//调用此方法唤醒线程,解锁、释放资源
}
}
}
}
class Output implements Runnable{
publicvoid run() {
synchronized (线程所对象) {
try {
r.wait();//调用等待方法锁住线程
} catch(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
notify();//调用此方法唤醒线程,解锁、释放资源
}
}
}
publicclass TestInfor {
publicstaticvoid main(String[] args) {
///开启线程
Res r =new Res();
Inputinput = new Input();
Output output = new Output();
Thread t1 = newThread(input);
Thread t2 = newThread(output);
t1.start();
t2.start();
}
}
这样就实现了线程的同步通信
再看一实例:
生产者-消费者问题是多线程同步处理的典型问题
原理:有一块生产者和消费者共享的有界缓冲区,生产者往缓冲区放入产品,消费者从缓冲区取走产品。并且生产者和消费者生产出产品后才通知消费者消费,否则,消费者只能等待生产者生产出来后在消费。
我们来看一个例子的出现的几种不同状况,从而也加深一下我们对于同步通信构架来历的理解。
packagecsdn.java.thread;
class Res{
String name;
charsex;
}
class Input implements Runnable{
Res r;
public Input(Res r){
this.r=r;
}
@Override
publicvoid run() {
int i =0;
while(true){
if(i==0){
r.name = "张三";
r.sex ='男';
}else {
r.name ="李四";
r.sex ='女';
}
r.flag =true;
i =(i+1)%2;
}
}
}
class Output implements Runnable{
Res r;
public Output(Res r){
this.r = r;
}
@Override
publicvoid run() {
while(true){
System.out.println(r.name +"..."+r.sex);
r.flag =false;
}
}
}
publicclass TestInfor {
publicstaticvoid main(String[] args) {
Res r =new Res();
Inputinput = new Input(r);
Output output = new Output(r);
Thread t1 = newThread(input);
Thread t2 = newThread(output);
t1.start();
t2.start();
}
}
最后的输出结果为:
张三...男
李四...男
张三...女
李四...女
李四...女
张三...女
李四...男
看这种结果我们知道张三和李四的性别显然不是我们所期待的这种性别的点到输出,是由于线程的不稳定性造成的,某一线程在它的时间权限内不停的改变着临界资源的值,但是当某一刻权限失去时它并不是改变了所有的值而是一部分,这就是出现此类问题的原因。
要解决它,很简单,我们只需在不同的线程上添加上同步代码块或同步函数即可。
如:
即只需要将两个runf()方法中加上同步代码块就OK了
publicvoid run() {
int i =0;
while(true){
//使用同步代码块为线程加锁
synchronized (r) {
if(i==0){
r.name = "张三";
r.sex ='男';
}else {
r.name ="李四";
r.sex ='女';
}
r.flag =true;
}
i =(i+1)%2;
}
}
publicvoid run() {
while(true){
//使用同步代码块为线程加锁
synchronized (r) {
System.out.println(r.name +"..."+r.sex);
r.flag =false;
}
}
}
像这样就解决了我们之前的那种性别或者姓名颠倒的情况,现在的运行结果为:
张三...男
张三...男
张三...男
张三...男
李四...女
李四...女
李四...女
李四...女
可是,这也不是我们所预期的那种效果,我们希望张三和李四交替着出现,可这又怎么实现呢?
其实,这就用到我们今天将到线程间的同步通信。
我们只需在两个run()方法中合适的地方添加上相应的wait()和notify()方法即可
在这里,我们
publicvoid run() {
int i =0;
while(true){
synchronized (r) {
if(r.flag){
try {
r.wait();//wait()方法锁定线程
} catch(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(i==0){
r.name = "张三";
r.sex ='男';
}else {
r.name ="李四";
r.sex ='女';
}
r.flag =true;
r.notify();//notify()方法唤醒线程,解除锁,释放CPU 资源
}
i =(i+1)%2;
}
}
publicvoid run() {
while(true){
synchronized (r) {
if(!r.flag){
try {
r.wait();//wait()方法锁定线程
} catch(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(r.name +"..."+r.sex);
r.flag =false;
r.notify();//notify()方法唤醒线程,解除锁,释放CPU 资源
}
}
}
此时我们的运行结果为:
张三...男
李四...女
张三...男
李四...女
张三...男
李四...女
张三...男
李四...女
...
最终通过线程间的同步通信达到了按时交替操作的效果。这样也能避免因缓冲区已满而生产者却不停止产品的生产,也不能因缓冲区空消费者却继续消费了,这二者只能同时进行。
这样也就简单的介绍了关于线程间的同步通信。