线程之间的通信
在之前学习的线程的操作中,开启的多个线程执行的都是一样的操作,也就是说执行同一个run方法中的内容,但是,如果需要开启两个线程用不同的操作方法操作同一个数据呢?(a线程存数据,b线程取数据)具体需求如下图:
按照需求先写代码出来:
/*
需求,一个数据区用来存放姓名了年龄
开启一个线程负责输入数据
开启一个线程负责打印刚刚输入的数据
步骤,先把这三个事物进行描述,因为两个线程操作的方式不一样,两个线程要单独进行描述
*/
class Regs
{
String name;
String sex;
}
class Input implements Runnable
{
private Regs r;//因为要操作的是同一个数据,所以这里不能直接new Regs的对象,需要建立一个该类的引用
//也可以使用单例设计模式
int x =0;//这是一个标号,通过每次赋值后%2的运算进行男女姓名交替的赋值。
Input(Regs r)//确保两个线程操作的是同一个数据源,采用引用对象的方式,
{
this.r = r;
}
public void run()
{
while (true)
{
if (x==0)
{
r.name = "mike";
r.sex = "man";
}
else
{
r.name = "丽丽";
r.sex = "女女女女女女";
}
x = (x+1)%2;
}
}
}
class Output implements Runnable
{
private Regs r;
Output(Regs r)
{
this.r = r;
}
public void run()
{
while (true)
{
System.out.println(r.name+"::"+r.sex);
}
}
}
public class InputOutputDemo {
public static void main(String[] args)
{
Regs r = new Regs();//先来一个存储数据的对象
Input in = new Input(r);//创建存数据的对象
Output out = new Output(r);//这是打印数据的对象
Thread t1 = new Thread(in);//创建线程
Thread t2 = new Thread(out);
t1.start();//开启线程
t2.start();
}
}
实验运行结果:
由实验结果可以看出来:出现妖了。
这是为什么呢?
因为a线程存数据,b线程取数据,两个线程是同时进行的,举例:a线程将mike man存进去,这个时候b线程执行(此时a线程也在执行),打印出来了mike,当打印sex值得时候,a线程已经将里边存的数据变成了女女女女女,所以b线程获取到的sex的值就是女女女女,出现了mike 女的这种情况。
怎么解决这样的问题呢?
当然是同步了,用同步代码块,
但是这里有两个线程在运行,怎样给两个线程操作添加同一个锁呢?
将同步中的参数对象都给成同一个就行了。
发现该程序中有一个现成的对象: r
所以对程序进行了以下修改:
class Regs
{
String name;
String sex;
}
class Input implements Runnable
{
private Regs r;
int x =0;
Input(Regs r)
{
this.r = r;
}
public void run()
{
while (true)
{
synchronized(r)
{
if (x==0)
{
r.name = "mike";
r.sex = "man";
}
else
{
r.name = "丽丽";
r.sex = "女女女女女女";
}
x = (x+1)%2;
}
}
}
}
class Output implements Runnable
{
private Regs r;
Output(Regs r)
{
this.r = r;
}
public void run()
{
while (true)
{
synchronized(r)
{
System.out.println(r.name+"::"+r.sex);
}
}
}
}
public class InputOutputDemo {
public static void main(String[] args)
{
Regs r = new Regs();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
实验结果:出现了和谐,初步实现线程安全
然而这还不是我们想要的实验结果:我们需要的具体细节是,a线程存一个数据,b线程取一个数据。这样的交替执行。所以这里就要用到等待唤醒机制
等待唤醒机制其实就是当一个线程运行到一定程度时,需要等wait 等其他线程执行完需要的代码之后,再回来把你唤醒notify
具体放到代码中是这样运用的。
class Regs
{
String name;
String sex;
boolean flag = false;//在这里添加一个旗标,用来标识线程的运行状态(假则说明需要存储数据,真则说明有数据了,执行打印语句)
}
class Input implements Runnable
{
private Regs r;
int x =0;
Input(Regs r)
{
this.r = r;
}
public void run()
{
while (true)
{
synchronized(r)
{
if (!r.flag)//如果旗标为假,存储数据
{
if (x==0)
{
r.name = "mike";
r.sex = "man";
}
else
{
r.name = "丽丽";
r.sex = "女女女女女女";
}
x = (x+1)%2;//为下一次存储定义好x的值
r.flag = true;//将旗标改为真
r.notify();//唤醒打印线程
}
else//如果旗标为真,则线程等待
{
try{r.wait();}catch(Exception e){/*这里先不进行处理*/}
//注意:不管是wait 还是 notify都必须写在同步中。
}
}
}
}
}
class Output implements Runnable
{
private Regs r;
Output(Regs r)
{
this.r = r;
}
public void run()
{
while (true)
{
synchronized(r)
{
if (r.flag)
{
System.out.println(r.name+"::"+r.sex);
r.flag = false;
r.notify();
}else
{
try{r.wait();}catch(Exception e){/*这里先不进行处理*/}
}
}
}
}
}
public class InputOutputDemo {
public static void main(String[] args)
{
Regs r = new Regs();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
实验结果:出现了我们想要的结果:
wait();
notify();
notifyAll();
- 都使用在同步中,因为要对持有监视器(锁)的线程操作。
- 所以要使用在同步中,因为只有同步才有锁。
在查看API时发现,wait方法和notify方法都定义在Object类中,这是为什么呢?
- 因为这些方法在操作同步中线程时,都必须要标识他们所操作的线程之中的锁。
- 只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。
- 不可以对不同锁中的线程进行唤醒。
- 也就是说,等待和唤醒的必须是同一个锁。
- 而锁可以使任意对象,所以可以被任意对象调用的方法定义在Object类中。
将上面程序的代码进行优化后:
class Regs
{
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name,String sex)
{
if (flag)
try{this.wait();}catch(Exception e){}
this.name =name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out()
{
if (!flag)
try{this.wait();}catch(Exception e){}
System.out.println(name+"..."+sex);
flag = false;
this.notify();
}
}
class Input implements Runnable
{
private Regs r;
Input(Regs r)
{
this.r =r;
}
public void run()
{
int x =0;
while (true)
{
if (x==0)
r.set("mike","man");
else
r.set("丽丽","女女女女");
x = (x+1)%2;
}
}
}
class Output implements Runnable
{
private Regs r;
Output(Regs r)
{
this.r =r;
}
public void run()
{
while (true)
{
r.out();
}
}
}
public class InputOutputDemo {
public static void main(String[] args)
{
Regs r = new Regs();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
运行结果:
按照以上的例子,我们再练习一个生产者和消费者的例子,
//顾名思义就是生产者生产一个,消费者消费一个,
//不能多生产也不能多消费
class Resource
{
private String name;
private int count;
private boolean flag=false;
public synchronized void set(String name)
{
if (flag)
try{this.wait();}catch(Exception e){}
this.name = name;
System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name+count++);
flag = true;
this.notify();
}
public synchronized void out()
{
if(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"-----消费者--------"+this.name+(count-1));
flag = false;
this.notify();
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while (true)
{
res.set("商品");
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while (true)
{
res.out();
}
}
}
public class ProducerConsumerDemo {
public static void main(String[] args)
{
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(con);
t0.start();
t1.start();
}
}
运行结果为:
可以看到,结果很和谐,但是我们知道在实际生活中会有很多的生产者和消费者,也就是说会有更多的线程执行,所以我们试这多加两个线程运行代码:
其他地方不变,只修改了主函数,就是多开了两个线程,修改之后为两个生产者,两个消费者:
public class ProducerConsumerDemo {
public static void main(String[] args)
{
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
运行结果:
由运行结果可知,出现了错误的情况,生产者生产两次,消费者消费一次,这是什么原因造成的呢?
由上图可知,产生不安全结果的根本原因是线程被唤醒之后就直接执行下边的部分,不再判断flag的值,如果被唤醒的线程对flag再一次加以判断的话,就不会出现这样的情况了,于是我们修改了代码:
//顾名思义就是生产者生产一个,消费者消费一个,
//不能多生产也不能多消费
class Resource
{
private String name;
private int count;
private boolean flag=false;
public synchronized void set(String name)
{
while(flag)
try{this.wait();}catch(Exception e){}
this.name = name;
System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name+count++);
flag = true;
this.notify();
}
public synchronized void out()
{
while(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"-----消费者--------"+this.name+(count-1));
flag = false;
this.notify();
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while (true)
{
res.set("商品");
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while (true)
{
res.out();
}
}
}
public class ProducerConsumerDemo {
public static void main(String[] args)
{
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
然后发现运行结果发生了进行不下去的情况,类似于死锁的运行结果:
这里的运行结果像是死锁,但其实不是死锁,是因为被唤醒的线程每次都判断flag的值,被唤醒的自己一方的线程一判断就会将线程进入等待状态,进而进入全部等待状态,动不了了。
为了解决这种办法呢,我们需要在唤醒的时候唤醒所有等待的线程notifyAll();这样的话每次唤醒线程,程序就知道该唤醒哪一个线程,线程轮流去判断,这样运行的结果就是安全的。
//顾名思义就是生产者生产一个,消费者消费一个,
//不能多生产也不能多消费
class Resource
{
private String name;
private int count;
private boolean flag=false;
public synchronized void set(String name)
{
while(flag)
try{this.wait();}catch(Exception e){}
this.name = name;
System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name+count++);
flag = true;
this.notifyAll();
}
public synchronized void out()
{
while(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"-----消费者--------"+this.name+(count-1));
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while (true)
{
res.set("商品");
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while (true)
{
res.out();
}
}
}
public class ProducerConsumerDemo {
public static void main(String[] args)
{
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
运行结果:和谐了。
因为在实际开发中用到的基本都是多于两个线程在操作,所以while,NotifyAll用的比较多。
jdk1.5中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作
将Object中的wait,notify,notifyAll,替换成了Condition对象
该对象可以对Lock锁进行获取。
在该实例中实现了本方只唤醒对方的操作。
//利用jdk1.5的新特性重新改写此程序
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count;
private boolean flag=false;
//因为Lock是一个接口,所以获取锁只能通过已经实现的他的子类进行获取
private Lock lock = new ReentrantLock();//获取锁
private Condition con_pro = lock.newCondition();//获取Condition对象,通过lock的方法获取该对象
private Condition con_con = lock.newCondition();
public void set(String name) throws Exception
{
try
{
lock.lock();//上锁
while(flag)
con_pro.await();//生产者进程等待,会抛出异常,没处理,在set方法中声明出来。
this.name = name;
System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name+count++);
flag = true;
con_con.signal();//唤醒消费者进程
}
finally
{
lock.unlock();//释放锁,一定要执行的
}
}
public synchronized void out() throws Exception
{
try
{
lock.lock();
while(!flag)
con_con.await();
System.out.println(Thread.currentThread().getName()+"-----消费者--------"+this.name+(count-1));
flag = false;
con_pro.signal();
}
finally
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while (true)
{
try
{
res.set("商品");
}
catch (Exception e)
{
//这里先不做任何处理
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while (true)
{
try
{
res.out();
}
catch (Exception e)
{
}
}
}
}
public class ProducerConsumerDemo2 {
public static void main(String[] args)
{
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
运行结果是:
停止线程
stop方法已经结束;
那么如何停止线程?
只有一种。run方法结束。
开启多线程运行的时候,运行代码通常都是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
下面有一个事例,停止线程的方法演示:
//停止线程
//利用的是让run方法结束的原理
class StopThread implements Runnable
{
private boolean flag = true;
public void run()
{
while (flag)//run方法里边有一个while循环,循环判断语句是flag
{
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()//改变flag的值的方法
{
flag = false;
}
}
public class StopThreadDemo {
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t0 = new Thread(st);
Thread t1 = new Thread(st);
t0.start();//开启两个线程
t1.start();
int num = 0;//在主线程也开一个循环,三个线程同时进行
while (true)
{
if (num++ == 60)
{
st.changeFlag();//这里修改掉flag的值,将flag的值变为假,
//当自定义的两个线程再判断的时候while不满足条件,run方法结束
//也就表示线程结束
break; //主线程也跳出循环,程序结束
}
System.out.println(Thread.currentThread().getName()+"..."+num);
}
}
}
运行结果为:
实验结果可以看出,自定义线程和主线程交替运行,当main函数执行到60的时候,程序自动停止,也就是说这里线程自己停止了。
但是注意,有一种特殊情况用这种方法程序是停不下来的。
当线程处于了冻结状态
就不会读取到标记,那么线程就不会结束。
//停止线程
class StopThread implements Runnable
{
private boolean flag = true;
public synchronized void run()//将函数变成同步函数
{
while (flag)
{
try
{
wait();//函数中加上wait方法,线程进来之后就冻结了。然后到主线程执行完,这两个自定义线程也醒不过来。
}
catch (InterruptedException e)
{
System.out.println(Thread.currentThread().getName()+"....Exception");
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()
{
flag = false;
}
}
public class StopThreadDemo {
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t0 = new Thread(st);
Thread t1 = new Thread(st);
t0.start();
t1.start();
int num = 0;
while (true)
{
if (num++ == 60)
{
st.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"..."+num);
}
}
}
运行结果:程序没有停下来。
这是为什么呢?
因为线程0进去判断为真,就冻结了,线程1进去也是冻结了,一直到主函数执行完都没有唤醒他们的语句,所以线程一直挂在那里,程序结束不了。
怎样解决呢?
查阅API文档,知道Thread类中有一个interrupt方法:
该方法可以将处于冻结状态的线程强制恢复到运行状态中来,但是会抛出异常,这就是中断异常。
具体应用方法看代码:
//停止线程
class StopThread implements Runnable
{
private boolean flag = true;
public synchronized void run()
{
while (flag)
{
try
{
wait();
}
catch (InterruptedException e)
{
System.out.println(Thread.currentThread().getName()+"....Exception");
flag = false;//清除休眠之后,为了不再让线程继续休眠,将flag的值改为假。
//当该线程再次回来判断的时候,不符合条件,线程结束。
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()
{
flag = false;
}
}
public class StopThreadDemo {
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t0 = new Thread(st);
Thread t1 = new Thread(st);
t0.start();
t1.start();
int num = 0;
while (true)
{
if (num++ == 60)
{
//当num为60的时候,将线程强制清除休眠状态,让他继续执行。
t0.interrupt();
t1.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"..."+num);
}
System.out.println("over");
}
}
运行结果:线程可以停止。
守护线程
线程其实有前台线程和后台线程之分,
标记守护的线程就是后台线程,不标记的就是前台线程。
前台(守护)线程和后台线程在启动和运行上没有区别,在结束时,如果前台线程结束了,后台线程自动结束。
也就是说,当程序执行的只剩下后台线程了,Java虚拟机退出。
还是上边的例子:使用守护线程
//守护线程的用法
class StopThread implements Runnable
{
private boolean flag = true;
public void run()
{
while (flag)
{
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()
{
flag = false;
}
}
public class StopThreadDemo {
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t0 = new Thread(st);
Thread t1 = new Thread(st);
//在启动前将线程标记为守护线程。用setDaemon方法
t0.setDaemon(true);
t1.setDaemon(true);
t0.start();
t1.start();
int num = 0;
while (true)
{
if (num++ == 60)
{
break;//这里break,表明主线程结束,其他两个守护线程就全都关掉了,程序结束
}
System.out.println(Thread.currentThread().getName()+"..."+num);
}
}
}
运行结果,不用停止线程,只要主线程结束运行,其他线程就全都结束了
join方法
等待该线程终止;
当A线程执行到了B线程的.join()方法时,A就会等待,等线程B都执行完,A才会执行。
join可以用来临时加入线程。
通过例子来看:
//join方法,等待该线程结束
class Demo implements Runnable
{
public void run()
{
for (int x = 0;x<70 ;x++ )
{
System.out.println(Thread.currentThread().getName()+"...."+x);
}
}
}
public class JoinDemo {
public static void main(String[] args) throws InterruptedException
{
Demo d = new Demo();
Thread t0 = new Thread(d);
Thread t1 = new Thread(d);
t0.start();//线程0开启(主线程开启了线程0)
t0.join();//等待0线程结束(执行权给了线程0)
t1.start();//开启线程1(接下来就是线程1和主线程交替运行)
for (int x = 0;x<80 ;x++ )
{
System.out.println(".....main"+x);
}
System.out.println("over");
}
}
运行结果是:等到线程0全部运行完,线程1和主线程才交替运行:
那么当我将join方法换一个位置之后呢?
//join方法,等待该线程结束
class Demo implements Runnable
{
public void run()
{
for (int x = 0;x<70 ;x++ )
{
System.out.println(Thread.currentThread().getName()+"...."+x);
}
}
}
public class JoinDemo {
public static void main(String[] args) throws InterruptedException
{
Demo d = new Demo();
Thread t0 = new Thread(d);
Thread t1 = new Thread(d);
t0.start();
t1.start();//主线程开启了两个线程。
t0.join();//等待0线程执行完,主线程才能继续往下走,
//这个时候一共存在的有两个线程,所以是线程0和线程1交替执行
//等到线程0结束(线程1可以不结束)主线程才会继续执行下去。
for (int x = 0;x<80 ;x++ )
{
System.out.println(".....main"+x);
}
System.out.println("over");
}
}
运行结果为:
Thread类中的其他方法
Thread类重写了Object中的toString方法
//toString方法查看优先级,setPriority方法设置优先级
class Demo implements Runnable
{
public void run()
{
for (int x = 0;x<70 ;x++ )
{
System.out.println(Thread.currentThread().toString()+"...."+x);
}
}
}
public class JoinDemo {
public static void main(String[] args) throws InterruptedException
{
Demo d = new Demo();
Thread t0 = new Thread(d);
Thread t1 = new Thread(d);
t0.start();
t1.start();
}
}
运行结果是:
由图上可以看出,toString方法显示的内容(荧光笔划住的内容)依次是:线程名称,优先级(默认都为5)线程组(本线程由谁开启的线程组就是谁的)
那么问题来了,怎么样修改线程的优先级呢?
//toString方法查看优先级,setPriority方法设置优先级
//优先级10最大,1最小
class Demo implements Runnable
{
public void run()
{
for (int x = 0;x<70 ;x++ )
{
System.out.println(Thread.currentThread().toString()+"...."+x);
}
}
}
public class JoinDemo {
public static void main(String[] args) throws InterruptedException
{
Demo d = new Demo();
Thread t0 = new Thread(d);
Thread t1 = new Thread(d);
t0.setPriority(1);//利用传入int类型的值得方式传入优先值
t1.setPriority(Thread.MAX_PRIORITY);//利用常量值得方式传入优先值
t0.start();
t1.start();
}
}
运行结果:
yield方法的使用
暂停当前正在执行的线程,执行其他线程,
这个可以干什么呢?两个线程可以交替的执行。因为暂停只是暂时的。
但交替执行也是不确定的,因为暂停一下不能保证一定回去执行另外一个线程。
//yield方法的使用
class Demo implements Runnable
{
public void run()
{
for (int x = 0;x<10 ;x++ )
{
System.out.println(Thread.currentThread().toString()+"...."+x);
Thread.yield();//线程执行到这里的时候暂停一下,去执行其他的线程。
}
}
}
public class JoinDemo {
public static void main(String[] args) throws InterruptedException
{
Demo d = new Demo();
Thread t0 = new Thread(d);
Thread t1 = new Thread(d);
t0.start();
t1.start();
}
}
运行结果:
那么在实际开发中怎么写线程呢?
比如说我要三个for循环语句同时执行,
public class ThreadTest {
public static void main(String[] args)
{
new Thread()
{
public void run()
{
for (int x = 0;x<30 ;x++ )
{
System.out.println("Thread..."+x);
}
}
}.start();
Runnable r = new Runnable()
{
public void run()
{
for (int x = 0;x<30 ;x++ )
{
System.out.println("Runnable..."+x);
}
}
};
new Thread(r).start();
for (int x = 0;x<30 ;x++ )
{
System.out.println("main..."+x);
}
}
}
运行结果: