1.1 意义:多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。
此时输入输出都要上锁,而且要保证是同一个锁。
1.2 等待/唤醒机制涉及的方法:
1、wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
2、notify():唤醒线程池中的一个线程(任何一个都有可能)。
3、notifyAll():唤醒线程池中的所有线程。
1.3注意:
1、这些方法都需要定义在同步中。
2、这些方法必须要标示所属的锁。
A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。
3、这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,锁可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。
1.4wait和sleep区别:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
sleep:线程会释放执行权,但不是不释放锁。
wait是定义在Object中的方法,sleep是线程中的方法。
2. 生产者消费者示例
public class H_04ThreadWaitNotify
{
public static void main(String[] args)
{
Reso r=new Reso();//创建资源对象
new Thread(new Input(r)).start();//匿名创建线程
new Thread(new Output(r)).start();//匿名创建线程
}
}
class Reso
{
private Stringname;
private Stringsex;
private boolean flag=false;
public synchronized void set(String name,String sex)//锁是this
{
if(this.flag)
try
{
this.wait();//使用时要指定锁
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
this.name=name;
this.sex=sex;
this.flag=true;
this.notify();
}
public synchronized void out()//锁是this
{
if(!this.flag)
try
{
this.wait();
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
System.out.println(name+"......."+sex);
this.flag=false;
this.notify();
}
}
class Input implements Runnable
{
private Reso r;
Input(Reso r)
{
this.r=r;
}
public void run()
{
int x=0;
while(true)
{
if (x==0)
r.set("mike","man");
else
r.set("lili","女");
x=(x+1)%2;
}
}
}
class Output implements Runnable
{
private Reso r;
Output(Reso r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
结果(不停打印)
mike.......man
lili.......女
mike.......man
lili.......女
3. 多生产者—消费者问题
代码
public class H_05ProductDemo
{
public static void main(String [] args)
{
Resouce r=new Resouce();
new Thread(new Producer(r)).start();
new Thread(new Consumer(r)).start();
new Thread(new Producer(r)).start();
new Thread(new Consumer(r)).start();
}
}
class Resouce
{
private String name;
private int count=1;
private boolean flag=false;
public synchronized void set (String name)
{
if(flag)
{
try{this.wait();}
catch(Exceptione){}
}
this.name=name+"---"+count++;//名字+编号
System.out.println(Thread.currentThread().getName()+"_______....生产者..._______"+this.name);
flag=true;
this.notify();
}
public synchronized void out ()
{
if(!flag)
{
try{this.wait();}
catch(Exceptione){}
}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag=false;
this.notify();
}
}
class Producer implements Runnable//生产者调用set方法
{
private Resouce res;
Producer(Resouce res)
{
this.res=res;
}
public void run()
{
while(true)
{
res.set("+商品+");
}
}
}
class Consumer implements Runnable//消费者调用out方法
{
private Resouce res;
Consumer(Resouce res)
{
this.res=res;
}
public void run()
{
while(true)
{
res.out();
}
}
}
部分结果
Thread-2_______....生产者..._______+商品+---66093
Thread-0_______....生产者..._______+商品+---66094
Thread-3...消费者...+商品+---66094
Thread-2_______....生产者..._______+商品+---66095
Thread-0_______....生产者..._______+商品+---66096
Thread-3...消费者...+商品+---66096
Thread-2_______....生产者..._______+商品+---66097
Thread-0_______....生产者..._______+商品+---66098
Thread-3...消费者...+商品+---66098
线程2生产了66093,flag设为true,2继续执行,2等待。分析:(0、2生产,1、3消费)
线程0(之前等待)生产了66094,flag设为true,线程0等待。
线程3获得了执行权,消费了66094,flag设为false,3等待,唤醒线程2,
线程2无需判断,生产了66095,flag为true,线程2等待,并唤醒了线程0.
线程0也无需判断,生产了66096,flag为true,线程0等待,唤醒了线程3.
原因:
由于if判断,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。故修改成while判断,线程获取CPU执行权及锁后,将重新判断是否具备运行条件。
notify方法只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。notifyAll解决了本方线程一定会唤醒对方线程的问题。
while+notify的死锁分析
线程0生产了。001,flag为true,0继续执行,0等待
线程2判断,flag为true,2等待。
线程1获取了执行权和锁,消费了001,flag设为false,线程1等待,唤醒了线程0,但是线程0无锁。
线程3执行,flag为false,线程3等待。此时1、2、3均等待。
线程0获得了锁,生产了002,flag设为true,唤醒了2,但flag为true,所以2等待。0继续执行,0等待。
此时所有线程都在等待,形成死锁。
故应该使用while+notifyAll,每次判断并唤醒其他所有线程,避免数据出错和死锁。
4. Lock和Condition
jdk1.5 关于多线程升级解决方案:
4.1 将同步synchronized替换成了显式Lock操作。
4.2 将Object中的wait,notify,notifyAll,替换成了condition对象。
4.3 Condition对象可以通过Lock锁获取。
4.4 同一个锁创建不同的Condition对象,同一个Condition对象分别在a方法中等待,在b方法中唤醒。
这样可以实现在本方法中唤醒对方操作,解决了notify可能唤醒本方操作无意义的问题。
注意:释放锁动作一定要执行,要放在finally中。
替换后的主要代码:
class Resouce2
{
private Stringname;
private int count=1;
private boolean flag=false;
private Locklock=new ReentrantLock();//创建一个锁
//Lock是接口,所以用子类创建
private Conditioncondition_pro=lock.newCondition();//创建不同的对象
private Conditioncondition_con=lock.newCondition();
public void set (String name)
{
lock.lock();//显式加锁
try
{
while(flag)
condition_pro.await();//通过Condition对象调用
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"_______....生产者..._______"+this.name);
flag=true;
condition_con.signal();//唤醒对方线程(与con对应)
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
finally
{
lock.unlock();//一定要释放锁
}
}
public void out ()
{
lock.lock();
try
{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag=false;
condition_pro.signal();//唤醒对方
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
}
替换方式:
建立锁对象
建立不同的Condition对象
去掉原有的synchronized
使用Condition对象的wait方法
使用另一个Condition对象的signal方法。
释放锁。
5. 线程的停止
5.1 stop方法已经过时。
5.2 如何停止线程?
只有一种方法:run方法结束,
开启多线程运行,运行代码通常是循环结构,
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于冻结状态,就不会读取标记,那么线程就不会结束。
5.3 清除冻结状态
当没有指定的方式让冻结的线程恢复到运行状态时,这是需要对冻结状态进行清除。
强制让线程恢复到运行状态汇总来,这样就可以操作标记让线程结束。
Thread提供了interrupt方法。
public class H_07StopThread
{
public static void main(String[] args)
{
StopTest st=new StopTest();
Thread t1=new Thread(st);
Thread t2=new Thread(st);
t1.start();//wait
t2.start();//wait
intnum=0;
while(true)
{
if(num++==60)//主线程运行到num=60 main...60
{
t1.interrupt();//打断wait运行Thread-0...run
t2.interrupt();//打断wait运行Thread-1...run
break;
}
System.out.println(Thread.currentThread().getName()+"......"+num);
}
}
}
class StopTest implements Runnable
{
private boolean flag=true;
public synchronized void run()
{
while(flag)
{
try
{
wait();
}
catch (InterruptedExceptione)
{
flag=false;//在catch中处理
}
System.out.println(Thread.currentThread().getName()+".....run");
}
}
public void changeflag()
{
flag=false;
}
}
6. 多线程的其他方法
6.1 setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
Stop st=new Stop();
Thread t1=new Thread(st);
Thread t2=new Thread(st);
t1.setDaemon(true);//运行前执行,当只剩下守护线程时,jvm退出
t2.setDaemon(true);
t1.start();
t2.start();
intnum=0;
while(true)//当主线程运行结束,只剩下守护线程,jvm退出
{
if(num++==60)
{
st.changeflag();
break;
}
System.out.println(Thread.currentThread().getName()+"......"+num);
}
6.2 join:临时加入一个线程的时候可以使用join方法。
当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。
6.3 Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
setPriority(int newPriority):更改线程的优先级。
getPriority():返回线程的优先级。(默认是5)
public class H_09JoinThreadDemo
{
public static void main(String[] args) throws Exception
{
Demo d=new Demo();
Thread t1=new Thread(d);
Thread t2=new Thread(d);
t1.start();
t1.join();//t1强制获得执行权
//t1.setPriority(Thread.MAX_PRIORITY);//静态常量
t2.start();
t2.setPriority(Thread.MIN_PRIORITY);
//t1.join();
for(inti=0;i<80;i++)//主线程在执行
{
System.out.println(Thread.currentThread().toString()+"...."+i);
}
}
}
class Demo implements Runnable
{
public void run()
{
for(inti=0;i<70;i++)
{
System.out.println(Thread.currentThread().toString()+"...."+i);
Thread.yield();//每次运行就切换到另外一个线程执行
}
}
}
结果:
线程0先执行完(强制插入)
线程1和main线程穿插执行(线程1的优先级显示为1)