Java基础之多线程知识点总结

进程:当前正在执行的程序。代表一个应用程序在内存中的执行区域。
线程:是进程中的一个执行控制单元,执行路径。(线程就是进程中的一个执行者)
一个进程中至少有一个线程在负责控制程序的执行。
一个进程中如果只有一个执行路径,这个程序称为单线程。
一个进程中有多个执行路径时,这个程序成为多线程。
多线程的出现:可以有多条执行路径,让多部分代码可以完成同时执行。 以提高效率。本身也是对问题的一种解决方案。
           比如:图形界面中的多个小程序的同时执行,例子:360管理软件。
JVM启动是单线程,还是多线程的呢?
jvm的启动其实就是多线程程序。其中有一个程序负责从主函数开始执行,并控制程序运行的流程。同时为了提高效率,还启动了另一个控制单元(执行路径)专门负责堆内存中的垃圾回收。在程序正常执行过程中,如果出现了垃圾,这时另一个负责收垃圾的线程会在不定时间内进行垃圾的处理。这两个程序是同时执行的。所以说JVM启动时多线程的。
负责执行正常代码的线程,称为主线程。该线程执行的代码都存放于主函数中。
负责收垃圾代码执行的线程,称为垃圾回收线程。该线程要执行的代码在finalize中。
如何在我们自定义的程序中去创建一个线程(执行路径,控制单元)呢?
如何让自定义的线程运行我们执行的代码,可以和主线程同时执行呢?
例子1.自定义的程序中创建一个线程。

  
  
[java] view plain copy
  1. package cn.itheima.day09;  
  2. class Demo{  
  3.     private String name;  
  4.     Demo(String name) {  
  5.         this.name=name;  
  6.     }  
  7.     public void show(){  
  8.         for(int x=0;x<10;x++){  
  9.             for(int y=-99999999;y<999999999;y++){}  //为了减缓程序的执行  
  10.             System.out.println("name="+name);  
  11.         }  
  12.     }  
  13. }  
  14. public class ThreadDemo {  
  15.     public static void main(String[] args) {  
  16.         Demo d1 = new Demo("one");  
  17.         Demo d2 = new Demo("two");  
  18.         d1.show();  
  19.         d2.show();  
  20.     }  
  21. }  
在该示例中,只有一个主线程在控制代码执行的流程。
当d1.show()没有执行完,那么d2.show(),是不可能执行的。
如果d1执行时,遇到了较复杂的运算时,d2只有等d1结束。
可不可以完成一个效果,让 d1 和 d2同时执行呢?
     这时就需要由一个线程控制d1,由另一个线程控制d2.
那如何在Java中创建一个线程呢?
     其实Java中对线程这类事物已经进行了描述,并提供了相对应的对象。这个对象就是Thread。
通过API查阅,发现Thread类描述时,有两种创建线程的方式。
方式一:定义一个类继承Thread类,并覆盖Thread类中run方法。
问题:为什么要继承Thread,为什么要覆盖run?
       其实直接建立Thread类对象即可。并开启线程执行就可以了。但是虽然线程执行了,可是执行的代码是该线程默认的代码,该代码就存放在run方法中。
       可是定义线程的目的是为了执行自定义的代码。而线程运行代码都存储在run方法中,所以只有覆盖了run方法,才可以运行自定义的内容,想要覆盖,必须先要继承。主线程运行的代码都在main函数中,自定义线程运行的代码都在run方法中。
直接创建Thread类的子类对象就是创建了一个线程。
在内存中其实:
     1.堆内存中产生了一个对象,
     2.需要调用了底层资源,去创建执行路径。
如果直接调用该对象的run方法。
    这时,底层资源并没有完成线程的创建和执行。 仅仅是简单的对象调用方法的过程。所以这时,执行控制流程的只有主线程 .
如果想要开启线程,需要去调用Thread类中另一个方法完成。
start方法完成:该方法做了两件事,1,开启线程,2,调用了线程的run方法。
例子2:创建两个线程。
[java]  view plain  copy
  1. package cn.itheima.day09;  
  2. class Demo2 extends Thread{  
  3.     private String name;  
  4.     Demo2(String name) {  
  5.         this.name=name;  
  6.     }  
  7.     public void run(){  
  8.         for(int x=0;x<10;x++){  
  9.             //for(int y=-99999999;y<999999999;y++){}  //为了减缓程序的执行  
  10.             System.out.println("name="+name);  
  11.         }  
  12.     }  
  13. }  
  14. public class ThreadDemo2 {  
  15.     public static void main(String[] args) {  
  16.         //创建了一个Thread子类对象,其实就是在创建一个线程  
  17.         Demo2 d1 = new Demo2("one");    
  18.         Demo2 d2 = new Demo2("two");  
  19.         d1.start();  
  20.         d2.start();  
  21.         for(int x=0;x<20;x++){  
  22.             System.out.println("main------"+x);  
  23.         }  
  24.     }  
  25. }  
当创建了两个线程对象d1,d2后,这时程序就有了三个线程在同时执行。当主函数执行完d1.start(),d2.start()后,这时三个线程同时打印,结果比较杂乱:这是因为线程的随机性造成的。
随机性的原理是windows中的多任务同时执行,其实就是多个应用程序在同时执行。而每一个应用程序都有线程来负责控制的。所以window就是一个多线程的操作系统。那么cpu是负责提供程序运算的设备。
cpu特点:在某一个时刻,只能执行一个程序,所以多个程序执行并不是真正的同时执行。其实就是cpu做这快速的切换完成的。只是你感觉上同时而已.
能不能真正意义上的同时执行呢?
     可以,要是计算机有多个CPU,就可以了。也就是现在所见多核。
线程中的几个方法。
   多线程的创建,为了对各个线程进行标识,他们有一个自己默认的名称。格式:Thread-编号,编号从0开始。
例子3:获得程序中多线程中,各个线程的名字。

   
   
[java] view plain copy
  1. package cn.itheima.day09;  
  2. class Demo3 extends Thread{  
  3.     private String name;  
  4.     Demo3(String name) {  
  5.         super(name);  
  6.         //this.name=name;  
  7.     }  
  8.     public void run(){  
  9.         for(int x=0;x<10;x++){  
  10.             //for(int y=-99999999;y<999999999;y++){}  //为了减缓程序的执行  
  11.     //因为Demo类是Thread类的子类,所以可以直接使用thread类中的gatName()方法,获取当前线程的名字。  
  12.             System.out.println(getName()+"----------"+name);  
  13.         }  
  14.         //System.out.println("name="+name+"--------"+3/0);  
  15.     }  
  16. }  
  17. public class ThreadDemo3 {  
  18.     public static void main(String[] args) {  
  19.         //创建了一个Thread子类对象,其实就是在创建一个线程  
  20.         Demo3 d1 = new Demo3("小红");    
  21.         Demo3 d2 = new Demo3("小黄");  
  22.         //d1.setName("小白");  //自定义线程的名字  
  23.         //d2.setName("小黑");  
  24.         d1.start();  
  25.         d2.start();  
  26.         for(int x=0;x<20;x++){  
  27.         //如何获取到主线程对象呢?  
  28.     //可以通过Thread类中的一个方法,currentThread()返回当前的线程对象,该方法是静态的。  
  29.             System.out.println(Thread.currentThread().getName()+"------"+x);  
  30.         }  
  31.         //System.out.println("main------->"+4/0);  
  32.     }  
  33. }  
