多线程提高、Lock、停止线程

上文中——同步代码块和同步函数主要解决:多线程做相同的事(调用同一个run函数)


本文通过生产者和消费者问题(1对1,多对对)探讨多线程同步问题。

1.线程间通信
多线程在处理同一资源,但任务却不同

2.obj.wait(),obj.notify(),obj.notifyAll()
1)必须存在synchronized(Obj)的同步代码块或同步函数中。(因为必须标示wait,notify等方法所属的锁)
2)锁可以是任何对象,这些方法定在在Object中。(方法必须标示锁,锁可以是任意对象,任意对象可以调用的方法必然是Object类中的方法)
3)wait:线程在获取对象锁后,通过wait方法主动释放对象锁,同时本线程阻塞。直到有其它线程调用该对象锁的notify()唤醒该线程,才能继续获取对象锁,并继续执行。(线程存入线程池)。
4)notify:相当于对象锁的唤醒操作。调用后,并不是马上就释放对象锁,而是在相应的synchronized(){}语句块执行结束后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程(★★★唤醒的线程继续执行,而不是重新执行)。

2.wait()与sleep()区别
1)前者释放cpu执行权并释放锁,后者释放cpu执行权,不释放锁

2)wait可以指定时间也可以不指定,sleep必须指定

3.例子
两个线程同时操作对象,先输入然后输出,循环显示
Person.java

[java]  view plain copy
  1. package mypackage;  
  2. public class Person {  
  3.     String name;  
  4.     String sex;  
  5.     boolean flag;  
  6. }  
input.java
[java]  view plain copy
  1. package mypackage;  
  2. public class Input implements Runnable {  
  3.     Person p;  
  4.   
  5.     public Input(Person p) {  
  6.         this.p = p;  
  7.     }  
  8.     public void run() {  
  9.         int x = 0;  
  10.         while (true) {  
  11.             synchronized (p) {  
  12.                 if(p.flag){  
  13.                     try {  
  14.                         p.wait();  
  15.                     } catch (InterruptedException e) {                        
  16.                         e.printStackTrace();  
  17.                     }  
  18.                 }  
  19.                       
  20.                 if (x % 2 == 0) {  
  21.                     p.name = "Tom";  
  22.                     p.sex = "male";  
  23.                 } else {  
  24.                     p.name = "Lily";  
  25.                     p.sex = "female";  
  26.                 }  
  27.                 p.flag=true;  
  28.                 p.notify();  
  29.             }  
  30.             x++;  
  31.         }  
  32.     }  
  33. }  
Out.java
[java]  view plain copy
  1. package mypackage;  
  2. public class Out implements Runnable {  
  3.     Person p;  
  4.     public Out(Person p) {  
  5.         this.p = p;  
  6.     }  
  7.     public void run() {  
  8.         while (true) {  
  9.             synchronized (p) {  
  10.                 if (!p.flag) {  
  11.                     try {  
  12.                         p.wait();  
  13.                     } catch (InterruptedException e) {                        
  14.                         e.printStackTrace();  
  15.                     }  
  16.                 }  
  17.                 System.out.println(p.name + "..." + p.sex);  
  18.                 p.flag = false;  
  19.                 p.notify();  
  20.             }  
  21.         }  
  22.     }  
  23. }  
Demo.java
[java]  view plain copy
  1. import mypackage.*;  
  2. public class Demo {  
  3.     public static void main(String[] args)   
  4.     {  
  5.         Person p=new Person();            
  6.         Input in=new Input(p);  
  7.         Out out=new Out(p);  
  8.           
  9.         Thread t1=new Thread(in);  
  10.         Thread t2=new Thread(out);  
  11.           
  12.         t1.start();  
  13.         t2.start();       
  14.     }  
  15. }  
4.优化Person类
[java]  view plain copy
  1. public class Person {  
  2.     private String name;  
  3.     private String sex;  
  4.     private boolean flag;  
  5.       
  6.   public synchronized void input(String name,String sex){  
  7.         if(flag)  
  8.             try {  
  9.                 this.wait();  
  10.             } catch (InterruptedException e) {                
  11.                 e.printStackTrace();  
  12.             }  
  13.         this.name=name;  
  14.         this.sex=sex;  
  15.         flag=true;  
  16.         notify();  
  17.     }  
  18.       
  19.     public synchronized void out(){      
  20.         if(!flag)  
  21.             try {  
  22.                 this.wait();  
  23.             } catch (InterruptedException e) {  
  24.                 e.printStackTrace();  
  25.             }  
  26.         System.out.println("name:"+name+",sex:"+sex);  
  27.         flag=false;  
  28.         notify();  
  29.     }  
  30. }  

单个生产者-单个消费者





5.多个线程同时输入,多个线程同时输出情况下,引发异常(★★★)


但是如果变为多个生产者-多个消费者时就会出现下面问题




因为线程被唤醒后从挂起地方开始接着执行。一个生产者-一个消费者时,生产者被唤醒是不用再判断盘子是否还有食物,因为是消费者唤醒它的。

但是在多生产者-多消费者时,this锁中有生产者又有消费者,消费者使用notify有可能唤醒的仍然是消费者,造成死锁。所以我们就唤醒this锁中的全部线程。但是唤醒全部线程时如果不使用while再次判断,而是if已经判断过接着执行,那么有可能别的生产者又开始生产,导致接连生产(2-n)个(n是生产者线程个数)但是没有消费。所以:

