线程间通信
线程间通信
思考1:wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
1. 这些方法存在与同步中。
2. 使用这些方法时必须要标识所属的同步的锁。
3. 锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
思考2:wait(),sleep()有什么区别?
wait():释放资源,释放锁。
Sleep():释放资源,不释放锁。
wait()和sleep()方法的异同点:
两个方法都可以让线程处于冻结状态。
sleep必须指定时间,wait可以指定时间,也可以不指定。
sleep()方法会释放执行权,不会释放锁。
wait()方法会释放执行权,会释放锁。
线程间通讯:
其实就是多个线程在操作同一个资源,但是操作的动作不同。
多线程(线程间通信-等待唤醒机制)
Wait()
notify();
notifyAll();
都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
synchronized(obj)
{
obj.wait();
}
public synchronized void fun()
{
this.wait();
}
public class MyClass
{
public staticsynchronized void fun1()
{
MyClass.class.wait();
}
}
线程间通信—生产者消费者:
无论是同步函数,还是同步代码块,所做的都是隐式操作。
同步函数或同步代码块,使用的锁和监视器是同一个。
Lock接口:是将锁进行单独对象的封装。而且提供了对锁对象的很多功能。
比如lock()获取锁, unlock()释放锁。
Lock对锁的操作都是显示操作。
所以它的出现要比同步函数或者同步代码块明确的多。而且更符合面向对象思想。
简单一句话:Lock接口的出现替代了同步。
好处:将同步当中的隐式锁对象封装成了显示锁操作。
以前的锁是任意对象,现在的是指定对象Lock,Lock时必须写finally。
//生产者
class Producer implements Runnable
{
privateResource r;
Producer(Resourcer)
{
this.r= r;
}
publicvoid run()
{
while(true)
{
r.set("馒头");
}
}
}
//消费者
class Consumer implements Runnable
{
privateResource r;
Consumer(Resourcer)
{
this.r= r;
}
publicvoid run()
{
while(true)
{
r.get();
}
}
}
原来在同步中,锁和监视器是同一个对象。
现在升级后,锁是一个单独的对象。
而且将监视器的方法也单独封装到了一个对象中,这个对象就是升级后的condition,升级后,都进行了单独的封装。
锁被封装成了Lock对象。
监视器方法都被封装到了Condition对象(监视器对象)中。
说白了,Lock替代了同步中的同步,Conditon替代了Object中的监视器方法。
Condition中提供了监视器的方法:await(),signal(),signalAll()。
如何让锁和监视器产生联系?
直接通知Lock接口中的newCondition()方法就可以获取到能绑定到该Lock对象上的监视器对象Condition。
代码:
class ProConDemo3
{
publicstatic void main(String[] args)
{
Resourcer = new Resource();
Producerpro = new Producer(r);
Consumercon = new Consumer(r);
//两个线程负责生产。
Threadt0 = new Thread(pro);
Threadt1 = new Thread(pro);
//两个线程负责消费。
Threadt2 = new Thread(con);
Threadt3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
对于多个生产者和消费者。
为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。
为什么定义notifyAll?
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
JDK1.5中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
将Object中的wait,notify notifyAll,替换了condition对象。
该对象可以Lock锁进行获取。
该示例中,实现了本方只唤醒对方操作。
线程间通讯总结:
使用wait()、notify()、notifyAll()方法可以完成线程间的通讯,可叫它们通讯方法;
只能在同步环境下调用通讯方法;
只能使用监视器对象调用通讯方法;
每个监视器对象都有一个线程*:执行a.wait()的线程会被关押到a对象的线程*中;
若想释放出a对象的线程*中的线程,那么需要调用a.notify()文法,该方法只能保证在a对象的线程*中释放出一个线程,但不能保证释放的是哪一个;
还可以使用a.notifyAll()方法释放出a对象的*中关押的所有线程。
被wait()了的线程不能自己恢复到就绪状态,只能等待其他线程调用同一监视器对象上的notify()或notifyAll()方法来唤醒。
被wait()了的线程会释放监视器对象的对象锁,这样其他线程就可以进入他占用的同步环境。
被唤醒的线程恢复到了就绪状态,当再次获取监听器对象的锁后会在wait()处向下运行。
停止线程
1.定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即可。
2.使用interrupt(中断)方法。
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。
stop方法已经过时。
如何停止线程?
只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。
只要控制循环,就可以让run方法结束,也就是线程结束。
public void run()
{
while(true)
{
System.out.println("ok");
}
}
特殊情况:
当线程处于了冻结状态,就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
Thread类提供该方法interrupt();
如果读不到标记怎么办?
比如在任务中让线程处于了冻结状态。
释放了执行资格,无法执行标记,run方法没结束,线程也无法结束。
Thread类中有一个interupt方法。可以将线程的冻结状态清除,让线程恢复到具备执行资格。
例:
class StopThread implements Runnable
{
private booleanflag = true;
publicsynchronized void run()
{
while(flag)
{
try{
wait();
}catch (InterruptedException e){
System.out.println(Thread.currentThread().getName()+"...."+e.toString());
flag = false;
}
System.out.println(Thread.currentThread().getName()+"......run");
}
}
public voidsetFlag()
{
flag = false;
}
}
class StopThreadDemo
{
public staticvoid main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
while(true)
{
if(++num==50)
{
// st.setFlag();//将标记置为false。让t1,t2对应的线程结束。
//清除t1的冻结状态。
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"....."+num);
}
System.out.println("over");
}
}
使用interrupt()方法:
中断方法:interrupt()
Thread类中有一个boolean类型的属性,我们叫中断值,这个中断值默认为false。
当调用了线程对象的interrupt()方法后,这时中断值为true。
所有的声明了InterruptedException异常的方法都会在中断值为true时抛出这个异常。
Thread.sleep()
Object.wait()
Thread.join()
当这些方法抛出异常之后,中断值会恢复到false。
isInterrupted()方法和interrupted()的作用:
isInterrupted() –实例方法
获取该线程的中断状态,该方法不会去修改中断值,只是对中断值进行读操作。
interrupted() –静态方法
获取当前线程的中断状态,该方法会在获取中断值之后,把中断值修改为false。然后返回修改之后获取到的值。boolean b =中断值;中断值=false;return b。
interrupt() –实例方法
把当前中断值设置为true。
setDaemon()将线程标记为守护线程或用户线程(后台线程),一定要在start之前调用。
多线程(Join方法)
当A线程执行到了B线程的join()方法时,A就会等待,等B线程都执行完,A才会执行。
Join可以用来临时加入线程执行。
join() 等待该线程终止。当前线程等待调用此方法的线程执行完才执行。
凡是让线程处于中断的方法都需要抛出 InterruptedException异常。
join()方法的作用:
package cn.itcast.thread.join;
public class Test
{
public static void main(String[] args) throws InterruptedException
{
Thread th = new Thread()
{
public void run()
{
for(int i = 0; i < 10; i++)
{
System.out.println(i);
try {
Thread.sleep(500);
}catch(InterruptedException e) {}
}
}
}
th.start();
th.join(); //让当前线程(主线程)等待该线程(th)结束,再向下执行。
System.out.println("程序结束!!!");
}
}
让当前线程等待该线程结束再向下运行。
当前线程:语句是谁执行的,谁就是当前线程。
该线程:你调用了哪个线程对象的方法,哪个线程就是该线程。
多线程(优先级与yield方法)
yield()临时暂停线程,其为静态方法。
yield()方法的作用:
Thread.yield(); //暂停当前正在执行的线程对象,并执行其他线程。
当前线程执行了这条语句,表示当前线程已经完成最重要的部分,然后跟调度说,我可以休息了,你可以去为其他线程服务了,但当前线程不会进入阻塞状态,还是就绪状态。
让步不同与阻塞,让步之后,线程还是就绪状态。