导读:线程间通信-示例代码,等待唤醒,多线程的优化,生产者消费者,守护线程,join()方法,优先级&yield方法
1、 线程间通信-示例代码
l 什么叫线程操作的代码,赋值语句(第一个线程中)是,输出语句(第二个线程)也是。他们都在操作同一个资源,都是对于同一个资源的代码处理,因此都要放在同步当中。虽然在两个run()方法中,但是要处理同一个资源。
2、 线程通信-解决安全问题
3、 线程通信-等待唤醒机制(开发中常见)
l 一大片一大片男,一大片一大片女,是因为当输入一个名字和一个性别后,因为线程可能还是让input获得了执行权。将前一个内容给覆盖了。当某个时刻Output获得线程后,也可能输出多条语句。这由CPU的切换决定。为了输入一个,输出一个,可能加入一个标记flag,默认值设为false。输入之前先判断这里面有没有值啊,没有的话,再存入。如果有值的话,将false改为true,代表里面有数据了。当input再拿到时cpu的时候,一判断为true,就要等在那不要动了用sleep(),可是sleep的时间不确定啊。什么时候才能醒啊,当我把数据取走了之后,才能醒。最好的办法应该是wait();我叫你的时候你再动。当Output拿到执行权的时候要也做一下判断,如果为真,可取。打印完之后,设置flag为false。Output在wait()之前要将Input给notify();
l 等待的进程在哪里呢?线程运行的时候,线程中会建立一个线程池,等待进程都存在这个线程池当中。当notify的时候唤醒的都是线程池中的线程。notify()一般唤醒第一个被等待的
例:小时候的游戏“冰棍化了”
nogifyAll():唤醒线程池中的所有的。
l wait(),notify(),notifyAll(),他们是操作线程的怎么跑到上帝(Object类)里面去了呢?
Ø 因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
Ø 锁可以是任意类型,任意对象能调用能的方法应该定义在哪里?上帝里面。使用wait()的时候抛异常了,InterruputedException。想用wait()只能try一下。这三种方法全用在同步里面。wait(),notify()你必须标识出,一个线程要所持有的锁。如,try{r.wait();}catch(Exceptione){}因为同步会出现嵌套,两个锁。
l //线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。
class Res
{
String name;
String sex;
boolean flag = false;
}
class Inputimplements Runnable
{
private Res r ;
Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
synchronized(r)
{
if(r.flag)
try{r.wait();}catch(Exceptione){}
if(x==0)
{
r.name="mike";
r.sex="man";
}
else
{
r.name="丽丽";
r.sex ="女女女女女";
}
x= (x+1)%2;
r.flag = true;
r.notify();
}
}
}
}
class Outputimplements Runnable
{
private Res r ;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
synchronized(r)
{
if(!r.flag)
try{r.wait();}catch(Exceptione){}
System.out.println(r.name+"...."+r.sex);
r.flag = false;
r.notify();
}
}
}
}
class InputOutputDemo
{
public static void main(String[] args)
{
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
4、 多线程的优化
class Res
{
private String name; //设置为私有的变量
private String sex;
private boolean flag = false;
public synchronized void set(Stringname,String sex) //将同步放在了这里
{
if(flag)
try{this.wait();}catch(Exceptione){}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out() //将同步放在了这里
{
if(!flag)
try{this.wait();}catch(Exceptione){}
System.out.println(name+"........"+sex);
flag = false;
this.notify();
}
}
class Inputimplements Runnable
{
private Res r ;
Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
if(x==0) //将这里的同步移到了Res中去了。
r.set("mike","man");
else
r.set("丽丽","女女女女女");
x = (x+1)%2;
}
}
}
class Outputimplements Runnable
{
private Res r ;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class InputOutputDemo2
{
public static void main(String[] args)
{
Res r = new Res();
new Thread(new Input(r)).start(); //将6条语句,变以2条语句
new Thread(new Output(r)).start();
}
}
5、线程间通信,生产者消费者。
l t1把本方的给换醒了。因为线程池中顺序中t1、t2、t3、t4当t4唤醒t1后,t1执行完成后,把t2(本方,生产方)给唤醒了。当t2醒的时候,它没有去看这个标记。而这个标记已经被替之为真了,t1已经往里面存了数据了,t2却不知道。t2又生产了了个,这就导致了生产了2个。我们怎样让它每次醒的时候都回去判断标记呢。If只判断一次,可是如果变为while的时候就可以判断多次。
l 死锁和全部等待的区别是:
死锁:我在问你要,你在问我要,互相不放。等待是哥几个谁都动不了,都不是活着的,都冻结了。要怎么办呢,用notifyAll()即唤醒本方,也把对方唤醒。唤醒本方,一循环,它继续等。可是对方t3,t4全醒了。
l 当出现多个生产者消费者都在做这件事的时候。要写while循环,要写notifyAll(),这种方式是一种比较通用的方式。
/*
对于多个生产者和消费者。为什么要定义while判断标记?原因:让被唤醒的线程再一次判断标记。
为什么定义notifyAll?因为需要唤醒对方线程。因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
*/
classProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
// t1 t2
public synchronized void set(String name)
{
while(flag)
try{this.wait();}catch(Exceptione){}//t1(放弃资格) t2(获取资格)
this.name =name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
flag = true;
this.notifyAll();
}
// t3 t4
public synchronized void out()
{
while(!flag)
try{wait();}catch(Exceptione){}//t3(放弃资格) t4(放弃资格)
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag = false;
this.notifyAll();
}
}
class Producerimplements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.set("+商品+");
}
}
}
class Consumerimplements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.out();
}
}
}
6、JDK5.0升级后的新特性
l 只唤醒对方不唤醒本方要怎么去做呢?
Java.util.concurrent.locks
接口Lock ,实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition 对象。其中有方法lock():获取锁,unlock释放锁。程序中的锁的隐式的,可以如果用lock()和unlock()的话,就变成了显式的。
l 接口Condition,将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。JDK1.5以后,synchronized挂了,被Lock给我替代了。Object中的wait,notify,notifyAll方法也挂了,被condition给替代了。它中的方法await(),signal()唤醒一个等待线程,signalAll()唤醒所有等待线程。
l Wait(),notify()这样的语句都要放在同步语句块当中。每个wait(), notify()都要标识出自己所属的锁。Condition可以帮我们建立一个锁。用Lock中的newCondition()方法创建Condition。通过锁产生一个wait(),notify()这们的实例对象。
l 还有一个问题,如果在condition.await()这里,真的抛出异常了,那么放锁的没有?没有。线程还在,我还拿着锁,但是锁我没有放,这时候别人进不来。Lock.unlock()一定要执行,所以放在finally中,释放锁(资源)。
l 用JDK的新技术特性,可以建立多个锁。并只唤醒其中的某一个锁。如,在out()方法中t3,t4等待,只唤醒t1和t2中的一个。condition_pro.wait();只能被condition_pro.signal();唤醒,condition_con.wait();只能被condition_con.signal();唤醒。
l 其实程序中就多了三句,牛就牛在哪呢,一个锁中可以绑定多个Condition对象,同步中只能绑定一个,锁如果一嵌套就发生了死锁。
l 生产者消费者有什么替代方案呢,1.5以后它提供了显式的锁机制。以及显式的锁对象上的等待唤醒操作机制,同时把等待唤醒里进行封装,封装完后一个锁对应多个Condition。没有之前一个锁对应几个wait()和notify()?一个。再建锁就再需要进行同步。两个形成嵌套,容易形成死锁。
l /*
JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。将Object中的wait,notify notifyAll,替换了Condition对象。该对象可以Lock锁进行获取。该示例中,实现了本方只唤醒对方操作。
Lock:替代了Synchronized
lock
unlock
newCondition()
Condition:替代了Object waitnotify notifyAll
await();
signal();
signalAll();
*/
importjava.util.concurrent.locks.*; //要导入包
classProducerConsumerDemo2
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
// t1 t2
private Lock lock = newReentrantLock(); //只多了这三条语句
private Condition condition_pro =lock.newCondition();
private Condition condition_con =lock.newCondition();
public void set(String name)throws InterruptedException
{
lock.lock();
try
{
while(flag)
condition_pro.await();//t1,t2
this.name= name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
flag = true;
condition_con.signal();
}
finally
{
lock.unlock();//释放锁的动作一定要执行。
}
}
// t3 t4
public void out()throws InterruptedException //会抛出异常
{
lock.lock();
try
{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag = false;
condition_pro.signal();
}
finally
{
lock.unlock();
}
}
}
class Producerimplements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
Try //这里对于捕获的异常进行捕获。
{
res.set("+商品+");
}
catch (InterruptedExceptione)
{
}
}
}
}
class Consumerimplements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch (InterruptedExceptione)
{
}
}
}
}
7、多线程中停止线程
l Thread()中有一个方法,stop(),它已经过时,但不能清除了,才程序员还用过,老程序中还有。它有一个bug,可以让程序强行的停止,无论程序是在什么状态。
l /*
Ø 如何停止线程?
stop方法已经过时。因此只有一种,run方法结束。开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
Ø 特殊情况:
Ø 当线程处于了冻结状态。就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法 interrupt();
*/
class StopThreadimplements Runnable
{
private boolean flag =true;
public synchronized void run()
{
while(flag)
{
try
{
wait();
}
cathch(InterruptedException e)
{
System.out.println(Tread.currentThread().getName()+"……Exception");
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()
{
flag = false;
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
//st.changeFlag();
//t1.interrupt();
//t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
l 有一种特殊的情况程序也停不下来:
两个线程开启的时候,无论什么时候抢到cpu执行权,都到publicsynchronized void run()中去运行,线程0一进来,wait()了,放弃了资格,线程1一进来,也释放的资格。主线程已经执行完了。我改变了标记但是没有结束线程。
l 在Tread类中提供了一种方法,叫做interrupt()中断线程。它将处于冻结状态的线程,强制的恢复到运行中来。只有运行才能再读标记。(wait(),notify(),sleep()都是正常回到运行状态。)例子:催眠师,wait(),wait()给你催眠了,可是他出国了。我来了,也不会唤醒,一砖头下去,哦了。但有一个问题受伤了(出异常了)。
l interrupt()方法:线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int)方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException。(中断状态决不是中断线程,stop()方法才是中继线程。强制结束你的挂起状态,让你回到运行中来)。
8、守护线程
l Thread的方法。public final void setDaemon(booleanon),将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
l 守护线程(用户线程):你所看到的线程都是前台线程,当你把某些线程标记成后台线程后,它就有了一个特殊的含义。后台线程开启后 ,和前台线程,共同抢占CPU的执行权,运行。开启运行都没有区别,就结束后有区别。当所有的前台线程结束后,后台线程会自动结束有后台依赖前台的意思。我在你在,我不在,你自动结束不用刻意结束。这就是后台线程的特点。主线程是前台线程,前台线程一结束,后台线束自动结束。
l 举例:《圣战士星矢》,雅典娜是前台线程,他们兄弟五个就是后台线程。雅典娜一挂,他们五个就失业了。
9、多线程Join方法
l public final voidjoin() throws InterruptedException等待该线程终止。
主线程在往下走的时候,当读到t1.join()的话,表示t1要申请加入到运行中来,更具体一些是t1要CPU执行权。Join的意思就是要抢夺CPU的执行权。这时候执行权是CPU的,CPU把执行权放在那了,这时主线程处于冻结状态了。主线程不会再跟t1抢CPU执行权了。当t1执行完成后,主函数才回到运行状态中来。
l 作用:当我们在进行一个多线程运算的时候,一个线程可以在运行的过程中,加入一个线程(如果满足条件的话),这个加入的线程运行完成后,另外一个线程再继续运行。
l 主线程遇到谁的Join它就等谁。如,t1.start();t1.join(); t2.start;主线程执行àt1执行à主线程和t2交替执行。t1.start();t2.start;t1.join();主线程执行àt1和t2交替执行à主线程和t2交替执行。
l 如果t1用了join方法,但是t1又wait()在那里了,可以用interruput强行使主线程活过来,这时候会抛出异常。
10、优先级&yield方法
l toString()方法,返回该线程的字符串表示形式,包括线程名称、优先级和线程组。System.out.println(Thread.curentThread().toString());会打印出Thread[Thread-1,5,main]表示Thread1,优先级是5,所属的组是main这个组。
Ø 线程组:一般的谁开启你的你就属于哪一个组。主线程开启了t1和t2。你不想属于这个组,你就new一个ThreadGroup对象,把你想封装的线程封装到这个组中就可以了。线程组,几乎用不上,用起来还挺麻烦的。
l setPriority(int newPriority),更改线程的优先级。所有线程,包括主线程,默认优先级是5。优先级差别小的话也看不出来,在这10个优先级跨度最大的是1、5、10。所以将它们起了一个值是MAX_PRIORITY,MIN_PRIORITY, NORM_PRIORITY。这三个名字是static常量值。直接用数字的话,可读性不好。
l yield(),暂停当前正在执行的线程对象,并执行其他线程。(临时停止)线程在读到yield()后就释放了执行权了。稍微间缓线程执行的频率。如果不释放执行权为出现什么状况,t1线程会连续输出好几次。达到所有线程都能达到平均运行的效果。
l 多线程匿名类写法示例:
class ThreadTest
{
public static void main(String[] args)
{
new Thread() //用匿名类创建一个线程。技巧型的写法。
{
public void run()
{
for(int x=0;x<100; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
}.start();
for(int x=0; x<100; x++) //for在主线程中运行。
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
Runnable r = new Runnable() //创建另一个线程,另一种写法。
{
public void run()
{
for(int x=0;x<100; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
};
new Thread(r).start();
}
}
有了多线程后 ,什么时候用多线程?
当某些代码要求同时被执行时,就用单独的线程进行封装。
------- android培训、java培训、期待与您交流! ----------