之前的售票程序或者银行取钱例子中,所有线程执行的代码都是一样的。
线程间通信:A线程往里存,B线程往外取。
AB线程的动作不一致,也就需要有两个run方法。两个run方法就要存在两个线程当中。什么时候可以取,什么时候可以存,这就需要线程之间的通信。
例子:一个写入线程负责写,读取线程负责读,两者操作的是同一个资源,但是操作的动作是不同的。这时就要进行线程间通信。
package fighting;
public class ThreadCommunication {
/**
* 线程间通讯:解决安全问题(要用同一个锁)
* 其实就是多个线程在操作同一个资源,但是操作的动作不同。这样就需要通讯。
*
* 同步以后也会发生一种情况:
* 就是输入线程往里存了一个,输出线程可能会执行了多次,取了多次,
* 这样输出线程就会取到多次相同的值。这需要等待唤醒机制来解决。
* 等待唤醒机制:就是需要一个资源的flag。
*
* wait()和notify(),notifyAll()
* 这几个方法都是用在同步中,要对持有监视器(也就是锁)的线程操作。
*
* 为什么线程用到的方法wait()和notify(),notifyAll()会定义在Oject类中,而不是定义在Thread类中?
* 因为这些方法在操作同步的线程时,都必须要标识它们所操作线程持有的锁。
* 只有同一个锁上的被等待线程,可以被同一个锁上nofify唤醒。
* 不可以对不同锁中的线程唤醒。
* 也就是说,等待和唤醒必须是同一个锁。
* 而锁可以使任意对象,所以可以被任意对象调用的方法要定义在Oject类中。
*
*/
public static void main(String[] args) {
Resource r = new Resource();
InputThread in = new InputThread(r);
OutputThread out = new OutputThread(r);
new Thread(in).start();
new Thread(out).start();
}
}
//线程要操作的资源
class Resource{
String name;
String sex;
boolean flag = false;//等待唤醒机制的标志位
}
//输入的线程
class InputThread implements Runnable{
private Resource r;
InputThread(Resource r){
this.r=r;
}
public void run() {//同步synchronize写在run方法,就变成了单线程了,一个线程进来,其他线程就进不来了
boolean b=true;
int i=0;
while(true){
/**
* 这里同步锁用了r,是因为InputThread和OutputThread两个线程操作的都是一个r。
* 这里也可以用InputThread.class或OutputThread.class或Resource.class,
* 只要在内不能中是唯一的就可以,但是这样显得牵强。
*/
synchronized (r) {
if(r.flag){//flag为True,说明已经有资源,不用再存,线程等待
try {
r.wait();//知识点:等待的线程存在内存线程池中,notify唤醒的都是线程池中的线程。
//注意:wait和notify方法必须标明是哪个锁,而且这个锁要和同步的锁是一个才可以。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(b){
r.name="张三"+(i++);
r.sex="男";
b=false;
}else{
r.name="李四"+(i++);
r.sex="女";
b=true;
}
r.flag=true;
r.notify();//唤醒等待的一个线程。还有一个方法notifyAll,唤醒所有等待的线程
}
}
}
}
//取走的线程
class OutputThread implements Runnable{
private Resource r;
OutputThread(Resource r){
this.r=r;
}
public void run() {
while(true){
synchronized (r) {
if(!r.flag){//flag为false,说明没有资源,不能取,线程等待
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(r.name+"..."+r.sex);
r.flag=false;
r.notify();
}
}
}
}
代码优化:把资源类Resource的成员私有化。
package fighting;
public class ThreadCommunication {
/**
* 代码优化:把资源私有化
*/
public static void main(String[] args) {
Resource r = new Resource();
InputThread in = new InputThread(r);
OutputThread out = new OutputThread(r);
new Thread(in).start();
new Thread(out).start();
}
}
//线程要操作的资源
class Resource{
private String name;
private String sex;
private boolean flag = false;//等待唤醒机制的标志位
public synchronized void set(String name,String sex){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name=name;
//--->如果线程在执行完上一句,在这里被其他线程抢走了执行权,则这里会出现安全问题,所以这个方法需要同步
this.sex=sex;
flag=true;
this.notify();//有资源了,将其他线程唤醒,通知可以取资源了
}
public synchronized void out(){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name+"..."+sex);
flag=false;
this.notify();//这句唤醒要写在资源里面,如果线程里面,
//就会报错IllegalMonitorStateException(意思是当前线程不是此对象监视器的所有者)。
}
}
//输入的线程
class InputThread implements Runnable{
private Resource r;
InputThread(Resource r){
this.r=r;
}
public void run() {//同步synchronize写在run方法,就变成了单线程了,一个线程进来,其他线程就进不来了
boolean b=true;
int i=0;
while(true){
if(b){
r.set("张三"+(i++), "男");
b=false;
}else{
r.set("李四"+(i++), "女");
b=true;
}
// r.notify();//这句唤醒如果这里,就会报错IllegalMonitorStateException(意思是当前线程不是此对象监视器的所有者),
//因为同步是在资源类的方法上实现的,程序执行到这的时候,已经从同步方法里出来了。
//当前线程是InputThread这个线程,同步锁的所有者是Resource,
}
}
}
//取走的线程
class OutputThread implements Runnable{
private Resource r;
OutputThread(Resource r){
this.r=r;
}
public void run() {
while(true){
r.out();
// r.notify();
}
}
}