staticThread currentThread():获取当前线程对象。
StringgetName():获取线程名称。
void  setName():设置线程的名称。
Thread(Stringname):构造函数,线程对象一建立就可以指定名称。
例子4:铁路售票,一共100张,通过四个窗口卖完。

  
  
[java] view plain copy
  1. package cn.itheima.day09;  
  2. /** 
  3. * 需求: 
  4. * 铁路售票,一共100张,通过四个窗口卖完。 
  5. * 因为售票动作被同时执行所以使用到了多线程技术。 
  6. * 现在是要处理票资源和处理动作都被封装到了Thread类的子类中。 
  7. * 那么对于资源,是创建几个对象,就有几份资源。 
  8. * 当然可以用静态解决,但是,生命周期过长。 
  9. * 所以可以参考另一个创建线程的方式。 
  10. * 创建线程方式二:实现Runnable接口。 
  11. * @author wl-pc 
  12. */  
  13. class TickectWin extends Thread{  
  14.     public static int tickets = 100;  
  15.     public void run(){  
  16.         while(true){  
  17.             if(tickets>0){  
  18.                 System.out.println(getName()+"......"+tickets--);  
  19.             }  
  20.         }  
  21.     }  
  22. }  
  23. public class TicketDemo {  
  24.     public static void main(String[] args) {  
  25.         TickectWin t1=new TickectWin();  
  26.         TickectWin t2=new TickectWin();  
  27.         TickectWin t3=new TickectWin();  
  28.         TickectWin t4=new TickectWin();  
  29.         t1.start();  
  30.         t2.start();  
  31.         t3.start();  
  32.         t4.start();  
  33.     }  
  34. }  
例子5:修改例子4中的程序,使用实现Runnable接口的方法去完成铁路售票。

  
  
[java] view plain copy
  1. package cn.itheima.day09;  
  2. /** 
  3. * 需求: 
  4. * 铁路售票,一共100张,通过四个窗口卖完。 
  5. * 因为售票动作被同时执行所以使用到了多线程技术。 
  6. * 现在是要处理票资源和处理动作都被封装到了Thread类的子类中。 
  7. * 那么对于资源,是创建几个对象,就有几份资源。 
  8. * 当然可以用静态解决,但是,生命周期过长。 
  9. * 所以可以参考另一个创建线程的方式。 
  10. * 创建线程方式二:实现Runnable接口。 
  11. * @author wl-pc 
  12. */  
  13. class TickectWin implements Runnable{  
  14.     public  int tickets = 100;  
  15.     @Override  
  16.     public void run() {  
  17.         while(true){  
  18.             if(tickets>0){System.out.println(Thread.currentThread().getName()+"......"+tickets--);  
  19.             }  
  20.         }  
  21.     }  
  22. }  
  23. //创建线程对象,要么是线程对象,要么是线程的子类对象  
  24. public class TicketDemo {  
  25.     public static void main(String[] args) {  
  26.         TickectWin t=new TickectWin();  
  27.         Thread t1=new Thread(t);  
  28.         Thread t2=new Thread(t);  
  29.         Thread t3=new Thread(t);  
  30.         Thread t4=new Thread(t);  
  31.         t1.start();  
  32.         t2.start();  
  33.         t3.start();  
  34.         t4.start();  
  35.     }  
  36. }  
创建线程的两种方式:
1,继承Thread类。
    步骤:
      1)定义类继承Thread。
      2)覆盖Thread类中的run方法,run方法用于存储多线程要运行的代码。
      3)创建Thread类的子类对象创建线程。
      4)调用Thread类中的start方法开启线程,并执行子类中的run方法。
 
    特点:
       1.当类去描述事物,事物中有属性和行为。
       如果行为中有部分代码需要被多线程所执行,同时还在操作属性。就需要该类继承Thread类,产生该类的对象作为线程对象,可是这样做会导致每一个对象中都存储一份属性数据。无法在多个线程中共享该数据。加上静态,虽然实现了共享但是生命周期过长。
       2.如果一个类明确了自己的父类,那么很遗憾,它就不可以在继承Thread。
       因为java不允许类的多继承。
2,实现Runnable接口:
    步骤:
      1)定义类实现Runnable接口。
      2)覆盖接口中的run方法,将多线程要运行的代码定义在方法中。
      3)通过Thread类创建线程对象,并将实现了Runnable接口的子类对象
        作为实际参数传递给Thread类的构造函数。
为什么非要把Runnable接口的子类对象传递给Thread类的构造函数呢?
 是因为线程对象在建立时,必须要明确自己要运行的run方法,而这个run方法定义在了Runnable接口的子类中,所以要将该run方法所属的对象传递给Thread类的构造函数。让线程对象一建立,就知道运行哪个run方法。
      4)调用Thread类中的start方法,开启线程,并执行Runanble接口子类中的run方法。
    特点:
      1.描述事物的类中封装了属性和行为,如果有部分代码需要被多线程所执行。同时还在操作属性。那么可以通过实现Runnable接口的方式。因为该方式是定义一个Runnable接口的子类对象,可以被多个线程所操作实现了数据的共享。
       2.实现了Runnable接口的好处,避免了单继承的局限性。
       也就是说,一个类如果已经有了自己的父类是不可以继承Thread类的。
但是该类中还有需要被多线程执行的代码。这时就可以通过在该类上功能扩展的形式。即可以实现一个Runnable接口。
所以在创建线程时,建议使用第二种方式。
线程安全问题.因为线程的随机性,有可能会导致多线程在操作数据时发生数据错误的情况产生。
线程安全问题产生的原因:
    当线程中多条代码在操作同一个共享数据时,一个线程将部分代码执行完,还没有继续执行其他代码时,被另一个线程获取cpu执行权,这时,共享数据操作就有可能出现数据错误。
    简单说:多条操作共享数据的代码被多个线程分开执行造成的
    安全问题涉及的内容:
        1,共享数据。
        2,是否被多条语句操作。
    这也是判断多线程程序是否存在安全隐患的依据。

例子6:在线程中实现同步代码块来解决线程安全的问题。

  
  
[java] view plain copy
  1. package cn.itheima.day09;  
  2. /** 
  3. * 需求: 
  4. * 铁路售票,一共100张,通过四个窗口卖完。 
  5. * 因为售票动作被同时执行所以使用到了多线程技术。 
  6. * 现在是要处理票资源和处理动作都被封装到了Thread类的子类中。 
  7. * 那么对于资源,是创建几个对象,就有几份资源。 
  8. * 当然可以用静态解决,但是,生命周期过长。 
  9. * 所以可以参考另一个创建线程的方式。 
  10. * 创建线程方式二:实现Runnable接口。 
  11. * @author wl-pc 
  12. */  
  13. class TickectWin1 implements Runnable{  
  14.     public int tickets = 100;  
  15.     Object Obj = new Object();  
  16.     @Override  
  17.     public void run() {  
  18.         while(true){  
  19.             //实现同步功能,可以解决线程安全问题  
  20.             synchronized (Obj) {  
  21.                 if(tickets>0){  
  22.                     try {Thread.sleep(10);} catch (InterruptedException e) {}  
  23.                     System.out.println(Thread.currentThread().getName()+"......"+tickets--);  
  24.                 }  
  25.             }  
  26.         }  
  27.     }  
  28. }  
  29. //创建线程对象,要么是线程对象,要么是线程的子类对象  
  30. public class TicketDemo_syn {  
  31.     public static void main(String[] args) {  
  32.         TickectWin1 t=new TickectWin1();  
  33.         Thread t1=new Thread(t);  
  34.         Thread t2=new Thread(t);  
  35.         Thread t3=new Thread(t);  
  36.         Thread t4=new Thread(t);  
  37.         t1.start();  
  38.         t2.start();  
  39.         t3.start();  
  40.         t4.start();  
  41.     }  
  42. }  

解决安全问题的方式:
    java中提供了一个同步机制。
    解决原理;让多条操作共享数据的代码在某一时间段,被一个线程执行完,在执行过程中,其他线程不可以参与运算。
    同步格式:
       同步代码块:
       synchronized(对象)//该对象可以是任意对象
       {
           需要被同步的代码;
       }
    同步的原理,通过一个对象锁,将多条操作共享数据的代码进行了封装并加锁。只有持有这个锁的线程才有机会进入同步中的去执行,在执行期间,即使其他线程获取到执行权,因为没有获取到锁,所以只能在外面等着。只有同步中的线程执行完同步代码块中的代码。出同步代码时,才会释放这个锁,那么其他程序线程才有机会去获取这个锁,并只能有一个获取到而且进入到同步中。
    举例:火车上的卫生间。锁机制最好的体现。
    同步好处:
       同步的出现解决了多线程的安全问题。
    同步弊端       因为多个线程每次都要判断这个锁,所以效率会降低。
    以后写同步你会发现这样一个问题,如果出现了安全问题后:加入了同步,安全问题依然存在。
    因为同步是有前提的:
    同步前提       1.必须是两个或者两个以上的线程才需要同步。
       2.必须要保证多个线程使用的是同一个锁,才可以实现多个线程被同步。
    如果出现加上同步安全问题依然存在,就按照两个前提来排查问题。
例子7:有两个储户,到同一个银行存钱,每次存100,存3次,两个存储是随机存入的。

  
  
[java] view plain copy
  1. package cn.itheima.day09;  
  2. class Bank{  
  3.     private int sum = 0;  
  4.     public void add(int num){  
  5.         sum = sum + num;  
  6. try {Thread.sleep(10);} catch (Exception e) {}  
  7.         System.out.println(Thread.currentThread().getName()+"-----sum="+sum);  
  8.     }  
  9. }  
  10. class Cus implements Runnable{  
  11.     Bank b = new Bank();  
  12.     @Override  
  13.     public void run() {  
  14.         for(int x=0;x<3;x++){  
  15.             b.add(100);  
  16.         }  
  17.     }  
  18. }  
  19. public class BankDemo {  
  20.     public static void main(String[] args) {  
  21.         Cus cus=new Cus();  
  22.         Thread t1 = new Thread(cus);  
  23.         Thread t2 = new Thread(cus);  
  24.         t1.start();  
  25.         t2.start();  
  26.     }  
  27. }  
这个程序有没有安全隐患呢?
    分析:
    1,查看线程代码中是否有共享数据。
    2,这个共享数据有没有被多条语句所操作。 
    发现,sum是共享数据。有两条语句在操作这个共享数据,如果这两条语句被多个线程分开执行。也就是一个线程没有执行完,其他线程就参与执行,就容易发生安全问题.
解决办法:加入同步机制。 将需要被一个线程一次执行完的代码存储在同步代码块中 

例子8:修改例7,实现同步函数,来解决线程安全的问题。

[java] view plain copy
  1. package cn.itheima.day09;  
  2. class Bank{  
  3.     private int sum = 0;  
  4.     public synchronized void add(int num){  
  5.         sum = sum + num;  
  6.         try {Thread.sleep(10);} catch (Exception e) {}  
  7.         System.out.println(Thread.currentThread().getName()+"-----sum="+sum);  
  8.     }  
  9. }  
  10. class Cus implements Runnable{  
  11.     Bank b = new Bank();  
  12.     @Override  
  13.     public void run() {  
  14.         for(int x=0;x<3;x++){  
  15.             b.add(100);  
  16.         }  
  17.     }  
  18. }  
  19. public class BankDemo {  
  20.     public static void main(String[] args) {  
  21.         Cus cus=new Cus();  
  22.         Thread t1 = new Thread(cus);  
  23.         Thread t2 = new Thread(cus);  
  24.         t1.start();  
  25.         t2.start();  
  26.     }  
  27. }  

发现,同步代码块是用于封装代码的。
而函数也是用封装代码的。所不同之处是同步带有锁机制。
那么如果让函数这个封装体具备同步的特性。不就可以取代同步代码块了吗?
怎么让函数具备同步性呢?
其实很简单,只要在函数上加上一个同步关键字(synchronized)修饰即可。这就是同步的另一个体现形式:同步函数。
示例代码:
public synchronized void add(int num){
    sum = sum + num;
    try {Thread.sleep(10);}catch(Exception e) {}
    System.out.println(Thread.currentThread().getName()+"-----sum="+sum);
}
验证同步函数到底用的是哪个锁?
例子8:需求
    同样是卖票。
    一个线程到同步函数中卖票。
    一个线程到同步代码块中卖票。
    保证他们卖的是同100张票.
    如果同步函数和同步代码块使用的是同一个锁,就不会出现0号票这种错误的票情况。
[java]  view plain  copy
  1. package cn.itheima.day09;  
  2. class TicketWin2 implements Runnable {  
  3.     private int tickets = 100;  
  4.     Object obj = new Object();  
  5.     public boolean flag = true;  
  6.     @Override  
  7.     public void run() {  
  8.         if(flag){  
  9.             while (true) {  
  10.                 synchronized (obj) {  
  11.                     if (tickets > 0) {  
  12.                         try {Thread.sleep(10);} catch (InterruptedException e) {}  
  13.                         System.out.println(Thread.currentThread().getName()  
  14.                                 + "---code---" + tickets--);  
  15.                     }  
  16.                 }  
  17.             }  
  18.         }else {  
  19.             while(true)  
  20.                 show();  
  21.         }  
  22.     }  
  23.     public synchronized void show() {  
  24.         if (tickets > 0) {  
  25.             try {Thread.sleep(10);} catch (InterruptedException e) {}  
  26.             System.out.println(Thread.currentThread().getName() + "---show----"  
  27.                     + tickets--);  
  28.         }  
  29.     }  
  30. }  
  31. public class ThisLockDemo {  
  32.     public static void main(String[] args) {  
  33.         TicketWin2 t = new TicketWin2();  
  34.         Thread t1 = new Thread(t);  
  35.         Thread t2 = new Thread(t);  
  36.         t1.start();  
  37.         try {Thread.sleep(10);} catch (InterruptedException e) {}  
  38.         t.flag=false;  
  39.         t2.start();  
  40.     }  
  41. }  
运行结果:还是有0号票的存在。
修改例子8的部分代码:

  
  
[java] view plain copy
  1. if(flag)  
  2. while(true){  
  3.         synchronized(this){  
  4.            if(tickets>0){  
  5.               try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()  
  6. +"....code...."+tickets--);  
  7.             }  
  8.         }  
  9. }  
测试结果运行正常。
通过该示例:验证结果:同步函数使用的锁是 this
同步函数和同步代码的区别:
    同步代码块使用的锁可以是任意对象。
    同步函数使用的锁是固定对象是 this
    所以一般定义同步时,建议使用 同步代码块. 当然,如果对象可以使用this。那么可以简化同步函数的形式。
验证静态同步函数到底用的是哪个锁?
    静态同步函数使用的锁肯定不是this。因为静态函数中不可以定义this。静态随着类的加载而加载,这时有可能内容还没有该类对象。但是一个类加载进内存,会先将这个类对应的字节码文件封装成对象。该对象的表示方式:类名.class 代表一个类字节码文件对象,该对象在内存中是唯一的。
