Java之多线程

来源:互联网 发布:网络金融代理招聘信息 编辑:程序博客网 时间:2024/06/04 19:54


面向对象——多线程

01-多线程-概述

1、线程与进程

         整个区域叫做进程。进程是不直接执行的,他只是在分配该应用程序的内存空间,谁在负责执行呢?就是线程。一个进程中至少要有一个线程。

2、举例:360中,体检、清理……可以同时启动运行。

3、开启多个线程是为了同时运行多部分代码。每一个线程都有自己运行的内容。这个内容可以成为线程要执行的任务。

02-多线程-好处与弊端

1、多线程真的是在同时执行的吗?

         CPU处理切换非常快。正因为CPU的执行速度非常快,使得所有程序好像是在“同时”运行一样。

2、线程的好处和弊端

多线程好处:解决了多部分同时运行的问题。

多线程的弊端:线程太多会导致效率的降低。

3、其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。

03-多线程-JVM中的多线程解析

1、JVM本身就基于多线程。

2、JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。

①执行main函数的线程

         该线程的任务代码都定义在main函数中。

②负责垃圾回收的线程

04-多线程-主线程运行示例

05-多线程-多线程创建的方式1-继承Thread

1、创建新执行线程有两种方法:继承Thread类、实现Runnable接口。

2、创建线程方式1:继承Thread类

步骤:①、定义一个类,继承Thread类

          ②、覆盖Thread类中的run方法

          ③、直接创建Thread类的子类对象

          ④、调用start()方法,开启线程并调用线程的任务run()方法执行

3、创建线程的目的是为了开启一条执行路径,区运行指定的代码和其他代码实现同时执行。而运行的指定代码,就是这线程的任务。

         Jvm创建的主线程的任务都定义在了主函数中。而自定义的线程他的任务在哪儿呢?

         Thread类用于描述线程,线程是需要任务的。所以Thread类也对任务有描述。这个任务就通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数。

         Run方法中定义的就是线程要运行的任务代码。开启线程是为了运行指定代码。所以只有继承了Thread类并覆写run方法,将要运行的代码定义在run方法中即可。

4、代码示例

public class Demo21 {    public static void main(String[] args) {       MyThread mt1=new MyThread("线程A");       MyThread mt2=new MyThread("线程B");       MyThread mt3=new MyThread("线程C");        mt1.start();       mt2.start();       mt3.start();    }}class MyThread extends Thread{    private String title;    public MyThread(String title){       this.title = title;    }    public void run(){       for(int x=0;x<5;x++){           System.out.println(this.title+"运行,x="+x);       }    }}


可能的程序运行结果:

线程A运行,x=0

线程B运行,x=0

线程A运行,x=1

线程B运行,x=1

线程A运行,x=2

线程B运行,x=2

线程A运行,x=3

线程C运行,x=0

线程B运行,x=3

线程A运行,x=4

线程C运行,x=1

线程B运行,x=4

线程C运行,x=2

线程C运行,x=3

线程C运行,x=4

5、调用run()和调用start()有什么区别?

答:多线程的实现一定需要操作系统的支持,所以在多线程操作中,使用start()方法启动多线程的操作是需要进行操作系统函数调用的。

09-创建线程的第二种方式2-实现Runnable接口

1、  创建线程的第二种方式:实现Runnable接口

步骤:①定义类实现Runnable接口

          ②覆盖接口中的run方法,将线程的任务代码封装到run方法中。

          ③通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。

          为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时,就必须明确要运行的任务。

          ④调用线程对象的start方法开启线程。

2、  代码示例

public class Demo21 {    public static void main(String[] args) {       MyThread mt1=new MyThread("线程A");       MyThread mt2=new MyThread("线程B");       MyThread mt3=new MyThread("线程C");       new Thread(mt1).start();       new Thread(mt2).start();       new Thread(mt3).start();    }}class MyThread implements Runnable{    private String title;    public MyThread(String title){       this.title=title;    }    public void run(){       for(int x=0;x<5;x++){           System.out.println(this.title+"运行,x="+x);       }    }}


一种可能的情况:

线程A运行,x=0

线程A运行,x=1

线程C运行,x=0

线程B运行,x=0

线程A运行,x=2

线程C运行,x=1

线程B运行,x=1

线程A运行,x=3

线程C运行,x=2

线程B运行,x=2

线程A运行,x=4

线程C运行,x=3

线程B运行,x=3

线程C运行,x=4

线程B运行,x=4

11-多线程-第二种方式的好处

1、Runnable的出现对线程的任务进行了对象的封装。

2、实现Runnable接口的好处

①将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务封装成对象。

②避免了java单继承的局限性。

如果从java的实际开发而言,肯定使用Runnable接口。

3、Thread类也是Runnable接口的子类。

4、对于Runnable接口和Thread类还有一个不太好区分的区别:Runnable接口可以更方便的表示出数据共享的概念(但不是说Thread类不能实现数据共享)。

12-多线程-卖票示例

1、需求:一共100张票,四个窗口,同时开始卖

2、代码示例

public class Demo22 {    public static void main(String[] args) {       Ticket t=new Ticket();       Thread t1=new Thread(t);       Thread t2=new Thread(t);       Thread t3=new Thread(t);       Thread t4=new Thread(t);       t1.start();       t2.start();       t3.start();       t4.start();    }}class Ticket implements Runnable{    private int num=100;    public void run(){       while(true){           if(num>0){       System.out.println(Thread.currentThread().getName()+":"+num--);           }       }    }}


一种可能的结果:

Thread-0:100

Thread-0:99

Thread-0:98

Thread-1:97

Thread-3:96

Thread-1:95

Thread-3:94

Thread-1:93

……

3、4个线程中只传入同一个Ticket对象——t。则线程管线程分,但用的num还是同一个成员变量num。线程关注的,只是里面的run()方法,即线程所要执行的任务。

4、现在有两种票,各100张。1号、2号窗口卖一种票,3号、4号窗口卖一种票。

只需要主函数稍微做下更改:

   

 public static void main(String[] args) {       Ticket t=new Ticket();        Ticket tt=new Ticket();       Thread t1=new Thread(t);       Thread t2=new Thread(t);       Thread t3=new Thread(tt);       Thread t4=new Thread(tt);       t1.start();       t2.start();       t3.start();       t4.start();    }

08-多线程-线程的状态

1、CPU的执行资格:可以被CPU处理,在处理队列中排队。

2、CPU的执行权:正在被CPU处理。

3、线程的5种状态:

13-多线程-线程安全问题的现象

1、代码示例

class MyThread implements Runnable{    private int ticket = 6;    public void run(){       while(true){           if(ticket>0){              try {                  Thread.sleep(1000);              } catch (InterruptedException e) {                  e.printStackTrace();              }              System.out.println(Thread.currentThread().getName()+"卖票,ticket="+this.ticket--);           }       }    }}public class Demo24 {    public static void main(String[] args){       MyThread mt =new MyThread();       Thread t1 = new Thread(mt,"票贩子A");       Thread t2 = new Thread(mt,"票贩子B");       Thread t3 = new Thread(mt,"票贩子C");       Thread t4 = new Thread(mt,"票贩子D");       t1.start();       t2.start();       t3.start();       t4.start();    }}


可能的运行结果:

    票贩子C卖票,ticket=4

    票贩子B卖票,ticket=3

    票贩子A卖票,ticket=6

    票贩子D卖票,ticket=5

    票贩子D卖票,ticket=2

    票贩子B卖票,ticket=-1

    票贩子C卖票,ticket=1

    票贩子A卖票,ticket=0

2、为了更好的体现数据不同步所带来的问题,程序中加入了一个延迟操作。可以看到,这一运行结果下,出现了0号票和负数票。

3、为什么打印的卖出的票不是按照顺序来的?

答:其实System.out.println(……)这是调用了一个函数。里面有很多语句,因此可能会出现,先打印4,再打印6。专业点讲,是因为这个函数“是不同步的,不是线程安全的”。在java的API中,经常可以看到这种说明。

14-多线程-线程安全问题产生的原因

1、我们简化上例。

       

  public void run(){       while(true){           if(ticket>0){              Sop(Thread.…….getName()+"卖票,ticket="+this.ticket--);           }       }    }

         接下来分析线程安全问题产生的原因,因为线程是随机执行的,即在任何一种状态下,线程都有可能停止,其他线程也都有可能会进来。那么可以考虑下面一种情况:

         当ticket = 1的时候,

         A进,判断ticket>0完后,A停——执行完后,(ticket = 1)

         B进,判断ticket>0完后,B停——执行完后,(ticket = 1)

         C进,判断ticket>0完后,C停——执行完后,(ticket = 1)

         D进,判断ticket>0完后,D停——执行完后,(ticket = 1)

         A输出ticket = 1,ticket--,A结束——执行完后,(ticket= 0)

         B由于之前停在判断ticket>0完后的地方,所以不再判断ticket>0。输出ticket = 0,ticket--,B结束——执行完后(ticket= -1)

         C与B同理,C输出ticket = -1,ticket--,C结束——执行完后(ticket= -2)

         D与B同理,D输出ticket = -2,ticket--,D结束——(ticket =-3)

         因此,这样就造成了0号票和负数票的出现,导致了线程安全问题的产生。

