黑马程序员——java基础---多线程(二)
线程间的通信:简单来说,就是多个线程在操作同一资源,但操作的动作不同。
试想一下,对于同一个资源做不同的操作,这势必会在操作的过程中产生矛盾。为了避免这种情况的发生,就需要用的synchronized来保证,每次对共享资源的操作,只能是一条线程在进行。在用到同步的时候,就会因需求问题用到wait()、notify()、notifyAll()这三个方法。
wait()方法:作用是使调用线程自动释放CPU使用权,从而使线程本身进入线程池继续等待。
notify()方法:由运行的线程唤醒线程池中排在最前面的等待线程,使该线程具有使用CPU的资格。
notifyAll()方法:由运行的线程唤醒线程池中的所有等待线程。
因为三个方法都需以对持有(锁)监视器的对象操作,也就使他们只能在同步中使用,因为只有同步中才会出现锁这个概念。而通过查阅API,我们很容易就发现这三个方法都被定义在Object类当中,那么这些操作锁的方法为什么会被定义在Object类中呢?我们可以这样理解,同步中对于锁的标志位可以是任意的特有对象,而这三个方法都是操作锁的,也就是说他们可以操作任何对象,Java中能操作任何对象的类也只有Object类了。
虽然有这些方法来使同步机制更加完善,但是对于某些情况同步机制并不能完美的完成相应的功能。
针对以下代码:
1 * 2 * 生产者消费者问题 3 */ 4 public class ProducerConsumerDemo{ 5 public static void main(String[] args){ 6 Resource r = new Resource(); 7 Producer pro = new Producer(r); 8 Consumer con = new Consumer(r); 9 10 Thread t1 = new Thread(pro); 11 Thread t3 = new Thread(pro); 12 Thread t2 = new Thread(con); 13 Thread t4 = new Thread(con); 14 15 t1.start(); 16 t2.start(); 17 t3.start(); 18 t4.start(); 19 } 20 } 21 //定义共享的资源类 22 class Resource{ 23 private String name; 24 private int count = 1; 25 private boolean flag = false; 26 27 public synchronized void set(String name){ 28 while(flag) 29 try{ 30 this.wait(); 31 }catch(Exception e){ 32 33 } 34 this.name = name+"--"+count++; 35 System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name); 36 flag = true; 37 this.notifyAll(); 38 } 39 public synchronized void out(){ 40 while(!flag) 41 try{ 42 wait(); 43 }catch(Exception e){ 44 } 45 System.out.println(Thread.currentThread().getName()+"-----消费者-----"+this.name); 46 flag = false; 47 this.notifyAll(); 48 } 49 } 50 //定义生产者类 51 class Producer implements Runnable{ 52 private Resource res; 53 Producer(Resource res){ 54 this.res = res; 55 } 56 public void run(){ 57 while(true){ 58 res.set("+商品+"); 59 } 60 } 61 } 62 //定义消费者类 63 class Consumer implements Runnable{ 64 private Resource res; 65 Consumer(Resource res){ 66 this.res = res; 67 } 68 public void run(){ 69 while(true){ 70 res.out(); 71 } 72 } 73 }
在这个类中虽然可以初步满足生产者消费者这一功能需求,但我们可以发现在代码中我们用了notifyAll()这个方法,这个方法每运行一次将会把所有的等待状态的线程全部唤醒,这会给计算机带来很大的耗费,这种情况显然并不是我们所期望的。
针对上述问题,官方在JDK1.5版本中进行了升级:
* 引进了Lock接口从而替代了synchronized
* 用Condition 替代了 Object监视器方法
这样做的好处就是:一定程度上简化了同步代码的编写;一个Condition对象可以对不同锁进行操作,而且可以根据需求对特定的线程进行唤醒。而同时也要注意:一定要在对应的程序代码尾部执行释放锁的操作。
下面是通过运用Lock和Condition改进后的代码:
1 import java.util.concurrent.locks.Condition; 2 import java.util.concurrent.locks.ReentrantLock; 3 4 /* 5 * 生产者消费者问题 6 */ 7 public class ProducerConsumerDemo2{ 8 public static void main(String[] args){ 9 Resource2 r = new Resource2(); 10 Producer2 pro = new Producer2(r); 11 Consumer2 con = new Consumer2(r); 12 13 Thread t1 = new Thread(pro); 14 Thread t3 = new Thread(pro); 15 Thread t2 = new Thread(con); 16 Thread t4 = new Thread(con); 17 18 t1.start(); 19 t2.start(); 20 t3.start(); 21 t4.start(); 22 } 23 } 24 //定义共有的资源 25 class Resource2{ 26 private String name; 27 private int count = 1; 28 private boolean flag = false; 29 30 private ReentrantLock lock = new ReentrantLock(); 31 Condition c_pro = lock.newCondition(); 32 Condition c_con = lock.newCondition(); 33 34 public void set(String name) throws InterruptedException{ 35 lock.lock(); 36 try{ 37 while(flag) 38 c_pro.await(); 39 this.name = name+"--"+count++; 40 System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name); 41 flag = true; 42 c_con.signal(); 43 }finally{ 44 lock.unlock(); 45 } 46 } 47 public void out() throws InterruptedException{ 48 lock.lock(); 49 try{ 50 while(!flag) 51 c_con.await(); 52 System.out.println(Thread.currentThread().getName()+"-----消费者-----"+this.name); 53 flag = false; 54 c_pro.signal(); 55 }finally{ 56 lock.unlock(); 57 } 58 } 59 } 60 //定义生产者 61 class Producer2 implements Runnable{ 62 private Resource2 res; 63 Producer2(Resource2 r){ 64 this.res = r; 65 } 66 public void run(){ 67 while(true){ 68 try { 69 res.set("+商品+"); 70 } catch (InterruptedException e) { 71 // TODO Auto-generated catch block 72 e.printStackTrace(); 73 } 74 } 75 } 76 } 77 //定义消费者 78 class Consumer2 implements Runnable{ 79 private Resource2 res; 80 Consumer2(Resource2 r){ 81 this.res = r; 82 } 83 public void run(){ 84 while(true){ 85 try { 86 res.out(); 87 } catch (InterruptedException e) { 88 e.printStackTrace(); 89 } 90 } 91 } 92 }
接下来是关于线程的停止:
1、正常的情况是,等待线程运行完,自动释放CPU占用权。这种方式也是最为安全的。
2、多线程中常用循环控制语句,我们可以通过设置标志位,来使线程在不满足条件的情况下自动释放CPU资源,停止运行。
3、对于占有资源但却因为某种原因进入冻结的线程,我们可以使用interrupt()方法来打断其状态,进而使之可以正常运行释放资源。
设置守护进程的方法:通过调用SetDaemon()方法,可以把对应进程设置成守护进程或者是用户进程。
join()方法:作用是长期占有CPU使用权。原理是:当A线程执行到了B线程的join方法时,A就会等待,等B线程执行完,A才会执行。Join可以用来临时加入线程执行。
代码示例:
1 class Demo implements Runnable{ 2 public void run(){ 3 for(int x=0; x<20; x++){ 4 System.out.println(Thread.currentThread().getName()+"---"+x); 5 } 6 } 7 } 8 public class JoinTest { 9 public static void main(String[] args) throws InterruptedException { 10 Demo d = new Demo(); 11 Thread t1 = new Thread(d); 12 Thread t2 = new Thread(d); 13 14 t1.start(); 15 t1.join(); 16 t2.start(); 17 18 for(int x=0; x<30; x++){ 19 System.out.println("main--"+x); 20 } 21 System.out.println("over"); 22 } 23 }
执行结果:
1 Thread-0---0 2 Thread-0---1 3 Thread-0---2 4 Thread-0---3 5 Thread-0---4 6 Thread-0---5 7 Thread-0---6 8 Thread-0---7 9 Thread-0---8 10 Thread-0---9 11 Thread-0---10 12 Thread-0---11 13 Thread-0---12 14 Thread-0---13 15 Thread-0---14 16 Thread-0---15 17 Thread-0---16 18 Thread-0---17 19 Thread-0---18 20 Thread-0---19 21 main--0 22 Thread-1---0 23 main--1 24 Thread-1---1 25 Thread-1---2 26 Thread-1---3 27 Thread-1---4 28 Thread-1---5 29 Thread-1---6 30 Thread-1---7 31 main--2 32 Thread-1---8 33 Thread-1---9 34 main--3 35 Thread-1---10 36 main--4 37 Thread-1---11 38 Thread-1---12 39 Thread-1---13 40 Thread-1---14 41 Thread-1---15 42 Thread-1---16 43 main--5 44 main--6 45 main--7 46 main--8 47 main--9 48 main--10 49 main--11 50 main--12 51 main--13 52 main--14 53 main--15 54 main--16 55 main--17 56 Thread-1---17 57 main--18 58 Thread-1---18 59 main--19 60 Thread-1---19 61 main--20 62 main--21 63 main--22 64 main--23 65 main--24 66 main--25 67 main--26 68 main--27 69 main--28 70 main--29 71 over
我们可以发现只有当线程t1执行完以后,main线程才会和t2线程继续强度CPU的使用权,进而相继输出。所以可以理解为join()方法是线程具有了长期占有CPU的资格。
关于线程优先级的知识:
1、线程的优先级设置范围为(1-10);
2、线程默认的优先级是5;
3、我们可以通过SetPriority(1-10)来对线程的优先级进行设置。
4、因为相邻的优先级效果差别很小,说以一般提倡对优先级设置一下几个值:Thread.MAX_PRIORITY 10 ;Thread.MIN_PRIORITY 1;Thread.NORM_PRIORITY 5
yield()方法:暂停当前正在执行的线程,转而去执行其它线程。
开发中应用:保证以下三个代码同时运行。
案例代码:
1 new Thread(){ 2 for(int x=0;x<100;x++){ 3 sop(Thread.currentThread().getName()) 4 } 5 }.start(); 6 7 for(int x=0;x<100;x++){ 8 sop(Thread.currentThread().getName()) 9 } 10 11 Runnable r=new Runnable(){ 12 public voud run(){ 13 for(int x=0;x<100;x++){ 14 sop(Thread.currentThread().getName()) 15 } 16 } 17 }; 18 19 new Thread(r).start();