结论:静态同步函数使用的锁就是该类对应字节码文件对象。也就是 类名.class
例子9:验证静态同步函数到底用的是:类名.class。

  
  
[java] view plain copy
  1. package cn.itheima.day09;  
  2. class TicketWin3 implements Runnable {  
  3.     private static int tickets = 100;  
  4.     Object obj = new Object();  
  5.     public boolean flag = true;  
  6.     @Override  
  7.     public void run() {  
  8.         if(flag){  
  9.             while (true) {  
  10.                 synchronized (TicketWin3.class) {  
  11.                     if (tickets > 0) {  
  12.                         try {Thread.sleep(10);} catch (InterruptedException e) {}  
  13.                         System.out.println(Thread.currentThread().getName()  
  14.                                 + "---code---" + tickets--);  
  15.                     }  
  16.                 }  
  17.             }  
  18.         }else {  
  19.             while(true)  
  20.                 show();  
  21.         }  
  22.     }  
  23.     public synchronized void show() {  
  24.         if (tickets > 0) {  
  25.             try {Thread.sleep(10);} catch (InterruptedException e) {}  
  26.             System.out.println(Thread.currentThread().getName() + "---show----"  
  27.                     + tickets--);  
  28.         }  
  29.     }  
  30. }  
  31. public class StaticLockDemo {  
  32.     public static void main(String[] args) {  
  33.         TicketWin3 t = new TicketWin3();  
  34.         Thread t1 = new Thread(t);  
  35.         Thread t2 = new Thread(t);  
  36.         t1.start();  
  37.         /*让主线程睡眠10毫秒。让t1有获取到cpu的执行权。去if中的代码块执行。 
  38.            让10毫秒后,主线程在有执行资格,获取到执行权,将标记改为false。 
  39.            并开启t2.这时t2一定去else中的同步函数执行*/  
  40.         try {Thread.sleep(10);} catch (InterruptedException e) {}  
  41.         t.flag=false;  
  42.         t2.start();  
  43.     }  
  44. }  
单例模式有两种体现形式:
1,饿汉式。
class Single{
    privatestatic final Single s = new Single();
    private Single(){}
    publicstatic Single getInstance(){
        returns;
    }
}
2,懒汉式。
class Single{
    private static Single s = null;
    private Single(){}
    public static Single getInstance(){
        if(s==null){
            s = new Single();
        }
        return s;
    }
}
解决线程安全问题的修改代码:

  
  
[java] view plain copy
  1. class Single{  
  2.     private static Single s = null;  
  3.     private Single(){}  
  4.     public static  Single getInstance(){  
  5.         if(s==null){  //提高效率  
  6.              //函数是静态函数,所以锁不可能是this  
  7.             synchronized(Single.class){  
  8.                 if(s==null){  
  9.                     s = new Single();  
  10.                 }  
  11.             }  
  12.         }  
  13.         return s;  
  14.     }  
  15. }  
相对于懒汉式的单例设计:当多个线程并发执行getInstance方法时,容易发生线程安全问题。因为s是共享数据,有多条语句在操作共享数据。解决方式很简单。只要让getInstance方法具备同步性即可.即在getInstance()方法中上使用synchronized就可以解决问题。这虽然解决了线程安全问题,但是多个线程每一次获取该实例,都要调用这个方法,每次调用都判断一次锁,所以效率会比较低.为了保证安全,同时为了提高效率.可以通过双重判断的形式来完成。
原理:就是减少线程判断的锁次数。
虽然解决安全问题,也解决了效率问题,但是代码过多。
所以建议使用饿汉式体现单例设计模式。
但是面试时,考的都是懒汉式。
面试考懒汉式,可能会问到的问题?
    1.  什么是延迟加载?
    2.  如果是多线程怎么办?解决要加同步。
    3.  效率低怎么办?利用双重判断解决问题。
    4.  懒汉式用的锁是哪个锁?类名.class
虽然同步的出现解决了线程安全问题。
但是也带来了一些弊端    1,效率会降低。
    2,容易引发死锁。
死锁经常出现的状况为:同步嵌套。
例子10.线程产生死锁的情况。

  
  
[java] view plain copy
  1. package cn.itheima.day09;  
  2. /** 
  3.  * 虽然同步的出现解决了线程安全问题。 
  4.  * 但是也带来了一些弊端: 
  5.  *   1,效率会降低。 
  6.  *   2,容易引发死锁。 
  7.  *  死锁经常出现的状况为:同步嵌套。 
  8.  * @author wl-pc 
  9. */  
  10. class TicketWin4 implements Runnable {  
  11.     private int tickets = 100;  
  12.     Object obj = new Object();  
  13.     public boolean flag = true;  
  14.     @Override  
  15.     public void run() {  
  16.         if(flag){  
  17.             while (true) {  
  18.                 synchronized (obj) {  
  19.                     show();  
  20.                 }  
  21.             }  
  22.         }else {  
  23.             while(true)  
  24.                 show();  
  25.         }  
  26.     }  
  27.     public synchronized void show() {  
  28.         synchronized (obj) {  
  29.             if (tickets > 0) {  
  30.                 try {Thread.sleep(10);} catch (InterruptedException e) {}  
  31.                 System.out.println(Thread.currentThread().getName() + "---show---"  
  32.                         + tickets--);  
  33.             }  
  34.         }  
  35.     }  
  36. }  
  37. public class DeadLockDemo {  
  38.     public static void main(String[] args) {  
  39.         TicketWin4 t = new TicketWin4();  
  40.         Thread t1 = new Thread(t);  
  41.         Thread t2 = new Thread(t);  
  42.         t1.start();  
  43.         /*让主线程睡眠10毫秒。让t1有获取到cpu的执行权。去if中的代码块执行。 
  44.            让10毫秒后,主线程在有执行资格,获取到执行权,将标记改为false。 
  45.            并开启t2.这时t2一定去else中的同步函数执行*/  
  46.         try {Thread.sleep(10);} catch (InterruptedException e) {}  
  47.         t.flag=false;  
  48.         t2.start();  
  49.     }  
  50. }  
例子11.线程产生死锁的例子。

  
  
[java] view plain copy
  1. package cn.itheima.day09;  
  2. class Test implements Runnable{  
  3.     private boolean flag;  
  4.     Test(boolean flag) {  
  5.         this.flag = flag;  
  6.     }  
  7.     @Override  
  8.     public void run() {  
  9.         if(flag){  
  10.             while(true){  
  11.                 synchronized (MyLock.locka) {  
  12.                     System.out.println(Thread.currentThread().getName()+"--if-----locka----");  
  13.                     synchronized (MyLock.lockb) {  
  14.                         System.out.println(Thread.currentThread().getName()+"--if-----lockb----");  
  15.                     }  
  16.                 }  
  17.             }  
  18.         }else {  
  19.             while(true){  
  20.                 synchronized (MyLock.lockb) {  
  21.                     System.out.println(Thread.currentThread().getName()+"--else-----lockb----");  
  22.                     synchronized (MyLock.locka) {  
  23.                         System.out.println(Thread.currentThread().getName()+"--else-----locka----");  
  24.                     }  
  25.                 }  
  26.             }  
  27.         }  
  28.     }  
  29. }  
  30. class MyLock{  
  31.     public static Object locka = new Object();  
  32.     public static Object lockb = new Object();  
  33. }  
  34. public class DeadLockTest {  
  35.     public static void main(String[] args) {  
  36.         Test t1 = new Test(true);  
  37.         Test t2 = new Test(false);  
  38.         Thread th1 = new Thread(t1,"小白");  
  39.         Thread th2 = new Thread(t2,"小黑");  
  40.         th1.start();  
  41.         th2.start();  
  42.     }  
  43. }  