2、线程安全问题产生的原因:(看线程程序中是否会有安全问题,也是参照以下这两点)

         ①多个线程在操作共享数据

         ②操作共享数据的线程代码有多条

3、当一个线程在执行操作共享数据的多条代码过程中(关于共享数据的这些操作还没有全部执行完),其他线程参与了运算。就会导致线程安全问题的产生。

         简单说就是,我之前通过一个条件判断进来,你突然插一脚,把本来符合条件的东西,改的不符合条件了。但这时候,我还不能再做判断。——出问题了。

15-多线程-同步代码块

1、线程安全问题的解决思路:就是将多条操作共享数据的代码封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的。

         必须要当前线程把这些代码全部执行完毕后,其他线程才可以参与运算。(线程只要进去了,就要把里面全部走完)

2、如果想要解决这样的问题,就必须使用同步。所谓的同步:就是指多个操作在同一时间段内,只能由一个线程进行。其他线程要等待此线程完成后,才可以继续执行。

3、如果想要增加这个锁,在程序中可以通过两种方法完成

         ①同步代码块

         ②同步函数

3、先来学习一下同步代码块

         同步代码块的格式:synchronized(对象)

                                                 {

                                                        需要被同步的代码;

                                                 }

4、按照上面,13中的程序可以改成如下代码:

public class Demo24 {    public static void main(String[] args){       MyThread mt =new MyThread();       Thread t1 = new Thread(mt,"票贩子A");       Thread t2 = new Thread(mt,"票贩子B");       Thread t3 = new Thread(mt,"票贩子C");       Thread t4 = new Thread(mt,"票贩子D");       t1.start();       t2.start();       t3.start();       t4.start();    }}class MyThread implements Runnable{    private int ticket = 6;    public void run(){       while(true){           synchronized(this){              if(ticket>0){                  try {                     Thread.sleep(1000);                  } catch (InterruptedException e) {                     e.printStackTrace();                  }                  System.out.println(Thread.currentThread().getName()+"卖票,ticket="+this.ticket--);                        }           }       }    }}


可能的运行结果:

         票贩子A卖票,ticket=6

    票贩子A卖票,ticket=5

    票贩子A卖票,ticket=4

    票贩子A卖票,ticket=3

    票贩子D卖票,ticket=2

    票贩子D卖票,ticket=1

这时发现,不会再出现0号票跟负数票了。

5、如果我的同步块定义在了while(true)前面,代码示例如下:

public void run(){    synchronized(obj){       while(true){           if(ticket>0){               System.out.println(Thread.currentThread().getName()+"卖票,ticket="+this.ticket--);           }       }    }}

运行结果:

    票贩子A卖票,ticket=6

    票贩子A卖票,ticket=5

    票贩子A卖票,ticket=4

    票贩子A卖票,ticket=3

    票贩子A卖票,ticket=2

    票贩子A卖票,ticket=1

这种情况下,发现只有票贩子A在卖票。而且无论你怎么运行,都只会是这一种结果。这是因为,只有执行完同步块中的内容,其他线程才会进来。但是,你把while(true)也放在了同步代码块当中。则同步代码块中的内容都不会执行完。所以只有第一个进来的线程在卖票。因此,在定义同步代码块的时候,一定要明确好,我这里面到底要包含哪一部分的内容。到底要同步哪一部分的内容。

16-多线程-同步的好处和弊端

1、同步的好处:解决了线程的安全问题。

同步的弊端:相对降低了效率,因为同步外的线程都会判断同步锁。

17-多线程-同步的前提

1、同步的前提:必须有多个线程,并且使用的是同一把锁。(使用同步一定要记住这个前提)

2、观察以下两段简化代码:

代码1:

代码2:

代码1,如下图:两个线程使用的是同一把锁,正确

代码2,如下图:两个线程使用的是各自的obj,不是同一把锁,错误

3、以上的代码2为何会出问题?

         答:因为每个多线程的任务在run()方法中,每个多线程都new了自己的一个obj对象,所以用的不是同一把锁。run方法内新搞出来的内容,都在各自线程的空间里。

18-多线程-同步函数

1、同步,可以通过两种方法完成

         ①同步代码块

         ②同步函数

         之前介绍完了同步代码块,接下来看同步函数。

2、利用同步方法来解决卖票的问题,代码示例:

class MyThread1 implements Runnable{    private int ticket = 6;    public void run(){       while(true){           this.sale();       }    }    public synchronized void sale(){       if(this.ticket>0){           System.out.println(Thread.currentThread().getName()+"卖票,ticket="+this.ticket--);       }    }}public class Demo25 {    public static void main(String[] args) {       MyThread1 mt = new MyThread1();       Thread t1 = new Thread(mt,"票贩子A");       Thread t2 = new Thread(mt,"票贩子B");       Thread t3 = new Thread(mt,"票贩子C");       Thread t4 = new Thread(mt,"票贩子D");       t1.start();       t2.start();       t3.start();       t4.start();    }}


可能的运行结果:

    票贩子A卖票,ticket=6

    票贩子D卖票,ticket=5

    票贩子D卖票,ticket=4

    票贩子D卖票,ticket=3

    票贩子D卖票,ticket=2

    票贩子D卖票,ticket=1

不会出现线程安全问题。

19-多线程-验证同步函数的锁

1、同步代码块的锁很明显,就在括号里。那么同步函数的锁,在哪儿呢?

2、验证这一问题的代码如下:为了让想要的结果更容易出现,本例中加了多个延时(学习验证这个问题的思路)

class Ticket implements Runnable{    private int ticket = 6;    public boolean flag =true;    Object obj = new Object();    public void run(){       if(flag){           while(true){              synchronized(obj){                  if(ticket>0){                     try {                         Thread.sleep(1000);                     } catch (InterruptedException e) {                     }                      System.out.println(Thread.currentThread().getName()+",ticket="+this.ticket--);                  }              }           }       }       if(!flag){           while(true){              this.sale();           }       }    }    public synchronized void sale(){       if(ticket>0){           try {              Thread.sleep(1000);           } catch (InterruptedException e) {           }           System.out.println(Thread.currentThread().getName()+",ticket="+this.ticket--);       }    }}public class Demo26 {    public static void main(String[] args) {       Ticket t = new Ticket();       Thread t1 = new Thread(t,"票贩子A");       Thread t2 = new Thread(t,"票贩子B");       t1.start();       try {           Thread.sleep(1000);       } catch (InterruptedException e) {       }       t.flag = false;       t2.start();    }}


此时同步块的锁使用对象obj,可能的运行结果:

    票贩子A,ticket=6

    票贩子A,ticket=5

    票贩子B,ticket=4

    票贩子B,ticket=3

    票贩子A,ticket=2

    票贩子A,ticket=0

    票贩子B,ticket=1

3、验证这个问题的思路:

    首先明确,同步块的锁很容易知道,而且可以自己用任意对象来当同步块的锁。同步函数的锁未知。可以通过同步块来寻找同步函数的锁。

    建立两个线程,操作同一个ticket。一个线程用同步块卖票,一个线程用同步函数卖票。如果产生了线程安全问题,则更换同步块的锁。直到不再产生线程安全问题。如果不会产生线程安全问题,那么此时证明同步函数的锁,就是同步块的锁。

4、上面代码的原理分析

    上面的代码中,t1和t2线程操作的都是同一个ticket。只是中间通过一个标志位flag的切换。(刚开始flag为true)让t1使用同步块卖票,(之后将flag赋值为false)让t2使用同步函数卖票。同步块与同步函数实现的功能都相同。只是把同一功能分别以同步块和同步函数的方式展现出来了而已。

5、上面代码3处sleep的作用:

    (1)同步块中的sleep,是为了加延时,让其他线程在两句中间更容易进来。

    (2)同步函数中的sleep,作用与(1)相同。

    (3)主函数中的sleep,是为了防止t1执行过快。t2还没有start,t1就已经执行完了。这样就只会有t1这一个线程,就不会出现线程安全问题了。

    上面代码3处sleep的目的,都是为了更容易的产生线程安全问题。

6、从运行结果可以发现,出现了0号票。证明同步函数的锁不是对象obj(本例中同步代码块的锁)。

7、那么,我们把同步代码块的锁换成this,再运行代码。可能的运行结果:

    票贩子A,ticket=6

    票贩子B,ticket=5

    票贩子B,ticket=4

    票贩子B,ticket=3

    票贩子B,ticket=2

    票贩子B,ticket=1

发现不会再出现线程安全问题了。

8、同步函数的锁,是this

9、同步函数和同步代码块的区别:同步函数的锁是固定的this;而同步代码块的锁,可以是任意对象。

    开发时建议使用同步代码块。同步函数,只是同步代码块的一种简写格式,是简写,他就有缺陷。

20-验证静态同步函数的锁

1、如果上面的同步函数成为了静态的。即:

         public static synchronized void sale()

         这时,静态同步函数的锁就不是this了。因为静态的东西根本用不到对象。

2、静态同步函数使用的锁是,该函数所属的字节码文件对象。可以使用getClass()方法获取,也可以用——当前类名.class表示。

3、仍然利用19中的代码来验证,只需稍作更改即可。代码如下:

class Ticket implements Runnable{    private static int ticket = 6;    public boolean flag =true;    Object obj = new Object();    public void run(){       if(flag){           while(true){              synchronized(this){                  if(ticket>0){                     try {                         Thread.sleep(1000);                     } catch (InterruptedException e) {                     }                      System.out.println(Thread.currentThread().getName()+",ticket="+this.ticket--);                  }              }           }       }       if(!flag){           while(true){              sale();           }       }    }    public static synchronized void sale(){       if(ticket>0){           try {              Thread.sleep(1000);           } catch (InterruptedException e) {           }           System.out.println(Thread.currentThread().getName()+",ticket="+ticket--);       }    }}public class Demo26 {    public static void main(String[] args) {       Ticket t = new Ticket();       Thread t1 = new Thread(t,"票贩子A");       Thread t2 = new Thread(t,"票贩子B");       t1.start();       try {           Thread.sleep(1000);       } catch (InterruptedException e) {       }       t.flag = false;       t2.start();    }}


此时,同步块的锁使用this,可能的运行结果:

    票贩子A,ticket=6

    票贩子B,ticket=5

    票贩子A,ticket=4

    票贩子B,ticket=3

    票贩子A,ticket=2

    票贩子B,ticket=1

    票贩子A,ticket=0

可以发现,出现了0号票。证明静态同步函数的锁不是this。

4、如果同步块使用this.getClass()作为锁,可能的运行结果:

    票贩子A,ticket=6

    票贩子B,ticket=5

    票贩子B,ticket=4

    票贩子B,ticket=3

    票贩子B,ticket=2

    票贩子B,ticket=1

5、可以发现,不会出现线程安全问题了。证明了“静态同步函数使用的锁是,该函数所属的字节码文件对象”。

21-多线程-单例设计模式涉及的多线程问题

1、回顾一下单例设计模式的两种实现方式:

饿汉式:

class Single{    private Single s = new Single();    private Single(){    }    public static Single getInstance(){       return s;    }}


懒汉式:

class Single{    private Single s;    private Single(){    }    public static Single getInstance(){       if(s == null){           s = new Single();       }       return s;    }}


2、可以看出,懒汉式的getInstance()方法可能会出现线程安全问题,造成无法保证对象的唯一性。这样就不算是单例设计模式了。

3、懒汉式线程安全问题的解决方案

class Single{    private Single s;    private Single(){    }    public static Single getInstance(){       if(s==null){           synchronized(Single.class){              if(s==null){                  s = new Single();              }           }       }       return s;    }}


4、加锁是为了解决线程安全问题。多加一次s==null的判断,是为了解决效率问题。因为如果s不为null,你直接不用再判断同步锁,直接return s就可以了。

5、对于静态函数,它的锁不是this。

6、单例写饿汉好,但是面试一般都面懒汉式。

22-多线程-死锁示例

1、同步就是指一个线程要等待另外一个线程执行完毕,才会继续执行的一种操作形式。

2、死锁:就是指两个线程都在等待彼此先完成,造成了程序的停滞状态。

3、死锁的比喻:例如现在张三想要李四的画,李四想要张三的书,那么张三对李四说了:“把你的画给我,我就给你书”。李四也对张三说了:“把你的书给我,我就给你画”。此时,张三在等着李四的答复,而李四也在等着张三的答复。那么这样下去的结果可想而知,张三得不到李四的画,李四也得不到张三的书。这实际上就是死锁的概念。

4、死锁是由于同步的嵌套(即同步里有同步)所造成的。(我的锁里有你的锁,你的锁里有我的锁)

5、死锁的简化模型:

6、死锁代码示例

class MyLock{    public static final Object locka = new Object();    public static final Object lockb = new Object();}class Test1 implements Runnable{    private boolean flag;    public Test1(boolean flag){       this.flag = flag;    }    public void run(){       if(flag){           while(true){              synchronized(MyLock.locka){                  System.out.println(Thread.currentThread().getName()+"...if loacka...");                  synchronized(MyLock.lockb){                     System.out.println(Thread.currentThread().getName()+"...if loackb...");                  }              }           }       }else{           while(true){              synchronized(MyLock.lockb){                  System.out.println(Thread.currentThread().getName()+"...if loackb...");                  synchronized(MyLock.locka){                     System.out.println(Thread.currentThread().getName()+"...if loacka...");                  }              }           }       }    }}public class Demo23 {    public static void main(String[] args) {       Test1 a = new Test1(true);       Test1 b = new Test1(false);       Thread t1 = new Thread(a);       Thread t2 = new Thread(b);       t1.start();       t2.start();    }}


