------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------
多线程
1.什么叫线程?
如果我们想要了解什么叫线程,那么首先我们应该了解什么叫进程。
进程
线程
到这里我们就知道,其实多线程,指的就是在一个进程中,有多个控制单元,或者说多条执行路径,使得程序的各个部分好像在同时运行似的。
JVM
2.多线程的作用
3.线程的创建
继承:
/*
创建一个线程的第一种方法:
1.继承自Thread类
2.重写run方法(run方法时Thread类实现的Runnable接口的方法)
*/
package com.helong.threaddemo;
class MyThread extends Thread
{
public void run()
{
for(int i=0;i<100000;i++)
System.out.println("mythread run");
}
}
class ThreadDemo
{
public static void main(String[] args)
{
MyThread mt = new MyThread();
//mt.run();
mt.start();//使得线程开始执行,jvm调用mt的run方法,也只有这种方法才能主线程mt线程交替执行
for(int i=0;i<100000;i++)
System.out.println("mainthread run");
}
}
运行图:
实现:
/*
继承方式:
简单卖票程序:
多个窗口同时卖票
每卖出一张,票号加1
假设只有100张
*/
package com.helong.sellticket;
class TicketWindow extends Thread
{
private static int tickets=1;//没有static,会打印30张票
TicketWindow(String name)
{
super(name);
}
public void run()
{
while(tickets!=11)
{
System.out.println(this.getName()+"sell ticket :"+tickets++);
}
}
}
class SellTicket
{
public static void main(String[] args)
{
TicketWindow tw1 = new TicketWindow("窗口1");
TicketWindow tw2 = new TicketWindow("窗口2");
TicketWindow tw3 = new TicketWindow("窗口3");
tw1.start();
//tw1.start();
tw2.start();
tw3.start();
}
}
运行图:
/*
继承方式问题:
票数必须为static,否则有几个线程对象,就打印几份票
而static由于生命周期过长,通常不建议使用
实现方式:
这种方式即避免了继承方式的单继承局限性
不存放打印重复票的问题(因为本身TickWindow只有一个对象)
*/
/*
未使用同步代码块时出现安全问题的打印:
Thread-1sell ticket :1
Thread-0sell ticket :2
Thread-2sell ticket :1
Thread-1sell ticket :3
Thread-2sell ticket :4
Thread-0sell ticket :5
Thread-1sell ticket :6
Thread-2sell ticket :7
Thread-0sell ticket :8
Thread-1sell ticket :9
Thread-0sell ticket :10
Thread-2sell ticket :11
Thread-1sell ticket :12
*/
/*
使用同步代码块后的打印:
*/
package com.helong.sellticket2;
class TickWindow implements Runnable
{
private int tickets=1;
Object obj = new Object();
//并没有静态化tickets,不需要,因为自始至终多个线程运行的是同一段代码,操作的是同一个成员tickets
public void run()
{
while(true)
{
//解决这种安全问题的方法,叫做同步代码块,将操作了共享数据的代码块使用synchronized(对象)包起来
synchronized(obj)
{
if(tickets<1001)
{
try
{
//Thread.currentThread().sleep(1000);
Thread.sleep(100);
//有可能看上去不成功,那可能是因为睡眠时间不够,或者票数不够,一个线程还没执行完毕
//票就没了
//这种安全性问题出现的几率不大,但是出现了就很严重,因此多线程的安全问题一定要注意
}
catch (InterruptedException e)
{
}
System.out.println(Thread.currentThread().getName()+"sell ticket :"+tickets++);
}
}
}
}
}
class SellTicket2
{
public static void main(String[] args)
{
TickWindow tw = new TickWindow();
Thread t1 = new Thread(tw);
//将线程本身与要执行的代码的对象联系起来,联系的基础是实现了Runnable接口
//只是实现了该接口,都能在一个线程中独立运行
Thread t2 = new Thread(tw);
Thread t3 = new Thread(tw);
t1.start();
t2.start();
t3.start();
}
}
运行图:
继承与实现的区别:
4.线程在运行中的几种状态
5.线程常用方法
静态方法currentThread获取当前线程对象,作用相当于this(this只适用于继承方式的多线程中),在实现方法实现多线程中:由于线程运行代码本身不在线程的方法中,因此无法使用this来获取当前线程,此时只能用Thread.currentThread()来获取。
setName设置线程名称。
getName获取线程名称。
小知识点:一个线程不能多次start。
6.线程的安全性问题
class Ticket implements Runnable
{
private int tick = 1000;
Object obj = new Object();
public void run()
{
while(true)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行图:
我们看到,结果打印出了0,-1,-2这样的票号,这肯定是错误的,其实原因很简单,如图,当线程1判断成功准备打印票号1时,它睡眠了,这时线程3执行,也判断成功了,准备打印时也睡眠了,线程2和线程0同样的结果,都是判断成功了,但是没打印,此时回到线程1,它打印票号1,运行结束,线程3执行,此时票号已经为0了,按理说是不合理的,但是由于之前就判断成功了,所以此处没有再判断一次,结果就打印出了非法的票号0,线程2和线程0也是一样的道理,结果打印出了票号-1和-2。
其实解决这个问题的原则也很简单:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
java对于解决多线程安全性问题提供了专业的解决办法:同步代码块和同步函数。
同步代码块:
好处:解决了多线程的安全性问题。
弊端:每次执行到此都要进行锁的判断等,消耗资源。
class Ticket implements Runnable
{
private int tick = 100;
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行图:
可以看到,当买到1号票时,程序就会停止卖票,这就是使用同步代码块解决多线程的安全性问题。
同步函数:
同步函数synchronized修饰一个函数,使得函数具有同步的能力。
此时锁对象是this,因此如果使用的是继承方式的话,那就代表当前线程,也就是说该锁不唯一,因此此方法只适用于实现方式。
如果同步函数被static修饰,它随着类的加载而进入内存,静态进内存时,还没有本类对象,但是有本类对应的字节码文件对象,因此静态同步函数使用的锁就是类名.class。
/*
验证:同步函数中使用的锁是this
前提:实现方式
*/
package com.helong.threaddemo3;
class Ticket implements Runnable
{
private static int tickets=1;
private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
flag=false;
while(true)
{
//synchronized(this)
//如果此时为obj,那么达不到同步的效果,因为两个线程持有的锁不同,换成this即可
synchronized(Ticket.class)
//当同步函数为静态时,它使用的锁是本类对应的字节码文件对象也就是Ticket.class
{
if(tickets<1001)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("code ..... "+tickets++);
}
}
}
}
else
{
while(true)
sale();
}
}
private static synchronized void sale()//将此同步函数设为静态,那么tickets也应该为静态,但此时它使用的锁是Ticket.class对象
{
if(tickets<1001)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("function ..... "+tickets++);
}
}
}
class ThreadDemo3
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t2.start();
}
}
运行图:
7.死锁
/*
需求:实现死锁现象
形成死锁的原因:两个或以上的线程,两个或以上的锁,
此处使用线程A,线程B,锁A,锁B,当线程A持有锁A,
线程B持有锁B,而执行需要同时持有两种锁时,这时
线程A有锁A,请求锁B,线程B有锁B,请求锁A,进入
死锁现象。
思路:
1.run方法中分两路对应两个线程,设置两个锁
2.每一路请求锁的顺序不同
*/
package com.helong.deadlocktest;
class DeadLock implements Runnable
{
private int tickets=1;
private Object obj1 = new Object();
private Object obj2 = new Object();//两把锁
boolean flag = true;
public void run()
{
if(flag)
{
flag=false;
while(true)
{
synchronized(obj1)//请求obj1锁
{
//很可能线程A在请求到obj1时,线程B已经持有了obj2了,此时就是死锁
synchronized(obj2)//请求obj2锁
{
if(tickets<1001)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"卖出票:"+tickets++);
}
}
}
}
}
else
{
while(true)
{
synchronized(obj2)
{
synchronized(obj1)
{
if(tickets<1001)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"卖出票:"+tickets++);
}
}
}
}
}
}
}
class DeadLockTest
{
public static void main(String[] args)
{
DeadLock dl = new DeadLock();
Thread t1 = new Thread(dl);
Thread t2 = new Thread(dl);
t1.start();
t2.start();
}
}
运行图:
可以看到,在某一时刻,票没有卖完,但是所有线程都进入了等待的状态,这就是死锁。
8.线程间通信
线程间通信:不同的线程操作同一片资源,但是操作动作不同(例如:存,取)。
安全问题:例如资源包含两个数据name,sex,两个线程,一个负责存,一个负责取,当存线程存入"mike","man"时,此时取线程来打印name,sex,没问题,但是如果当存线程存入"lili"之后还没来得及存入"woman",结果取线程就打印了,那输出的就是"lili","man",这就有了安全性问题,因为这组数据时相关的,要么全部存好再取,要么全部取出再存,应该讲这一片资源视为共享数据组,对它们的操作都应该是同步的。
/*
线程间通信:就是说不同的线程操作同一个资源,但是操作动作不同(存,取)
*/
package com.helong.threadmessagetest1;
class Resource
{
private String name;
private String sex;
boolean flag=false;//用于表示此时是否存在可用数据组
private static Resource r = new Resource();
private Resource(){}
public static Resource getInstance()
{
return r;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name=name;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex=sex;
}
}
//解决方法只需两个线程中的一个对数据进行操作
//结束前不允许其他线程来操作
class Input implements Runnable//负责存入数据
{
static Thread thread;
public void run()
{
thread=Thread.currentThread();
int x=1;
Resource r = Resource.getInstance();
while(true)
{
if(x==1)//模拟根据不同的情况,存入的每组数据都是不同的,此处设置两组
{
r.setName("mike");
<span style="white-space:pre"> </span>r.setSex("man");
}
else
{
r.setName("肥园");
r.setSex("女");
}
x=(x+1)%2;//通过x控制该run方法反复设置两组数据到Resource中
}
}
}
class Output implements Runnable//负责打印数据
{
static Thread thread;
public void run()
{
thread=Thread.currentThread();
Resource r = Resource.getInstance();
while(true)
{
System.out.println(r.getName()+"......"+r.getSex());
}
}
}
class ThreadMessageTest1
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Input());
Thread t2 = new Thread(new Output());
t1.start();
t2.start();
}
}
运行图:
我们可以看到,此时的数据就是错误的,打印出了“肥园......man”和“mike......女”这样明显错误的数据,当然,这个只是共享数据问题,我们可以使用同步代码块来解决。
/*
线程间通信:就是说不同的线程操作同一个资源,但是操作动作不同(存,取)
*/
package com.helong.threadmessagetest1;
class Resource
{
private String name;
private String sex;
boolean flag=false;//用于表示此时是否存在可用数据组
private static Resource r = new Resource();
private Resource(){}
public static Resource getInstance()
{
return r;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name=name;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex=sex;
}
}
//解决方法只需两个线程中的一个对数据进行操作
//结束前不允许其他线程来操作
class Input implements Runnable//负责存入数据
{
static Thread thread;
public void run()
{
thread=Thread.currentThread();
int x=1;
Resource r = Resource.getInstance();
while(true)
{
synchronized(r)//Resource为单例模式设计的,因此r肯定只有一个,可以作为锁
{
if(x==1)//模拟根据不同的情况,存入的每组数据都是不同的,此处设置两组
{
r.setName("mike");
r.setSex("man");
}
else
{
r.setName("肥园");
r.setSex("女");
}
}
x=(x+1)%2;//通过x控制该run方法反复设置两组数据到Resource中
}
}
}
class Output implements Runnable//负责打印数据
{
static Thread thread;
public void run()
{
thread=Thread.currentThread();
Resource r = Resource.getInstance();
while(true)
{
synchronized(r)
{
System.out.println(r.getName()+"......"+r.getSex());
}
}
}
}
class ThreadMessageTest1
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Input());
Thread t2 = new Thread(new Output());
t1.start();
t2.start();
}
}
运行图:
我们看到,虽然数据是没问题了,但是由于每个线程运行的时间都是一小段,因此每个线程都会打印一片数据,之后再轮到下一个线程运行,能不能使每个线程只打印一条数据呢?
/*
线程间通信:就是说不同的线程操作同一个资源,但是操作动作不同(存,取)
控制台:存多次,打印一片
原因:每个线程执行都是一段时间,那么在打印线程中打印一段时间肯定是同样的数据,
因为这一段时间内,存入线程根本没执行
实现,存一个,打印一个的效果
*/
package com.helong.threadmessagetest;
class Resource
{
private String name;
private String sex;
boolean flag=false;//用于表示此时是否存在可用数据组
private static Resource r = new Resource();
private Resource(){}
public static Resource getInstance()
{
return r;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name=name;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex=sex;
}
}
//解决方法只需两个线程中的一个对数据进行操作
//结束前不允许其他线程来操作
class Input implements Runnable//负责存入数据
{
static Thread thread;
public void run()
{
thread=Thread.currentThread();
int x=1;
Resource r = Resource.getInstance();
while(true)
{
synchronized(r)//Resource为单例模式设计的,因此r肯定只有一个,可以作为锁
{
if(r.flag)
{
try{r.wait();}catch(Exception e){}
}
if(x==1)//模拟根据不同的情况,存入的每组数据都是不同的,此处设置两组
{
r.setName("mike");
r.setSex("man");
}
else
{
r.setName("肥园");
r.setSex("女");
}
r.flag=true;
r.notify();
}
x=(x+1)%2;//通过x控制该run方法反复设置两组数据到Resource中
}
}
}
class Output implements Runnable//负责打印数据
{
static Thread thread;
public void run()
{
thread=Thread.currentThread();
Resource r = Resource.getInstance();
while(true)
{
synchronized(r)
{
if(!r.flag)//如果没有可用数据组
{
try{r.wait();}catch(Exception e){}
}
System.out.println(r.getName()+"......"+r.getSex());
r.flag=false;
r.notify();
}
}
}
}
class ThreadMessageTest
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Input());
Thread t2 = new Thread(new Output());
t1.start();
t2.start();
}
}
运行图:
此时我们看到,不仅仅数据时正确的,而且是一条一条打印出来的了。
9.等待--唤醒机制
wait(),notify(),notifyAll()。
这三个方法都是定义在Object类上的,因为它们都是由监视器(锁)调用的,而任何对象都可以作为锁,因此定义在Object类上;它们操作的必须是同一把锁上的线程,例如wait只能冻结同一把锁上的运行线程,而notify和notifyAll也只能唤醒同一把锁上的线程。
锁.wait();作用是将当前持有该锁的线程对象冻结。
锁.notify();作用是将锁上第一个被冻结的线程对象解冻。
锁.notifyAll();作用是将锁上所有被冻结的线程解冻。
10.生产者消费者问题
安全性问题:wait使当前线程进入冻结状态等待其他线程notify它,如果此处有三个线程,且他们的运行是由顺序的,假设进入了一个运行,两个等待的情况,那么当线程A运行完毕,notify时,可能唤醒的是线程C,而不是线程B,这时由于不满于线程C执行的条件,于是线程C进入wait状态,此时进入了类似死锁的状态,线程A,B,C都进入wait状态了。
产生这种安全性问题的原因是:多线程的随机性,唤醒机制不会固定每次都依次唤醒线程A,B,C,而是随机唤醒锁上的冻结在线程池中的一个,这时就会可能唤醒到不想唤醒的线程,按照程序,根据条件判断线程是执行还是进入wait,所以如果唤醒到错的线程,那么该线程就会wait,导致此次没有唤醒到应该唤醒的线程,也就运行不了了,所有线程均wait。
解决方法:在程序中判断是否满足执行,如果不满足则wait的代码块中,加入判断是否应该运行当前线程的语句,如果不应该那么就notify其他线程,也就是说如果当前线程还不满足运行条件,那么执行两步:
1.wait;让该线程进入等待状态,将执行权让出来。
2.if(不该这个线程运行)notify;如果此时该线程不应该执行,但是却获得了执行权,那么应该notify别的线程,这样即便唤醒了错的线程,也能让该线程回到wait状态,且notify别的线程。
加强版生产者--检查者--消费者代码:
/*
扩展:生产-》检查-》消费
wait使当前线程进入冻结状态等待其他线程notify它,
如果此处有三个线程,且他们的运行是由顺序的,假设进入了
一个运行,两个等待的情况,那么当线程A运行完毕,notify时,
可能唤醒的是线程C,而不是线程B,这时由于不满于
线程C执行的条件,于是线程C进入wait状态,
此时进入了类似死锁的状态,线程A,B,C都进入wait状态了;
*/
package com.helong.producerconsumer;
import java.util.concurrent.locks.*;
class Res
{
private String name;
private int count=1;
private boolean haved=false;//标志是否有可用货物
private boolean checked=false;//标志货物是否已经检查
private static Res r = new Res();
private Res(){};
public static Res getInstance()
{
return r;
}
public synchronized void set(String name)
{
if(!haved)//如果没有货物可用
{
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
haved=true;//设置有可用货物
checked=false;//设置该货物未检查
this.notify();
}
else
{
try{this.wait();}catch(InterruptedException e){}
//到此处有两种可能,1:这就是即将运行的线程,2:这不是将要运行的线程
//如果这不是将要运行的线程,那么应该唤醒其他线程
//每一个线程到此都应该判断,因为多线程的随机性,不一定轮到哪个线程
//就是说应该它运行,此程序是有固定顺序的
if(haved)//不该此线程运行
this.notify();
}
}
public synchronized void check()
{
if(haved&&!checked)//有货物并且未检查
{
System.out.println(Thread.currentThread().getName()+"...检查者..."+this.name);
checked=true;
this.notify();
}
else
{
try{this.wait();}catch(InterruptedException e){}
if(!haved||checked)
this.notify();
}
}
public synchronized void out()
{
if(haved&&checked)
{
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
haved=false;
this.notify();
}
else
{
try{this.wait();}catch(InterruptedException e){}
if(!haved||!checked)
this.notify();
}
}
}
class Producer implements Runnable
{
public void run()
{
Res r = Res.getInstance();
while(true)
{
r.set("电脑键盘");
}
}
}
class Checker implements Runnable
{
public void run()
{
Res r = Res.getInstance();
while(true)
{
r.check();
}
}
}
class Consumer implements Runnable
{
public void run()
{
Res r = Res.getInstance();
while(true)
{
r.out();
}
}
}
class ProducerConsumer
{
public static void main(String[] args)
{
new Thread(new Producer()).start();
new Thread(new Checker()).start();
new Thread(new Consumer()).start();
}
}
运行图:
我们可以看到,使用了等待--唤醒机制的生产--检查--消费问题,得到了很好的解决。
11.多个生产者消费者问题
安全性问题:
例如:生产者线程A,B,消费者线程C,D;假设先是A运行,生产了商品1,进入冻结,此时线程B运行,判断进入冻结状态,线程C运行,消费了商品2,唤醒了A,进入冻结,线程D判断进入冻结,A生产了商品2,唤醒了B,此时B的位置是wait处,被唤醒后直接向下执行,生产了商品3(产生错误,多次生产商品)。
原因:
多线程的随机性,导致唤醒的不一定是应该执行的那个线程,但是由于线程运行代码内部只有一次判断,因此当一个线程wait后,下一次运行它时就不需要判断而直接执行,这就导致了错误的产生。
解决方法:
1):将一次判断改为多次循环判断。
如果仅仅是改为循环会导致所有线程等待,当B,C,D为冻结,A唤醒了B,A也冻结,此时由于循环判断不该B执行,因此B也进入冻结,也就是ABCD均冻结。
2):唤醒该唤醒的线程,因为无法决定唤醒哪个线程,因此使用notifyAll唤醒所有。
错误的线程将进入等待,而正确的线程将执行。
package com.helong.threadmessagetest2;
class Resource
{
private String name;
private boolean flag=false;
private int count=1;
private static final Resource r = new Resource();
private Resource(){}
public static Resource getInstance()
{
return r;
}
public synchronized void set(String name)
{
if(flag)//如果当前有可用的数据,先冻结set,等待print将数据使用后,再存数据
try{this.wait();}catch(InterruptedException e){}
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag=true;
this.notify();
}
public synchronized void print()
{
if(!flag)//如果当前没有可用数据,先冻结print,等待set将数据存入后,再打印数据
try{this.wait();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
flag=false;
this.notify();
}
}
class Input implements Runnable
{
public void run()
{
Resource r = Resource.getInstance();
while(true)
{
r.set("商品");
}
}
}
class Output implements Runnable
{
public void run()
{
Resource r = Resource.getInstance();
while(true)
{
r.print();
}
}
}
class ThreadMessageTest2
{
public static void main(String[] args)
{
new Thread(new Input()).start();
new Thread(new Input()).start();
new Thread(new Input()).start();
new Thread(new Output()).start();
new Thread(new Output()).start();
new Thread(new Output()).start();
}
}
运行图:
我们可以看到,出现了很多生产一次,却消费了多次的情况,这肯定是不允许的。使用解决方法解决后:
package com.helong.threadmessagetest2;
class Resource
{
private String name;
private boolean flag=false;
private int count=1;
private static final Resource r = new Resource();
private Resource(){}
public static Resource getInstance()
{
return r;
}
public synchronized void set(String name)
{
while(flag)//如果当前有可用的数据,先冻结set,等待print将数据使用后,再存数据
try{this.wait();}catch(InterruptedException e){}
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag=true;
this.notifyAll();
}
public synchronized void print()
{
while(!flag)//如果当前没有可用数据,先冻结print,等待set将数据存入后,再打印数据
try{this.wait();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
flag=false;
this.notifyAll();
}
}
class Input implements Runnable
{
public void run()
{
Resource r = Resource.getInstance();
while(true)
{
r.set("商品");
}
}
}
class Output implements Runnable
{
public void run()
{
Resource r = Resource.getInstance();
while(true)
{
r.print();
}
}
}
class ThreadMessageTest2
{
public static void main(String[] args)
{
new Thread(new Input()).start();
new Thread(new Input()).start();
new Thread(new Input()).start();
new Thread(new Output()).start();
new Thread(new Output()).start();
new Thread(new Output()).start();
}
}
运行图:
我们看到那种生产一次,消费多次的情况已经没有了。
12.多线程小练习
/*
本质原因:
不管是生产,检查,消费三个线程按顺序执行,还是多个生产,消费的执行,都会
产生混乱的情况,例如生产,检查,消费中的其中某一步突然不运行了,而只运行
其他两步,或者多个生产,消费中的生产了1,2,结果只消费了2,或者生产了3,
结果两次消费了3。本质都是因为多线程的随机性导致任何线程都可能抢到执行权,
由于代码中只进行了一次判断,因此当这些错的线程抢到执行权时,已经不需要判断
而直接运行代码,这时就有错误产生了。线程顺序执行也一样,可能跳过了某个线程
直接运行后一个线程,但由于不需要判断因此也能运行。
解决方法:
处理好两点即可:
1.将判断改为需要多次判断
2.使得唤醒到对的线程,错误的线程即便唤醒了也要再次进入wait
*/
/*
生产-》检查-》消费
生产,生产-》检查,检查-》消费,消费
产生的问题:
按照原来的代码实现方式,总是到某一时刻就有线程不在执行了,此处为检查线程
原因:
例如:线程A-》线程B-》线程C
由于多线程的随机性,且notify本身也是随机唤醒一个冻结线程很可能线程A结束之后
运行线程C,而由于在代码中判断只是一次,而如果上一次判断失败了线程wait,那么
线程被notify后就不用判断了,无论是否该此线程运行,它都会运行,此时不管哪个线程
抢到线程执行权,都无需判断,直接运行,因此产生了不确定的错误。
总结:
1.只判断一次
2.正确的线程没有被唤醒
解决方法:
方法1:
(1)将if改为while(循环判断)
将一次判断改为循环判断,这时即便错误的线程抢到执行权,也会再判断,从而
进入wait冻结,但是如果此时错误的在运行的线程wait了,那所有线程就都wait
了,程序也没法运行下去了,因此还有第二步。
(2)将notify改为notifyAll(唤醒所有线程)
这是将所有冻结线程唤醒,因为其中肯定有正确的线程,这些线程被唤醒开始抢
执行权,如果是正确线程抢到,那就运行代码,如果是错误线程抢到,那就继续
wait,同时让其他线程运行,因此可能能够让正确的线程运行。
方法2:
原来if(...){wait}
.............
改为if(...){wait,notify}
else{........}
(1)由于调用者是循环调用这些方法,因此线程从if中出来后又循环回去判断
(2)当错误线程被运行时,它先在if中notify了一个其他线程,再循环判断后
自己又进入了wait状态,交出执行权给其他可能正确的线程。
*/
package com.helong.threadtest2;
class Res
{
private String name;
private int count=1;
private boolean haved=false;
private boolean checked=true;
private static Res r = new Res();
private Res(){}
public static Res getInstance()
{return r;}
public synchronized void set(String name)
{
if(haved)
{
try{wait();}catch(Exception e){}
notify();
}
else
{
this.name=name+"----"+count++;
System.out.println(Thread.currentThread().getName()+"生产者"+this.name);
haved=true;
checked=false;
notify();
}
}
public synchronized void check()
{
if(!haved||checked)
{
try{wait();}catch(Exception e){}
notify();
}
else
{
System.out.println(Thread.currentThread().getName()+"........检查者"+this.name);
checked=true;
notify();
}
}
public synchronized void out()
{
if(!haved||!checked)
{
try{wait();}catch(Exception e){}
notify();
}
else
{
System.out.println(Thread.currentThread().getName()+"................消费者"+this.name);
haved=false;
notify();
}
}
}
class Producer implements Runnable
{
public void run()
{
Res r = Res.getInstance();
while(true)
{
r.set("时尚服饰");
}
}
}
class Checker implements Runnable
{
public void run()
{
Res r = Res.getInstance();
while(true)
{
r.check();
}
}
}
class Consumer implements Runnable
{
public void run()
{
Res r = Res.getInstance();
while(true)
{
r.out();
}
}
}
class Thread2
{
public static void main(String[] args)
{
//单一的生产者,检查者,消费者情况
//new Thread(new Producer()).start();
//new Thread(new Checker()).start();
//new Thread(new Consumer()).start();
//多个生产者,检车者,消费者情况
new Thread(new Producer()).start();
new Thread(new Checker()).start();
new Thread(new Consumer()).start();
new Thread(new Producer()).start();
new Thread(new Checker()).start();
new Thread(new Consumer()).start();
new Thread(new Producer()).start();
new Thread(new Checker()).start();
new Thread(new Consumer()).start();
}
}
运行图:
12.JDK1.5新特性
package com.helong.threadmessagetest2;
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private boolean flag=false;
private int count=1;
private Lock lock=new ReentrantLock();
Condition setCon=lock.newCondition();
Condition printCon=lock.newCondition();
private static final Resource r = new Resource();
private Resource(){}
public static Resource getInstance()
{
return r;
}
public void set(String name)throws InterruptedException
{
lock.lock();
try
{
while(flag)//如果当前有可用的数据,先冻结set,等待print将数据使用后,再存数据
setCon.await();
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag=true;
printCon.signal();
}
finally
{
lock.unlock();
}
}
public void print()throws InterruptedException
{
lock.lock();
try
{
while(!flag)//如果当前没有可用数据,先冻结print,等待set将数据存入后,再打印数据
printCon.await();
System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);
flag=false;
setCon.signal();
}
finally
{
lock.unlock();
}
}
}
class Input implements Runnable
{
public void run()
{
Resource r = Resource.getInstance();
while(true)
{
try{r.set("商品");}catch(Exception e){}
}
}
}
class Output implements Runnable
{
public void run()
{
Resource r = Resource.getInstance();
while(true)
{
try{r.print();}catch(Exception e){}
}
}
}
class ThreadMessageTest2
{
public static void main(String[] args)
{
new Thread(new Input()).start();
new Thread(new Input()).start();
new Thread(new Input()).start();
new Thread(new Output()).start();
new Thread(new Output()).start();
new Thread(new Output()).start();
}
}
运行图:
13.结束线程
/*
结束线程:
1.使用Thread的stop方法,但是该方法已经过时了,原因是会有BUG。
2.结束的本质是run方法结束,也就是说能控制run方法中的循环结构也就能结束线程。
特殊情况:
当run方法有同步时,此时线程0,1进入run方法,判断成功,进入wait冻结,轮到
main执行,即便改变了标记,由于线程0,1都在冻结状态,因此进入了main线程结束,
线程0,1一直冻结的状态。
解决方法:interrupt:清除由wait,sleep,join引发的冻结状态
清除线程0,1的冻结状态,使用Thread类的interrupt方法,该方法的功能是在线程处于
冻结(中断)状态时,强行唤醒线程,清除线程的冻结状态,使线程恢复到运行状态,
因此在一个线程不想醒时叫醒它,所以发生了InterruptedException异常。
*/
package com.helong.stopthreadtest;
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");
}
*/
System.out.println(Thread.currentThread().getName()+"...run");
}
}
public void stop()
{
flag=false;
}
}
class StopThreadTest
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
t1.setDaemon(true);
t1.start();
Thread t2 = new Thread(st);
t2.setDaemon(true);
t2.start();
int i=0;
while(true)
{
if(i++==60)
/*
当i++==60时,即之后i为61,运行st.stop改变标记,跳出循环,main线程结束,
轮到线程0,1运行时,都是先打印一次,再判断标记,为false,跳出循环,结束
线程。可得:线程0,1每次停止的地方都在判断后,因此轮到他们执行时会先打印
一次,再判断失败跳出来。
*/
{
//st.stop();//这一步也可以省略,在异常处理catch中结束循环,因为只要进入catch说明线程被强制唤醒。
//t1.interrupt();
//t2.interrupt();
//由于两个线程进入了冻结状态,如果不强行唤醒,那么改变标记也没用。
break;
}
System.out.println(Thread.currentThread().getName()+"......"+i);
}
}
}
可以看到,我们并没有强制结束程序,但是线程依然自动结束了。
14.守护线程
也叫用户线程,后台线程。
Thread方法setDaemon(true);将一个线程设置为后台线程,当所有正在运行的线程都是后台线程时,jvm结束。
注意:
a.该方法必须在开启线程前执行。
b.当一个进程中,前台线程都结束了,那么该进程也就结束了,不管后台进程的run方法是否结束。
比喻:
前台线程相当于雅典娜们,守护线程相当于圣斗士们,当雅典娜们都over了,那么圣斗士们也就都失业了。
15.小知识点
join方法:
package com.helong.joindemo;
class Join implements Runnable
{
public void run()
{
for(int x=0;x<50;x++)
System.out.println(Thread.currentThread().getName()+"......."+x);
}
}
class JoinDemo
{
public static void main(String[] args) throws Exception
{
Join j=new Join();
Thread t1 = new Thread(j);
Thread t2 = new Thread(j);
//线程main,0,1交替执行。
/*
t1.start();
t2.start();
for(int x=0;x<50;x++)
System.out.println(Thread.currentThread().getName()+"......."+x);
*/
//主线程运行到t1.start()开启线程0,然后可能运行线程0的代码打印,或者继续向下执行t1.join主线程进入冻结,执行
//线程0的代码打印,因此两种可能的结果都一样,那就是先打印完线程0的,然后主线程和线程1交替运行。
/*
t1.start();
t1.join();
t2.start();
for(int x=0;x<50;x++)
System.out.println(Thread.currentThread().getName()+"......."+x);
*/
//主线程创建了线程0,1,然后可能0,1交替运行也可能主线程继续执行t1.join,主线程进入冻结,结果都一样,那就是
//线程0,1交替运行知道线程0结束,主线程被唤醒,此时可能线程1运行结束,那么就是单独运行主线程,或者线程1
//未结束,那么就是主线程和线程1交替运行。
/*
t1.start();
t2.start();
t1.join();
for(int x=0;x<50;x++)
System.out.println(Thread.currentThread().getName()+"......."+x);
*/
//主线程在运行到t1.join时进入冻结状态,当线程0结束,主线程恢复时,又运行t2.join进入冻结,当线程1结束,主线程
//再次恢复运行,知道主线程结束。因此这种情况运行结果是线程0,1交替运行知道两个都结束,再运行主线程。
t1.start();
t2.start();
t1.join();
t2.join();
for(int x=0;x<50;x++)
System.out.println(Thread.currentThread().getName()+"......."+x);
}
}
运行图:
线程优先级:
yield方法(放弃):
16.多线程总结
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------