例子12.线程间通信的例子。

  
  
[java] view plain copy
  1. package cn.itheima.day10;  
  2. class Resource{  
  3.     String name;  
  4.     String sex;  
  5. }  
  6. //输入  
  7. class Input implements Runnable{  
  8.     private Resource r = new Resource();  
  9.     Input(Resource r) {  
  10.        this.r = r;  
  11.     }  
  12.     @Override  
  13.     public void run() {  
  14.         int x = 0;   //定义x是为了做切换,切换出入的是小王还是小红  
  15.         while(true){  
  16.             //在这里使用同步可以解决线程安全的问题,不会使的结果错乱  
  17.             //但是加入同步synchronized(this)安全问题依然存在,所  
  18.             //以要考虑是否有多个(in和out两个线程)线程在操作同一个  
  19.             //数据和这两个线程使用的锁是否是同一把锁。  
  20.             synchronized (r) {     
  21.             //因为synchronized (任意对象),而且要保证in和out使用同一把锁,  
  22.                     //这时想到Resource是相同的,所以in和out都使用Resource的对象(即资源唯一)。即 synchronized (r)  
  23.                 if(x==0){  
  24.                     r.name = "小王";  
  25.                     r.sex = "男";      
  26.                 }else {  
  27.                     r.name = "小红";  
  28.                     r.sex = "女女女女女女";     
  29.                 }  
  30.             }  
  31.             //做切换:任何数模于2=(0还是1)  
  32.             x = (x+1)%2;  //利用这个算式,是为了让线程进入if或者else中  
  33.         }  
  34.     }  
  35. }  
  36. //输出  
  37. class Output implements Runnable{  
  38.     private Resource r = new Resource();  
  39.     Output(Resource r) {  
  40.         this.r = r;  
  41.     }  
  42.     @Override  
  43.     public void run() {  
  44.         while(true){  
  45.             synchronized (r) {  
  46.                 System.out.println(r.name+"-------"+r.sex);  
  47.             }  
  48.         }  
  49.     }  
  50. }  
  51. public class ResourceDemo {  
  52.     public static void main(String[] args) {  
  53.         Resource r = new Resource();  
  54.         Input in = new Input(r);  
  55.         Output out = new Output(r);  
  56.         Thread t1 = new Thread(in);  
  57.         Thread t2 = new Thread(out);  
  58.         t1.start();  
  59.         t2.start();  
  60.     }  
  61. }  
例子13,让输出的数据间隔格式输出。

  
  
[java] view plain copy
  1. /** 
  2.  * 需求:希望输入一个信息,就打印一个信息。 
  3.  * 输入在进行时,输出是不可以执行的,当输入完毕,再让输出执行。 
  4.  * 到底是执行输入还是输出,是通过资源中的标记判断的。 
  5.  * 这里要用到多线程的一个机制:等待唤醒机制。 
  6.  * 使用的方法是:wait()  <----->  notity() 
  7.  * wait(): 让线程等待, 
  8.  * notity(): 唤醒被等待的线程。 
  9. * 注意:等待唤醒机制通常都用在同步中。因为需要锁的支持。 
  10.  * 而且必须要明确wait()、notity()所作用的锁对象。 
  11.  */  
  12. package cn.itheima.day10;  
  13. class Resource2{  
  14.     String name;  
  15.     String sex;  
  16.     boolean b = false;  
  17. }  
  18. //输入  
  19. class Input2 implements Runnable{  
  20.     private Resource2 r = new Resource2();  
  21.     Input2(Resource2 r) {  
  22.        this.r = r;  
  23.     }  
  24.     @Override  
  25.     public void run() {  
  26.         int x = 0;   //定义x是为了做切换,切换出入的是小王还是小红  
  27.         while(true){  
  28.             //在这里使用同步可以解决线程安全的问题,不会使的结果错乱  
  29.             //但是加入同步synchronized(this)安全问题依然存在,所  
  30.             //以要考虑是否有多个(in和out两个线程)线程在操作同一个  
  31.             //数据和这两个线程使用的锁是否是同一把锁。  
  32.             synchronized (r) {     
  33.             //因为synchronized (任意对象),而且要保证in和out使用同一把锁,  
  34.                     //这时想到Resource是相同的,所以in和out都使用Resource的对象(即资源唯一)。即 synchronized (r)  
  35.                 if(r.b){  
  36.                     try {  
  37.                         r.wait();   //让r上的线程处于等待  
  38.                     } catch (InterruptedException e) {  
  39.                         e.printStackTrace();  
  40.                     }  
  41.                 }  
  42.                 //如果r.b=false是不符合if(r.b)的,所以就不会去执行wait(),  
  43.                 //那么直接就执行了下面的语句,然后,将b的值改为true,然后当  
  44.                 //线程再次执行时,会判断b的值,当b的值为true时,线程不会执行  
  45.                 //会wait(),只有当线程被notity()时,线程才处于临时阻塞状态。  
  46.                 if(x==0){  
  47.                     r.name = "小王";  
  48.                     r.sex = "男";      
  49.                 }else {  
  50.                     r.name = "小红";  
  51.                     r.sex = "女女女女女女";     
  52.                 }  
  53.                 r.b = true;  //修改标记的值为true  
  54.                 r.notify();   //notity()唤醒的都是r锁上的线程。  
  55.             }  
  56.             //做切换:任何数模于2=(0还是1)  
  57.             x = (x+1)%2;  //利用这个算式,是为了让线程进入if或者else中  
  58.         }  
  59.     }  
  60. }  
  61. //输出  
  62. class Output2 implements Runnable{  
  63.     private Resource2 r = new Resource2();  
  64.     Output2(Resource2 r) {  
  65.         this.r = r;  
  66.     }  
  67.     @Override  
  68.     public void run() {  
  69.         while(true){  
  70.             synchronized (r) {  
  71.                 if(!r.b){  
  72.                     try {  
  73.                         r.wait();  //让r上的线程处于等待  
  74.                     } catch (InterruptedException e) {  
  75.                         e.printStackTrace();  
  76.                     }  
  77.                 }  
  78.                 System.out.println(r.name+"-------"+r.sex);  
  79.                 r.b = false;  //修改标记为false  
  80.                 r.notify();   //notity()唤醒的都是r锁上的线程。  
  81.             }  
  82.         }  
  83.     }  
  84. }  
  85. public class ResourceDemo2 {  
  86.     public static void main(String[] args) {  
  87.         Resource2 r = new Resource2();  
  88.         Input2 in = new Input2(r);  
  89.         Output2 out = new Output2(r);  
  90.         Thread t1 = new Thread(in);  
  91.         Thread t2 = new Thread(out);  
  92.         t1.start();  
  93.         t2.start();  
  94.     }  
  95. }  
等待唤醒机制:
使用的方法是:wait()  <-----> notity()
  wait(): 让线程等待,将线程存储到一个线程池中。
  notity(): 唤醒被等待的线程。通常都唤醒线程池中的第一个。让被唤醒的线程处于临时阻塞状态。
  notityAll():唤醒所有的线程。将线程池中的所有线程都唤醒,都让他们从冻结状态转到临时阻塞状态(所有的线程全都wait了,没
  一个存活的了,这时,可以用notityAll唤醒全部wait的线程)
  注意:等待唤醒机制通常都用在同步中。因为需要锁的支持。而且必须要明确wait()、notity()所作用的锁对象。
 
 wait(),notity(),notityAll()这三个方法用于操作线程,可是都定义在Object类中,为什么?
      因为,这三个方法在使用时dou需要定义在同步中,要明确这些方法所操作的线程所属的锁。简单说,就是在A锁中被wait的线程,只能被A锁的notity唤醒。所以必须要表示wait,notity方法所属的锁对象。而锁对象可以使任意的对象,可以被任意的对象调用的方法肯定定义在Object.
例子14:将例13代码进行优化,将Resource中的属性私有化。

  
  
[java] view plain copy
  1. /** 
  2.  * 将代码进行优化,将Resource中的属性私有化 
  3.  */  
  4. package cn.itheima.day10;  
  5. class Resource3{  
  6.     private String name;  
  7.     private String sex;  
  8.     private boolean b = false;  
  9.     public synchronized void set(String name,String sex){  
  10.         if(b){  
  11.             try {this.wait();} catch (InterruptedException e) {}  //InterruptedException终端异常  
  12.         }  
  13.         this.name = name;  
  14.         this.sex = sex;  
  15.         b = true;  
  16.         this.notify();  
  17.     }  
  18.     public synchronized void out(){  
  19.         if(!b){  
  20.              try {this.wait();} catch (InterruptedException e) {}  //InterruptedException终端异常  
  21.         }  
  22.         System.out.println(name+"-------"+sex);  
  23.         b = false;  
  24.         this.notify();  
  25.     }  
  26. }  
  27. //输入  
  28. class Input3 implements Runnable{  
  29.     private Resource3 r;  
  30.     Input3(Resource3 r) {  
  31.        this.r = r;  
  32.     }  
  33.     @Override  
  34.     public void run() {  
  35.         int x = 0;   //定义x是为了做切换,切换出入的是小王还是小红  
  36.         while(true){  
  37.             if(x==0){  
  38.                 r.set("小王""男");   
  39.             }else {  
  40.                 r.set("小红""女女女女女女");    
  41.             }  
  42.             //做切换:任何数模于2=(0还是1)  
  43.             x = (x+1)%2;  //利用这个算式,是为了让线程进入if或者else中  
  44.         }  
  45.     }  
  46. }  
  47. //输出  
  48. class Output3 implements Runnable{  
  49.     private Resource3 r;  
  50.     Output3(Resource3 r) {  
  51.         this.r = r;  
  52.     }  
  53.     @Override  
  54.     public void run() {  
  55.         while(true){  
  56.             r.out();  
  57.         }  
  58.     }  
  59. }  
  60. public class ResourceDemo3 {  
  61.     public static void main(String[] args) {  
  62.         Resource3 r = new Resource3();  
  63.         Input3 in = new Input3(r);  
  64.         Output3 out3 = new Output3(r);  
  65.         Thread t1 = new Thread(in);  
  66.         Thread t2 = new Thread(out3);  
  67.         t1.start();  
  68.         t2.start();  
  69.     }  
  70. }  
生产者,消费者。
例子15:生产者,消费者的例子。

  
  
[java] view plain copy
  1. package cn.itheima.day10;  
  2. class Res{  
  3.    private String name;  
  4.    private int count = 0;  
  5.    private boolean b = false;  
  6.    public synchronized void set(String name){  
  7.        if(b){  
  8.            try {this.wait();} catch (InterruptedException e) {}  
  9.        }  
  10.        this.name = name+"-------"+count;  
  11.        count++;  
  12.        System.out.println(Thread.currentThread().getName()+".....生产者......"+this.name);  
  13.        b = true;  
  14.        this.notify();  
  15.    }  
  16.    public synchronized void out(){  
  17.        if(!b){  
  18.            try {this.wait();} catch (InterruptedException e) {}  
  19.        }  
  20.        System.out.println(Thread.currentThread().getName()+".....消费者......"+this.name);  
  21.        b = false;  
  22.        this.notify();  
  23.    }  
  24. }  
  25. class Pro implements Runnable{  
  26.     private Res r;  
  27.     Pro(Res r) {  
  28.         this.r = r;  
  29.     }  
  30.     @Override  
  31.     public void run() {  
  32.         while(true){  
  33.             r.set("产品");  
  34.         }  
  35.     }  
  36. }  
  37. class Cus implements Runnable{  
  38.     private Res r;  
  39.     Cus(Res r) {  
  40.         this.r = r;  
  41.     }  
  42.     @Override  
  43.     public void run() {  
  44.         while(true){  
  45.             r.out();  
  46.         }  
  47.     }  
  48. }  
  49. public class ProCusDemo {  
  50.     public static void main(String[] args) {  
  51.         Res res = new Res();  
  52.         Pro pro = new Pro(res);  
  53.         Cus cus = new Cus(res);  
  54.         Thread t1 = new Thread(pro);  
  55.         Thread t2 = new Thread(cus);  
  56.         t1.start();  
  57.         t2.start();  
  58.     }  
  59. }  
代码完成,加入同步和等待唤醒机制后,可以实现,生产一个,就消费一个。可是在实际开发中,生产者和消费者,并不是一个。有可能是多个也就是有多个生产者生产,有多个消费者消费。
例子16.多个生产者和多个消费者。

  
  
[java] view plain copy
  1. package cn.itheima.day10;  
  2. class Res2{  
  3.    private String name;  
  4.    private int count = 0;  
  5.    private boolean b = false;  
  6.    public synchronized void set(String name){  
  7.        if(b){  
  8.            try {this.wait();} catch (InterruptedException e) {}  
  9.        }  
  10.        this.name = name+"-------"+count;  
  11.        count++;  
  12.        System.out.println(Thread.currentThread().getName()+".....生产者......"+this.name);  
  13.        b = true;  
  14.        this.notify();  
  15.    }  
  16.    public synchronized void out(){  
  17.        if(!b){  
  18.            try {this.wait();} catch (InterruptedException e) {}  
  19.        }  
  20.        System.out.println(Thread.currentThread().getName()+".....消费者......"+this.name);  
  21.        b = false;  
  22.        this.notify();  
  23.    }  
  24. }  
  25. class Pro2 implements Runnable{  
  26.     private Res2 r;  
  27.     Pro2(Res2 r) {  
  28.         this.r = r;  
  29.     }  
  30.     @Override  
  31.     public void run() {  
  32.         while(true){  
  33.             r.set("产品");  
  34.         }  
  35.     }  
  36. }  
  37. class Cus2 implements Runnable{  
  38.     private Res2 r;  
  39.     Cus2(Res2 r) {  
  40.         this.r = r;  
  41.     }  
  42.     @Override  
  43.     public void run() {  
  44.         while(true){  
  45.             r.out();  
  46.         }  
  47.     }  
  48. }  
  49. public class ProCusDemo2 {  
  50.     public static void main(String[] args) {  
  51.         Res2 res = new Res2();  
  52.         Pro2 pro = new Pro2(res);  
  53.         Cus2 cus = new Cus2(res);  
  54.         Thread t1 = new Thread(pro);  
  55.         Thread t2 = new Thread(pro);  
  56.         Thread t3 = new Thread(cus);  
  57.         Thread t4 = new Thread(cus);  
  58.         t1.start();  
  59.         t2.start();  
  60.         t3.start();  
  61.         t4.start();  
  62.     }  
  63. }  
造成数据错误的原因:当生产者消费者多个时,本方的线程有可能唤醒本方的线程,而且,本方被唤醒后,没有判断标记,就进行了执行,会到导致原来本方的操作还没有被对方所操作就已经被覆盖了。生产者1,进行了生产后,将本方生产者2唤醒,生产者2没有判断标记直接继续生产,导致生产者1的产品还没有被消费就覆盖了。
解决方式:因为有本方唤醒本方的情况,所以必须每次的醒来都要判断一次标记。判断标记的动作要执行多次。所以不使用if,而是使用while.当进行while标记判断后,本方唤醒本方的动作还会发生,但是本方被唤醒后,继续判断标记,虽然没有将前一次操作覆盖,但是导致了程序中的线程都处于了等待状态。导致程序处于死锁状态。
到这里发现原因有两个:
     1,是判断标记。通过循环判断比较搞定。
     2,一定要唤醒对方。notify是唤醒一个,这个线程有可能是本方,也有可能是对方。干脆,无论是本方还是对方,全唤醒。通过notifyAll搞定。
在jdk1.5版本之后,出现了一些新的特性,将原理的线程进行了改良。
在java.util.concurrent.locks包中提供了一个接口Lock。替代了synchronized。而synchronized使用的是锁操作是隐式的。
Lock接口,使用的锁操作是显示的。
由两个方法来完成:
    lock():获取锁。
    unlock():释放锁。
还有一个对象,Condition.该对象的出现替代了Object中的wait(), notify(), notifyAll()这些操作监视器的方法.
替代后的方式:await(),signal(),signalAll().
接下来,把下列代码替换成JDK1.5版本只有的新对象。新功能最大好处,就是在一个Lock锁上,可以添加多组监视器对象。这样就可以实现本方只唤醒对方的线程.
例子17.把例子16换成JDK1.5版本的新对象操作锁的方法。

  
  
[java] view plain copy
  1. package cn.itheima.day10;  
  2. import java.util.concurrent.locks.Condition;  
  3. import java.util.concurrent.locks.Lock;  
  4. import java.util.concurrent.locks.ReentrantLock;  
  5. class Resourec{  
  6.    private String name;  
  7.    private int count = 0;  
  8.    private boolean b = false;  
  9.    //在这里就是把原来的同步代码块替换成了一个对象和两个方法。  
  10.    //定义一个锁  
  11.    Lock lock = new ReentrantLock();  
  12.    //通过制定的锁创建一个该锁上可以使用的监视器对象  
  13.    Condition con = lock.newCondition();  
  14.    public void set(String name){  
  15.        //获取锁  
  16.        lock.lock();  
  17.        try {  
  18.            while(b){  
  19.                try {con.await();} catch (InterruptedException e) {}  //线程等待  
  20.            }  
  21.            this.name = name+"-------"+count;  
  22.            count++;  
  23.            System.out.println(Thread.currentThread().getName()+".....生产者......"+this.name);  
  24.            b = true;  
  25.            con.signalAll();   //唤醒线程  
  26.         } catch (Exception e) {  
  27.         }finally{  
  28.             //释放锁  
  29.             lock.unlock();  
  30.         }  
  31.    }  
  32.    public void out(){  
  33.        lock.lock();  
  34.        try {  
  35.            while(!b){  
  36.                try {con.await();} catch (InterruptedException e) {}  //线程等待  
  37.            }  
  38.            System.out.println(Thread.currentThread().getName()+".....消费者......"+this.name);  
  39.            b = false;  
  40.            con.signalAll();  //唤醒线程  
  41.         }catch(Exception e){  
  42.               
  43.         }finally{  
  44.             lock.unlock(); //释放锁  
  45.         }  
  46.    }  
  47. }  
  48. class Product implements Runnable{  
  49.     private Resourec r;  
  50.     Product(Resourec r) {  
  51.         this.r = r;  
  52.     }  
  53.     @Override  
  54.     public void run() {  
  55.         while(true){  
  56.             r.set("产品");  
  57.         }  
  58.     }  
  59. }  
  60. class Custor implements Runnable{  
  61.     private Resourec r;  
  62.     Custor(Resourec r) {  
  63.         this.r = r;  
  64.     }  
  65.     @Override  
  66.     public void run() {  
  67.         while(true){  
  68.             r.out();  
  69.         }  
  70.     }  
  71. }  
  72. public class ProCusDemo3 {  
  73.     public static void main(String[] args) {  
  74.         Resourec res = new Resourec();  
  75.         Product pro = new Product(res);  
  76.         Custor cus = new Custor(res);  
  77.         Thread t1 = new Thread(pro);  
  78.         Thread t2 = new Thread(pro);  
  79.         Thread t3 = new Thread(cus);  
  80.         Thread t4 = new Thread(cus);  
  81.         t1.start();  
  82.         t2.start();  
  83.         t3.start();  
  84.         t4.start();  
  85.     }  
  86. }  
锁,是同步的机制.通过锁来控制同步.监视器是用于同步中对象的操作.比如wait(),notify(),notifyAll().每一组监视器方法对应一个锁.
到了jdk1.5以后,将监视器的方式从Object中,封装到了Condition对象中,每一个锁lock,可以对应多组监视器对象,这就可以实现本方只唤醒对方的操作。
代码实例:
[java]  view plain  copy
  1.  //定义一个锁  
  2.    Lock lock = new ReentrantLock();  
  3.    //通过制定的锁创建一个该锁上可以使用的监视器对象  
  4.    Condition proCon = lock.newCondition();  
  5.    //升级后的lock可以对应多组监视器对象。  
  6.    Condition cusCon = lock.newCondition();  
  7.    public void set(String name){  
  8.        //获取锁  
  9.        lock.lock();  
  10.        try {  
  11.            while(b){  
  12.                try {proCon.await();} catch (InterruptedException e) {}  //线程等待  
  13.            }  
  14.            this.name = name+"-------"+count;  
  15.            count++;  
  16.            System.out.println(Thread.currentThread().getName()+".....生产者......"+this.name);  
  17.            b = true;  
  18.            cusCon.signal();   //唤醒对方中一个线程  
  19.         } catch (Exception e) {  
  20.         }finally{  
  21.             //释放锁  
  22.             lock.unlock();  
  23.         }  
  24.    }   
  25.    public void out(){  
  26.        lock.lock();  
  27.        try {  
  28.            while(!b){  
  29.                try {cusCon.await();} catch (InterruptedException e) {}  //线程等待  
  30.            }  
  31.            System.out.println(Thread.currentThread().getName()+".....消费者......"+this.name);  
  32.            b = false;  
  33.            proCon.signal();  //唤醒对方中一个线程  
  34.         }catch(Exception e){  
  35.         }finally{  
  36.             lock.unlock(); //释放锁  
  37.         }  
  38.    }  
  39. }  
sleep和wait有什么区别?
对时间的指定。
     1,sleep方法必须指定时间。
     2,wait方法有重载形式,可以指定时间,也可以不指定时间。
对于执行权和锁的操作.
     1,sleep():释放执行权,不释放锁,因为肯定能醒,肯定可以恢复到临时阻塞状态。
     2,wait():释放执行权,释放锁,因为wait不释放锁,如果没有指定时间,那么其他线程都进行不了同步,无法将其唤醒。
代码实例:
public synchronized void show()
{
    if()
        wait();
    code....;
    notify();
    code....;
}
针对上面代码实例的结论:同步中可以有多个存活的线程,但是只能有一个执行同步的代码。因为只有一个线程会持有同步的锁。只有当该线程释放了锁,其他线程才会有机会获取到锁,而且只能有一个线程获取到锁,继续执行。
如何让线程停止。
停止线程有两种方式:
    1,使用Thread类中的stop方法。很遗憾,该方法过时了。
    2,线程执行的代码结束,也就是run方法结束。
    通常定义线程代码都有循环,因为需要单独开辟一个执行路径去重复做很多事情。既然有循环,只要控制住循环,即可结束run方法。
例子18.使用定义标记的方式来停止线程。

  
  
[java] view plain copy
  1. package cn.itheima.day10;  
  2. class StopThread implements Runnable{  
  3.     private boolean flag = true;  
  4.     @Override  
  5.     public void run() {  
  6.         while(flag){  
  7.             System.out.println(Thread.currentThread().getName()+"......");  
  8.         }  
  9.     }  
  10.     public void setFlag(){  
  11.         flag = false;  
  12.     }  
  13. }  
  14. public class StopThreadDemo {  
  15.     public static void main(String[] args) {  
  16.         StopThread stopThread = new StopThread();  
  17.         Thread t1 = new Thread(stopThread);  
  18.         Thread t2 = new Thread(stopThread);  
  19.         t1.start();  
  20.         t2.start();  
  21.         int num = 1;  
  22.         while(true){  
  23.             if(num++==50){  
  24.                 stopThread.setFlag();  
  25.                 break;  
  26.             }  
  27.             System.out.println(Thread.currentThread().getName()+"-------"+num);  
  28.         }  
  29.     }  
  30. }  
定义标记可以结束线程,但是如果线程在运行过程中存储了冻结状态,没有执行到标记,这时,程序还能结束吗?
代码实例:

  
  
[java] view plain copy
  1.  class StopThread implements Runnable{  
  2.     private boolean flag = true;  
  3.     @Override  
  4.     public synchronized void run() {  
  5.         while(flag){  
  6.             try {wait();} catch (InterruptedException e) {}  //这时,t1和t2全都wait(),没有执行到标记,程序就挂啦。  
  7.             System.out.println(Thread.currentThread().getName()+"......");  
  8.         }  
  9.     }  
  10.     public void setFlag(){  
  11.         flag = false;  
  12.     }  
  13. }  

 可以通过Thread类中的interrupt()方法中断线程的冻结状态。强制让其恢复到运行状态中来,就可以有机会执行标记,但是这种强制动作会发生InterruptedException异常。

例子19.使用Thread的interrupt()方法中断线程的冻结状态。

[java] view plain copy
  1. package cn.itheima.day10;  
  2. class StopThread implements Runnable{  
  3.     private boolean flag = true;  
  4.     @Override  
  5.     public synchronized void run() {  
  6.         while(flag){  
  7.             try {wait();} catch (InterruptedException e) {  
  8.                 System.out.println(Thread.currentThread().getName()+"......Exception");  
  9.                 setFlag();  
  10.             }  //这时,t1和t2全都wait(),没有执行到标记,程序就挂啦。  
  11.             System.out.println(Thread.currentThread().getName()+"......");  
  12.         }  
  13.     }  
  14.     public void setFlag(){  
  15.         flag = false;  
  16.     }  
  17. }  
  18. public class StopThreadDemo {  
  19.     public static void main(String[] args) {  
  20.         StopThread stopThread = new StopThread();  
  21.         Thread t1 = new Thread(stopThread);  
  22.         Thread t2 = new Thread(stopThread);  
  23.         t1.start();  
  24.         t2.start();  
  25.         int num = 1;  
  26.         while(true){  
  27.             if(num++==50){  
  28.                 //stopThread.setFlag();  
  29.                     t1.interrupt();   //将t1的线程的冻结状态强制清除,强制让其恢复到运行状态中。  
  30.                                   //但是这种强制动作会发生异常,需要对中断异常进行处理。  
  31.                                   //只有恢复到运行状态中,才有可能执行到标记。  
  32.                 t2.interrupt();  
  33.                 break;  
  34.             }  
  35.             System.out.println(Thread.currentThread().getName()+"-------"+num);  
  36.         }  
  37.     }  
  38. }  

setDeamon(boolean):可以将线程标记为后台线程。
线程分前台线程和后台线程两种。运行方式都一样都会获取cpu的执行权执行。不同的在于,结束方式不同。前台线程只有run方法结束,才结束。后台线程,run方法结束,结束。还有,如果run方法没结束,而前台线程都结束了。后台线程一样自动结束。
所以一个进程是否结束参考的是:是否还有前台线程存活。如果前台线程都结束了,那么进程也就是结束了。
实例代码:
[java]  view plain  copy
  1. public class StopThreadDemo {  
  2.     public static void main(String[] args) {  
  3.         StopThread stopThread = new StopThread();  
  4.         Thread t1 = new Thread(stopThread);  
  5.         Thread t2 = new Thread(stopThread);  
  6.         t1.setDaemon(true);  
  7.         t2.setDaemon(true);   //将两个线程全标记成后台线程  
  8.         t1.start();  
  9.         t2.start();  
  10.         int num = 1;  
  11.         while(true){  
  12.             if(num++==50){  
  13.                 //stopThread.setFlag();  
  14.                         //t1.interrupt();   //将t1的线程的冻结状态强制清除,强制让其恢复到运行状态中。  
  15.                                    //但是这种强制动作会发生异常,需要对中断异常进行处理。  
  16.                                    //只有恢复到运行状态中,才有可能执行到标记。  
  17.                 //t2.interrupt();  
  18.                 break;  
  19.             }  
  20.             System.out.println(Thread.currentThread().getName()+"-------"+num);  
  21.         }  
  22.     }  
  23. }  
可以想想 圣斗士星矢的例子,雅典娜就是前台线程,,星矢哥五个就是守护线程,雅典娜挂了,那哥五个就自动结束了。失业了。
join:临时加入一个线程进行执行。
例如:
当主线程获取到了cpu的执行权,执行时,执行到了A线程的join方法。这时就知道A线程要加入进来进行,那么A执行就需要cpu的执行权。而这时cpu的执行权在主线程持有,主线程会释放自己的执行权。让A线程进行执行。
那么主线程什么时候执行呢?
只有等待A线程执行完以后,主线程才会执行,此时主线程就处于冻结状态。
能让线程处于冻结状态的方式有3种:wait(),sleep(),join().

例子20.线程中join()方法的使用。

  
  
[java] view plain copy
  1. package cn.itheima.day10;  
  2. class Demo implements Runnable{  
  3.     @Override  
  4.     public void run() {  
  5.         for(int x =0;x<60;x++){  
  6.             System.out.println(Thread.currentThread().getName()+"----"+x);  
  7.         }  
  8.     }  
  9. }  
  10. public class JoinDemo {  
  11.     public static void main(String[] args) {  
  12.         Demo d=new Demo();  
  13.         Thread t1 = new Thread(d);  
  14.         Thread t2 = new Thread(d);  
  15.         t1.start();  
  16.         try {  
  17.             t1.join();   //当有了join()方法时,t1线程会全部执行完,其他线程才可以执行。  
  18.         } catch (InterruptedException e) {  
  19.             e.printStackTrace();  
  20.         }  
  21.         t2.start();  
  22.         for(int x = 0;x<60;x++){  
  23.             System.out.println(Thread.currentThread().getName()+"-------"+x);  
  24.         }  
  25.     }  
  26. }  
一般使用情况,当在线程执行过程中,需要一个运算结果,可以通过加入一个临时线程,将该结果进行运算,这时需要的结果的线程处于冻结状态,等加入的线程执行完,该线程在继续执行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值