可能的运行结果:

    Thread-0...if loacka...

    Thread-1...if loackb...

    [以下代码不再执行,程序进入死锁状态]

7、死锁是在日后多线程程序开发中经常会遇见的问题。

23-多线程-线程间通信-示例

1、线程间通讯:多个县城在处理同一资源,但是任务却不同。

2、多线程通讯图解

3、技巧:java中实现切换,

       int x = 0;

       if(x == 0){

           ……

       }else{

           ……

       }

       x = (x+1)%2;

4、多线程通讯,代码示例

class Resource{    String name;    String sex;}class Input implements Runnable{    Resource r;    public Input(Resource r){       this.r = r;    }    public void run(){       int x = 0;       while(true){           synchronized(this){              if(x == 0){                  r.name = "A";                  r.sex = "man";              }else{                  r.name = "B";                  r.sex = "woman";              }              x = (x + 1)%2;           }       }    }}class Output implements Runnable{    Resource r;    public Output(Resource r){       this.r = r;    }    public void run(){       while(true){           synchronized(this){              System.out.println(r.name+"..."+r.sex);           }       }    }}public class Demo30 {    public static void main(String[] args) {       Resource r = new Resource();       Input in = new Input(r);       Output out = new Output(r);       Thread t1 = new Thread(in);       Thread t2 = new Thread(out);       t1.start();       t2.start();    }}


可能的代码运行结果:

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    A...man

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    B...woman

    [后面的结果省略]

5、本来的要求是,你input一条,我output一条。一条进,一条出。但观察上面的运行代码,却出现了一条进,多条出。与要求不符。

6、造成以上结果的原因是什么?

答:input线程,输入一个信息。之后output线程得到执行权,输出一个信息。但这之后,output线程并未释放执行权,仍然在执行。继续又把信息输出。因此导致了此种情况。 解决这一问题额办法,便是等待唤醒机制。

24-多线程-线程间通信-等待唤醒机制

1、应该达到的需求,应该是进去(input)一个数据,你立马输出(output)这个数据。即你输入一次,我立马输出一次。

2、解决方式(等待唤醒机制)思路图解

    如果想让生产者不重复生产,消费者不重复取走,则可以增加一个标志位——flag。假设标志位为boolean型变量。如果标志位为true,则表示可以生产,但是不能取走,如果此时线程执行到了消费者线程则应该等待;如果标志位内容为false,则表示可以取走,但是不能生产,如果生产者线程运行,则应该等待。

3、等待/唤醒机制涉及到的方法

    ①wait():让线程处于冻结状态,被wait的线程会被存储到线程池(一个容器)中。

    ②notify():唤醒线程池中的一个线程(任意)。

    ③notifyAll():唤醒线程池中的所有线程,让线程具备了执行资格。

4、这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程。

    你到底wait()的是哪个锁上的线程?你如果wait()的是A锁上的线程,你就只能用A锁的notify()来唤醒。

5、为什么操作线程的方法wait()、notify()、notifyAll()定义在了Object类中?

    答:因为这些方法是监视器的方法,监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中。

6、更改后的代码:

class Resource{    String name;    String sex;    boolean flag = true;}class Input implements Runnable{    Resource r;    public Input(Resource r){       this.r = r;    }    public void run(){       int x = 0;       while(true){           synchronized(r){              if(r.flag){                  try {                      r.wait();                  } catch (InterruptedException e) {                     e.printStackTrace();                  }              }              if(x == 0){                  r.name = "A";                  r.sex = "man";              }else{                  r.name = "B";                  r.sex = "woman";              }              x = (x + 1)%2;              r.flag = true;              r.notify();           }       }    }}class Output implements Runnable{    Resource r;    public Output(Resource r){       this.r = r;    }    public void run(){       while(true){           synchronized(r){              if(!r.flag){                  try {                     r.wait();                  } catch (InterruptedException e) {                     e.printStackTrace();                  }              }              System.out.println(r.name+"..."+r.sex);              r.flag = false;              r.notify();           }       }    }}public class Demo30 {    public static void main(String[] args) {       Resource r = new Resource();       Input in = new Input(r);       Output out = new Output(r);       Thread t1 = new Thread(in);       Thread t2 = new Thread(out);       t1.start();       t2.start();    }}


可能的运行结果:

A...man

B...woman

A...man

B...woman

A...man

B...woman

A...man

B...woman

A...man

B...woman

A...man

B...woman

A...man

B...woman

A...man

B...woman

[后面的结果省略]

从上面的结果可以发现,等待唤醒机制可以解决该问题。

25-多线程-线程间通信-等待唤醒机制-代码优化

1、代码优化:资源中的属性,需要被私有化