总结:在多生产者多消费者是必须使用while()判断条件。(notifyAll影响程序执行效率)




在main函数中,创建两个线程t0与t1,执行输入操作;创建两个线程t2与t3,执行输出操作。
分析:t0执行后阻塞-->t1阻塞-->t2执行后阻塞,唤醒t0-->t3阻塞(仅t0运行)--> t0输入数据后,并没有输出(异常!!!),唤醒t1,再次输入。
原因:if判断只执行一次,在t1阻塞后重新得到执行权,不会再次判断是否要运行,导致不该运行的线程运行,导致数据异常。

解决:将if换成while,解决判断是否运行,但会导致死锁,可将notify换成notifyAll解决。




6.Lock接口   用于替代synchronized,旧锁换新锁


1)提供了比使用synchronized方法和语句更广泛的锁定操作,允许更灵活的结构,支持多个Condition对象(支持多个监视器对象),Condition代替Object中的wait,notify,notifyAll。

2)将synchronized隐式实现转变为Lock对象的显示实现

3)使用
Lock l = 实现;
 l.lock();//获得锁(上锁)
 try

 {
 } 
finally {
      l.unlock();//释放锁(解锁)
 } 

 4)演示
[java]  view plain copy
  1. import java.util.concurrent.locks.*;  
  2. public class Product {  
  3.     String name;  
  4.     int Id;  
  5.     boolean flag;  
  6.     int no = 1;  
  7.     // 创建锁对象  
  8.     Lock l = new ReentrantLock();  
  9.     // 通过已有锁对象创建该锁上的监视器对象,一个监视生产者,一个监视销售者  
  10.     Condition in = l.newCondition();  
  11.     Condition out = l.newCondition();  
  12.       
  13.     void input(String name) {  
  14.         l.lock();  
  15.         try {  
  16.             while (flag)  
  17.                 try {  
  18.                     in.await();  
  19.                 } catch (InterruptedException e) {  
  20.                     e.printStackTrace();  
  21.                 }  
  22.             this.name = name;  
  23.             this.Id = no++;  
  24.             System.out.println("生产:" + name + "," + Id);  
  25.             flag = true;  
  26.             out.signal();  
  27.         } finally {  
  28.             l.unlock();  
  29.         }  
  30.     }  
  31.     void out() {  
  32.         l.lock();  
  33.         try {  
  34.             while (!flag)  
  35.                 try {  
  36.                     out.await();  
  37.                 } catch (InterruptedException e) {  
  38.                     e.printStackTrace();  
  39.                 }  
  40.             System.out.println("销售:" + name + ",id:" + Id + ",继续加油");  
  41.             flag = false;  
  42.             in.signal();  
  43.         } finally {  
  44.             l.unlock();  
  45.         }  
  46.     }  
  47. }  





以前每个锁对象一出现都有一组监视器wait() notify()notifyAll()

现在锁对象与监视器分离开,监视器都放在conditionawait(),signal().signalAll())接口用的时候将锁对象和监视器联系起来就行(每个锁对象可以有多个监视器)



因为唤醒对方进程,以前不能实现,我们通过唤醒所有进程的办法实现,这就造成了程序效率很低,(唤醒对方进程中只有一个能够执行,但是唤醒所有其他进程不满足的又会重新挂起,影响效率)

 

但是使用了condition接口后,我们的一个锁对象却可以定义两个监视器。这样就可以只唤醒对方线程中的一个。




7.停止线程
1)stop方法,涉及安全问题,已过时
2)run方法结束,通常任务中会有循环结构,控制循环标记,但如果线程处于堵塞状态,无法读取标记,则无法结束
3)interrupt方法,将线程从阻塞状态强制恢复到运行状态来,让线程具备cpu执行资格,但会发生InterruptException,可以在catch中处理循环标记

8.守护线程
setDaemon方法必须在启动线程前调用,当正在运行的线程都是守护线程时(),Java 虚拟机退出。
非守护线程可以理解为前台线程,守护线程守护前台线程,前台线程都结束,守护线程也结束。

9.join
当前线程等待该线程终止,后运行

10.经典面试题
3个线程顺序打印10次ABC

11.获取线程状态方法getState() 

对应枚举 Thread.State(6种状态)

12.获取当前程序所有线程
[java]  view plain copy
  1. ThreadGroup topGroup = null;  
  2. ThreadGroup group = Thread.currentThread().getThreadGroup();  
  3. // 遍历线程组树,获取根线程组  
  4. while (group != null) {  
  5.     topGroup = group;  
  6.     group = group.getParent();  
  7. }  
  8. // 此线程组中活动线程的估计数  
  9. int estimatedSize = topGroup.activeCount();  
  10. System.out.println("active:" + estimatedSize);  
  11. Thread[] slackList = new Thread[estimatedSize];  
  12. // 获取根线程组的所有线程  
  13. int actualSize = topGroup.enumerate(slackList);  
  14. //遍历  
  15. for (Thread t : slackList) {  
  16.     System.out.println("ID:" + t.getId() + ",Name:" + t.getName()  
  17.             + ",isAlive:" + t.isAlive());  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值