线程同步操作
生产者消费者案例
生产者不断生产,消费者取走生产者生产的产品;
生产者生产的是信息,就可以定义一个信息类,生产者和消费者同时占有这个信息类的引用,那么,就可以将生产者和消费者两个线程通过信息类联合在一起。
生产者实现多线程机制;消费者实现多线程机制;
class Info{ // 定义信息类
private String name = "李华"; // 定义name属性
private String content = "JAVA讲师" ; // 定义content属性
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.setName("李华") ; // 设置名称
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("JAVA讲师") ; // 设置内容
flag = false ;
}else{
this.info.setName("mldn") ; // 设置名称
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("www.mldnjava.cn") ; // 设置内容
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.info.getName() +
" --> " + this.info.getContent()) ;
}
}
};
public class ThreadCaseDemo01{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};
生产者生产50次信息,为了更好的发现问题,中间加入了延迟;然后是消费者,消费者不停的取出;最后进行测试;
问题
从测试运行记过看,生产的消息有重复取,有消息错乱的情况出现,消息1的name和消息2的content被成对的取出来了;
怎么解决呢?
之所以出现信息错乱不对应的情况,是因为中间加入了延迟操作,所以产生了不同步的问题,那么就可以使用解决该问题;使用同步方法,或者同步代码块;
怎么改?
问题解决
class Info{ // 定义信息类
private String name = "李兴华"; // 定义name属性
private String content = "JAVA讲师" ; // 定义content属性
public synchronized void set(String name,String content){
this.setName(name) ; // 设置名称
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.setContent(content) ; // 设置内容
}
public synchronized void get(){
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.getName() +
" --> " + this.getContent()) ;
}
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.set("李兴华","JAVA讲师") ; // 设置名称
flag = false ;
}else{
this.info.set("mldn","www.mldnjava.cn") ; // 设置名称
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
this.info.get() ;
}
}
};
public class ThreadCaseDemo02{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};
问题2
这样修改之后,解决了消息错乱不对应的问题,但是,还是有消息重复取的问题;
该怎么解决这个消息重复取的问题呢?
既然有重复取,就会有重复设置的情况。
现在的代码,还并没有达到设置一个,取走一个的功能要求,那需要怎么解决呢?
问题再解决
分析一个机制:
图1:
如上图1:生产者生产一个产品以后,放入一个盒子;
当盒子是空的,盒子上方的灯是绿色,表示生产者可以向盒子里放产品;
图2:
入上图2,当产品放入盒子以后,灯从绿色变成红色,表示此时生产者不可以往里放产品了,但是,此时,消费者可以从里面取走产品;
图3:
如上图3,消费者从盒子取走产品以后,再将盒子的灯从红色置位绿色,表示消费者不可以从里面取了,但是生产者又可以往里面放产品了;那么,将产品2放入盒子,盒子的灯从绿色变成红色了,如下图4;
图4:
图5:
如上图5,但是,当产品2还没有被消费者取走,生产者的产品3就过来了的时候,发现是红灯,那么此时产品3得等待,等待消费者将产品2取走等变成绿色了再放入盒子。如下图6;
图6:
等变绿色,再将产品3放入盒子;
要想实现这样的机制,则需要依靠Object类中的方法的支持,什么方法呢???
Object类中存在一个等待与唤醒的机制,对线程等待与唤醒的控制;
Object类对线程的支持
不需要修改其他类,直接修改Info类即可,增加等待与唤醒的机制:
修改:
class Info{ // 定义信息类
private String name = "李兴华"; // 定义name属性
private String content = "JAVA讲师" ; // 定义content属性
private boolean flag = false ; // 设置标志位
public synchronized void set(String name,String content){
if(!flag){
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
this.setName(name) ; // 设置名称
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.setContent(content) ; // 设置内容
flag = false ; // 改变标志位,表示可以取走
super.notify() ;
}
public synchronized void get(){
if(flag){
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.getName() +
" --> " + this.getContent()) ;
flag = true ; // 改变标志位,表示可以生产
super.notify() ;
}
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.set("李兴华","JAVA讲师") ; // 设置名称
flag = false ;
}else{
this.info.set("mldn","www.mldnjava.cn") ; // 设置名称
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
this.info.get() ;
}
}
};
public class ThreadCaseDemo03{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};
现在发现,实现了生产一条,取走一条,没有错乱,也没有重复取走了。此时,完成了生产者和消费者的正确操作。
线程的生命周期
一个新的线程创建以后可以通过start()方法进入运行状态,在运行状态可以通过yield()进行礼让,但是仍然可以执行,如果要让一个线程暂停的话,可以使用suspend(),sleep(),wait(),如果现在不需要线程再执行,则可以通过stop()停止线程,(run()方法执行结束也表示结束),一个新的线程直接调用stop()也可以进行结束;
但是,以下的几个方法都不再使用了,已经废弃了,因为他们都很容易引发死锁,所以都不建议使用了:
suspend()暂时挂起线程;
resume() 恢复挂起的线程;
stop() 结束线程;
那么,要结束一个线程,怎么操作?
可以通过设置标志位让线程停止;
class MyThread implements Runnable{
private boolean flag = true ; // 定义标志位
public void run(){
int i = 0 ;
while(this.flag){
System.out.println(Thread.currentThread().getName()
+"运行,i = " + (i++)) ;
}
}
public void stop(){
this.flag = false ; // 修改标志位
}
};
public class StopDemo{
public static void main(String args[]){
MyThread my = new MyThread() ;
Thread t = new Thread(my,"线程") ; // 建立线程对象
t.start() ; // 启动线程
try{
Thread.sleep(30) ;
}catch(Exception e){
}
my.stop() ; // 修改标志位,停止运行
}
};