class Resource{    private String name;    private String sex;    private boolean flag = false;    public synchronized void set(String name,String sex){       if(flag)           try{this.wait();}catch(InterruptedException e){}       this.name = name;       this.sex = sex;       flag = true;       this.notify();    }    public synchronized void out(){       if(!flag)           try{this.wait();}catch(InterruptedException e){}       System.out.println(name+"...+...."+sex);       flag = false;       notify();    }}//输入class Input implements Runnable{    Resource r ;    Input(Resource r){       this.r = r;    }    public void run(){       int x = 0;       while(true){           if(x==0){              r.set("A","man");           }else{              r.set("B","woman");           }           x = (x+1)%2;       }    }}//输出class Output implementsRunnable{    Resource r;    Output(Resource r){       this.r = r;    }public void run(){       while(true){           r.out();       }    }}class  ResourceDemo3{    public static void main(String[] args) {       //创建资源。       Resource r = new Resource();       //创建任务。       Input in = new Input(r);       Output out = new Output(r);       //创建线程,执行路径。       Thread t1 = new Thread(in);       Thread t2 = new Thread(out);       //开启线程       t1.start();       t2.start();    }}


26-多线程-线程间通信-多生产者消费者问题

1、多线程经典案例——生产者消费者模型

2、生产者生产一只烤鸭,消费者消费一只烤鸭,为了更清晰,我们让烤鸭带着编号count

3、代码示例:单生产者单消费者

class Resource{    private String name;    private int count = 1;    private boolean flag = false;    public synchronized void set(String name){       if(flag){           try{              this.wait();           }catch(Exception e){                  }       }       this.name = name + count;       count++;       System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);       flag = true;       notify();    }    public synchronized void out(){       if(!flag){           try{              this.wait();           }catch(Exception e){                  }       }       System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);       flag = false;       notify();    }}class Producer implements Runnable{    private Resource r;    Producer(Resource r){       this.r = r;    }    public void run(){       while(true){           r.set("烤鸭");       }    }}class Consumer implements Runnable{    private Resource r;    Consumer(Resource r){       this.r = r;    }    public void run(){       while(true){           r.out();       }    }}public class ProducerConsumer {    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(con);       t1.start();       t2.start();    }}


运行结果:

    Thread-1...消费者...烤鸭27490

Thread-0...生产者...烤鸭27491

Thread-1...消费者...烤鸭27491

Thread-0...生产者...烤鸭27492

Thread-1...消费者...烤鸭27492

Thread-0...生产者...烤鸭27493

Thread-1...消费者...烤鸭27493

4、现在要来多生产者和多消费者了。有朋友说,那我这么搞不就行了么?

public class ProducerConsumer {    public static void main(String[] args) {       Resource r = new Resource();       Producer pro = new Producer(r);       Consumer con = new Consumer(r);       Thread t0 = new Thread(pro);       Thread t1 = new Thread(pro);       Thread t2 = new Thread(con);       Thread t3 = new Thread(con);       t0.start();       t1.start();       t2.start();       t3.start();    }}


运行结果:

Thread-2...消费者...烤鸭27552

Thread-1...生产者...烤鸭27553

Thread-2...消费者...烤鸭27553

Thread-3...消费者...烤鸭27553

Thread-1...生产者...烤鸭27554

Thread-2...消费者...烤鸭27554

……

Thread-1...生产者...烤鸭27661

Thread-0...生产者...烤鸭27662

Thread-2...消费者...烤鸭27662

Thread-3...消费者...烤鸭27662

Thread-0...生产者...烤鸭27663

5、出问题了,烤鸭27553生产了1次,但是却被消费了2次。烤鸭27661生产了,烤鸭27662也生产了,但是只消费了27662。

6、同步了,但是不安全。怎么回事?

 

27-多线程-线程间通信-多生产者消费者问题解决

1、图解多生产者多消费者安全问题产生原因

t0进来,生产了“烤鸭1”。flag由false变成true。true之后,t0等待,进入线程池。

t1进来,发现flag为true。t1等待,进入线程池。

t2进来,发现flag为true。可以消费!烤鸭1就被消费了。flag由true变成false。false之后,t2他notify了一次。之后t2等待,进入线程池。

t3进来,发现flag为false。t3等待,进入线程池。

假设t2唤醒的是t0。那么此时只有t0醒着,其他3个全在线程池里。同时t0在醒的时候,由于是在if之后醒的,所以t0不会判断标记,直接进行生产。(因为是if,所以程序会从醒的地方继续向下执行。不会回过头来判断。)“烤鸭2”。t0把flag从false变成了true。t0此时也要notify一次。

假设t0唤醒了t1,t1不会判断标记,直接又生产了“烤鸭3”。

2、把if换成while,本方唤醒了本方会造成这种情况。

3、while代替if,notify代替notify

4、while判断标记,解决了线程获取执行权后是否要运行。

    if判断标记,只判断一次,会导致不该运行的线程运行了。出现了数据错误的情况。

5、notifyAll解决了本方线程一定会唤醒对方线程的问题

    notify只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁,四个线程全在等待。

6、代码示例——多生产者多消费者(正确代码)

class Resource{    private String name;    private int count = 1;    private boolean flag = false;    public synchronized void set(String name){       while(flag){           try{              this.wait();           }catch(Exception e){                  }       }       this.name = name + count;       count++;       System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);       flag = true;       notifyAll();    }    public synchronized void out(){       while(!flag){           try{              this.wait();           }catch(Exception e){                  }       }       System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);       flag = false;       notifyAll();    }}class Producer implements Runnable{    private Resource r;    Producer(Resource r){       this.r = r;    }    public void run(){       while(true){           r.set("烤鸭");       }    }}class Consumer implements Runnable{    private Resource r;    Consumer(Resource r){       this.r = r;    }    public void run(){       while(true){           r.out();       }    }}public class ProducerConsumer {    public static void main(String[] args) {       Resource r = new Resource();       Producer pro = new Producer(r);       Consumer con = new Consumer(r);       Thread t0 = new Thread(pro);       Thread t1 = new Thread(pro);       Thread t2 = new Thread(con);       Thread t3 = new Thread(con);       t0.start();       t1.start();       t2.start();       t3.start();    }}


7、多生产者多消费者:全用while,全用notifyAll。但是这样也有问题,全唤醒,效率太低。Java升级以后解决了。

28-多线程-线程间通信-多生产者消费者问题-JDK1.5新特性-Lock

1、我们已经通过while与notifyAll解决了多生产者多消费者的问题。但这样会产生一个弊端。因为notifyAll唤醒了本方与对方线程,导致了效率低下。

2、那么我们就想,notifyAll的时候,能不能只唤醒对方。

3、java升级到1.5之后,为我们解决了这个问题:

4、接口Lock

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition 对象。

    其实接口Lock的出现是为了替代synchronized。

方法摘要

 void

lock()
          获取锁。

 void

lockInterruptibly()
          如果当前线程未被中断,则获取锁。

 Condition

newCondition()
          返回绑定到此 Lock 实例的新 Condition 实例。

 boolean

tryLock()
          仅在调用时锁为空闲状态才获取该锁。

 boolean

tryLock(long time,TimeUnit unit)
          如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。

 void

unlock()
          释放锁。

5、旧版本——同步代码块,对于锁的操作是隐式的

Object obj = new Object();void show(){    synchronized(obj){       ……code    }}


6、新版本

Lock lock = new ReentrantLock();void show(){    lock.lock();//获取锁    ……code;    lock.unlock();//释放锁}


7、JDK1.5之后,将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中。将隐式动作变成了显示动作。

8、如果我获取锁以后,这代码抛了异常。那么释放锁这段应该放到finally语句中。所以6中的代码做成以下样子,是最靠谱的。

Lock lock = new ReentrantLock();void show(){try{        lock.lock();//获取锁        ……code throw Exception();    }catch(Exception e){    }    finally{        lock.unlock();//释放锁    }}


9、利用新特性Lock重新替换一下多生产者多消费者代码:

class Resource{    private String name;    private int count = 1;    private boolean flag = false;    Lock lock = new ReentrantLock();     public void set(String name){       lock.lock();       try{           while(flag){               try{                   this.wait();               }catch(Exception e){                      }           }           this.name = name + count;           count++;           System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);            flag = true;           notifyAll();       }       finally{           lock.unlock();}    }    public void out(){       lock.lock();       try{           while(!flag){               try{                   this.wait();               }catch(Exception e){                      }            }           System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);           flag = false;            notifyAll();       }       finally{           lock.unlock();}    }}class Producer implements Runnable{    private Resource r;    Producer(Resource r){       this.r = r;    }    public void run(){       while(true){           r.set("烤鸭");       }    }}class Consumer implements Runnable{    private Resource r;    Consumer(Resource r){       this.r = r;    }    public void run(){       while(true){           r.out();       }    }}public class ProducerConsumer {    public static void main(String[] args) {       Resource r = new Resource();       Producer pro = new Producer(r);       Consumer con = new Consumer(r);       Thread t0 = new Thread(pro);       Thread t1 = new Thread(pro);       Thread t2 = new Thread(con);       Thread t3 = new Thread(con);       t0.start();       t1.start();       t2.start();       t3.start();    }}


0 0
原创粉丝点击