A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始






5、多线程

5.1 多线程的概念

    5.1.3 创建线程方式二:实现Runnable接口

    1. 定义类实现Runnable接口。
    2. 覆盖接口中的run方法,将线程的任务代码封装到run方法中。
    3. 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
    4. 调用线程对象的start方法开启线程。

    实现Runnable接口的好处:
    1. 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
    2. 避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。

    示例:
  1. //准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行。
  2. //通过接口的形式完成。
  3. class Demo implements Runnable{
  4.       public void run(){
  5.             show();
  6.       }
  7.       public void show(){
  8.              for(int x = 0; x < 20; x++){
  9.                   System.out.println(Thread.currentThread().getName() + "..." + x);
  10.              }
  11.       }
  12. }

  13. class ThreadDemo{
  14.        public static void main(String[] args){
  15.             Demo d = new Demo();
  16.             Thread t1 = new Thread(d);
  17.             Thread t2 = new Thread(d);
  18.             t1.start();
  19.             t2.start();
  20.        }
  21. }
复制代码
    运行结果:



    Thread类、Runnable接口内部源码关系模拟代码:
  1. class Thread{
  2.       private Runnable r ;
  3.       Thread(){
  4.       }
  5.       Thread(Runnable r){
  6.              this.r = r;
  7.       }

  8.        public void run(){
  9.              if(r !=null)
  10.                    r.run();
  11.       }
  12.        public void start(){
  13.             run();
  14.       }
  15. }

  16. class ThreadImpl implements Runnable{
  17.        public void run(){
  18.             System.out.println("runnable run" );
  19.       }
  20. }

  21. class ThreadDemo4{
  22.        public static void main(String[] args){
  23.             ThreadImpl i = new ThreadImpl();
  24.             Thread t = new Thread(i);
  25.             t.start();
  26.       }
  27. }

  28. class SubThread extends Thread{
  29.        public void run(){
  30.             System.out.println("hahah" );
  31.       }
  32. }

  33. class ThreadDemo5{
  34.        public static void main(String[] args){
  35.             SubThread s = new SubThread();
  36.             s.start();
  37.       }
  38. }
复制代码

5.2 线程安全问题

    5.2.1 线程安全问题产生的原因

    需求:模拟4个线程同时卖100张票。

    代码:
  1. class Ticket implements Runnable{
  2.        private int num = 100;

  3.        public void run(){
  4.              while(true ){
  5.                    if(num > 0){
  6.                          try{
  7.                               Thread. sleep(10);
  8.                         } catch(InterruptedException e){
  9.                               e.printStackTrace();
  10.                         }
  11.                         System.out.println(Thread.currentThread().getName() + "...sale..." + num--);
  12.                   }
  13.             }
  14.       }
  15. }

  16. class TicketDemo{
  17.        public static void main(String[] args){
  18.             Ticket t = new Ticket();
  19.             Thread t1 = new Thread(t);
  20.             Thread t2 = new Thread(t);
  21.             Thread t3 = new Thread(t);
  22.             Thread t4 = new Thread(t);

  23.             t1.start();
  24.             t2.start();
  25.             t3.start();
  26.             t4.start();
  27.       }
  28. }
复制代码
   运行结果:

……

    原因分析:
    出现上图安全问题的原因在于Thread-0通过了if判断后,在执行到“num--”语句之前,num此时仍等于1。
    CPU切换到Thread-1、Thread-2、Thread-3之后,这些线程依然可以通过if判断,从而执行“num--”的操作,因而出现了0、-1、-2的情况。

   

    线程安全问题产生的原因:

    1. 多个线程在操作共享的数据。
    2. 操作共享数据的线程代码有多条。
    当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

    5.2.2 线程安全问题的解决方案

    思路:
    就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

    在java中,用同步代码块就可以解决这个问题。
    同步代码块的格式:
    synchronized(对象){
           需要被同步的代码;
    }

    同步的好处:解决了线程的安全问题。
    同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
    同步的前提:必须有多个线程并使用同一个锁。


    修改后代码:
  1. class Ticket implements Runnable{
  2.       private int num = 100;
  3.       Object obj = new Object();

  4.       public void run(){
  5.              while(true ){
  6.                    synchronized(obj ){
  7.                          if(num > 0){
  8.                               System.out.println(Thread.currentThread().getName() + "...sale..." + num--);
  9.                         }
  10.                    }
  11.              }
  12.       }
  13. }

  14. class TicketDemo{
  15.        public static void main(String[] args){
  16.             Ticket t = new Ticket();
  17.             Thread t1 = new Thread(t);
  18.             Thread t2 = new Thread(t);
  19.             Thread t3 = new Thread(t);
  20.             Thread t4 = new Thread(t);
  21.             
  22.             t1.start();
  23.             t2.start();
  24.             t3.start();
  25.             t4.start();
  26.       }
  27. }
复制代码
   运行结果:


……


    原因分析:
    上图显示安全问题已被解决,原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。因此,当num=1时,CPU切换到某个线程后,如上图的Thread-3线程,其他线程将无法通过同步代码块继而进行if判断语句,只有等到Thread-3线程执行完“num--”操作(此后num的值为0),并且跳出同步代码块后,才能抢到锁。其他线程即使抢到锁,然而,此时num值已为0,也就无法通过if语句判断,从而无法再执行“num--”的操作了,也就不会出现0、-1、-2等情况了。



    利用同步代码块解决安全问题案例:

    需求:储户,两个,每个都到银行存钱,每次存100,共存三次。

    代码:
  1. class Bank{
  2.        private int sum ;
  3.        public void add(int num){
  4.              synchronized(this ){
  5.                   sum = sum + num;
  6.                   System. out.println("sum = " + sum);
  7.             }
  8.       }
  9. }

  10. class Cus implements Runnable{
  11.        private Bank b = new Bank();
  12.        public void run(){
  13.              for(int x = 0; x < 3; x++){
  14.                    b.add(100);
  15.             }
  16.       }
  17. }

  18. class BankDemo{
  19.        public static void main(String[] args){
  20.             Cus c = new Cus();
  21.             Thread t1 = new Thread(c);
  22.             Thread t2 = new Thread(c);
  23.             t1.start();
  24.             t2.start();
  25.       }
  26. }
复制代码
   运行结果:


    原因分析:
    由如下代码中可以看到,同步代码块中的语句,存在可能有多个线程同时操作共享数据(sum)的情况,通过同步代码块即可解决存在的安全问题。


    如果不设置同步代码块,出现的结果如下:



    安全问题的另一种解决方案:同步代码块

    格式:在函数上加上synchronized修饰符即可。

    示例:
  1. class Bank{
  2.        private int sum ;
  3.        public synchronized void add(int num){ //同步函数
  4.              sum = sum + num;
  5.              System.out.println("sum = " + sum);
  6.        }
  7. }
复制代码
   运行结果:


    P.S.
    同步函数和同步代码块的区别:
    1. 同步函数的锁是固定的this。
    2. 同步代码块的锁是任意的对象。
    建议使用同步代码块。

    由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。

    示例:
  1. class Ticket implements Runnable{
  2.        private int num = 100;
  3.        boolean flag = true;

  4.        public void run(){
  5.              if(flag ){
  6.                    while(true ){
  7.                          synchronized(this ){
  8.                                if(num > 0){
  9.                                      try{
  10.                                           Thread. sleep(10);
  11.                                      } catch(InterruptedException e){
  12.                                           e.printStackTrace();
  13.                                      }
  14.                                      System. out.println(Thread.currentThread().getName() + "...obj..." + num--);
  15.                               }
  16.                         }
  17.                   }
  18.             } else
  19.                    while(true )
  20.                         show();
  21.         }

  22.        public synchronized void show(){
  23.              if(num > 0){
  24.                    try{
  25.                         Thread. sleep(10);
  26.                    } catch(InterruptedException e){
  27.                         e.printStackTrace();
  28.                    }
  29.                    System.out.println(Thread.currentThread().getName() + "...function..." + num--);
  30.             }
  31.       }
  32. }

  33. class SynFunctionLockDemo{
  34.        public static void main(String[] args){
  35.             Ticket t = new Ticket();
  36.             Thread t1 = new Thread(t);
  37.             Thread t2 = new Thread(t);
  38.             
  39.             t1.start();
  40.              try{//下面这条语句一定要执行。因为可能线程t1尚未真正启动,flag已经设置为false,那么当t1执行的时候,就会按照flag为false的情况执行,线程t2也按照flag为false的情况执行,实验就达不到目的了。
  41.                   Thread. sleep(10);
  42.             } catch(InterruptedException e){
  43.                   e.printStackTrace();
  44.             }
  45.             t. flag = false ;
  46.             t2.start();
  47.       }
  48. }
复制代码
   运行结果:
……



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

    示例:
  1. class Ticket implements Runnable{
  2.        private static int num = 100;
  3.       Object obj = new Object();
  4.        boolean flag = true;

  5.        public void run(){
  6.              if(flag ){
  7.                    while(true ){
  8.                          synchronized(Ticket.class){//this.getClass()
  9.                                if(num > 0){
  10.                                      try{
  11.                                           Thread. sleep(10);
  12.                                      } catch(InterruptedException e){
  13.                                           e.printStackTrace();
  14.                                      }
  15.                                      System.out.println(Thread.currentThread().getName() + "...obj..." + num--);
  16.                               }
  17.                         }
  18.                   }
  19.             } else
  20.                    while(true )
  21.                          show();
  22.       }

  23.       public static synchronized void show(){
  24.              if(num > 0){
  25.                    try{
  26.                         Thread. sleep(10);
  27.                   } catch(InterruptedException e){
  28.                         e.printStackTrace();
  29.                   }
  30.                   System.out.println(Thread.currentThread().getName() + "...function..." + num--);
  31.             }
  32.       }
  33. }

  34. class SynFunctionLockDemo{
  35.        public static void main(String[] args){
  36.             Ticket t = new Ticket();
  37.             Thread t1 = new Thread(t);
  38.             Thread t2 = new Thread(t);
  39.             
  40.             t1.start();
  41.              try{
  42.                   Thread. sleep(10);
  43.             } catch(InterruptedException e){
  44.                   e.printStackTrace();
  45.             }
  46.             t. flag = false ;
  47.             t2.start();
  48.       }
  49. }
复制代码
   运行结果:

……


    5.2.3 多线程下的单例模式

    饿汉式:
  1. class Single{
  2.        private static final Single s = new Single();
  3.        private Single(){}
  4.        public static Single getInstance(){
  5.              return s ;
  6.       }
  7. }
复制代码
    P.S.
    饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。

    懒汉式:
  1. class Single{
  2.       private static Single s = null;
  3.        private Single(){}
  4.        public static Single getInstance(){
  5.              if(s ==null){
  6.                    synchronized(Single.class){
  7.                          if(s == null)
  8.                                s = new Single();
  9.                   }
  10.             }
  11.             return s ;
  12.       }
  13. }
复制代码

    P.S.
    懒汉式存在安全问题,可以使用同步函数解决。
    但若直接使用同步函数,则效率较低,因为每次都需要判断。

    但若采取如下方式,即可提升效率。



    原因在于任何一个线程在执行到第一个if判断语句时,如果Single对象已经创建,则直接获取即可,而不用判断是否能够获取锁,相对于上面使用同步函数的方法就提升了效率。如果当前线程发现Single对象尚未创建,则再判断是否能够获取锁。
    1. 如果能够获取锁,那么就通过第二个if判断语句判断是否需要创建Single对象。因为可能当此线程获取到锁之前,已经有一个线程创建完Single对象,并且放弃了锁。此时它便没有必要再去创建,可以直接跳出同步代码块,放弃锁,获取Single对象即可。如果有必要,则再创建。
    2. 如果不能获取到锁,则等待,直至能够获取到锁为止,再按步骤一执行。

    5.2.4 死锁示例

    死锁常见情景之一:同步的嵌套。

    示例1:
  1. class Ticket implements Runnable{
  2.        private static int num = 100;
  3.        Object obj = new Object();
  4.        boolean flag = true;

  5.        public void run(){
  6.              if(flag ){
  7.                    while(true ){
  8.                          synchronized(obj ){
  9.                               show();
  10.                         }
  11.                   }
  12.             } else
  13.                    while(true )
  14.                         show();
  15.       }

  16.        public synchronized void show(){
  17.              synchronized(obj ){
  18.                    if(num > 0){
  19.                          try{
  20.                               Thread. sleep(10);
  21.                         } catch(InterruptedException e){
  22.                               e.printStackTrace();
  23.                         }
  24.                         System.out.println(Thread.currentThread().getName() + "...function..." + num--);
  25.                   }
  26.             }
  27.       }
  28. }

  29. class DeadLockDemo{
  30.        public static void main(String[] args){
  31.             Ticket t = new Ticket();
  32.             Thread t1 = new Thread(t);
  33.             Thread t2 = new Thread(t);
  34.             
  35.             t1.start();
  36.              try{
  37.                   Thread. sleep(10);
  38.             } catch(InterruptedException e){
  39.                   e.printStackTrace();
  40.             }
  41.             t. flag = false ;
  42.             t2.start();
  43.       }
  44. }
复制代码
   运行结果:


    原因分析:
    由上图可以看到程序已经被锁死,无法向下执行。
    由下图代码可以看到,run方法中的同步代码块需要获取obj对象锁,才能执行代码块中的show方法。
    而执行show方法则必须获取this对象锁,然后才能执行其中的同步代码块。
    当线程t1获取到obj对象锁执行同步代码块,线程t2获取到this对象锁执行show方法。同步代码块中的show方法因无法获取到this对象锁无法执行,show方法中的同步代码块因无法获取到obj对象锁无法执行,就会产生死锁。



    示例2:
  1. class Test implements Runnable{
  2.        private boolean flag ;
  3.       Test( boolean flag){
  4.              this.flag = flag;
  5.       }

  6.        public void run(){
  7.              if(flag ){
  8.                    while(true )
  9.                          synchronized(MyLock.locka){
  10.                               System.out.println(Thread.currentThread().getName() + "...if locka...");
  11.                                synchronized(MyLock.lockb){
  12.                                     System.out.println(Thread.currentThread().getName() + "...if lockb...");
  13.                               }
  14.                         }
  15.             } else{
  16.                    while(true )
  17.                          synchronized(MyLock.lockb){
  18.                               System.out.println(Thread.currentThread().getName() + "...else lockb...");
  19.                                synchronized(MyLock.locka){
  20.                                     System.out.println(Thread.currentThread().getName() + "...else locka...");
  21.                         }
  22.                   }
  23.             }
  24.       }
  25. }

  26. class MyLock{
  27.        public static final Object locka = new Object();
  28.        public static final Object lockb = new Object();
  29. }

  30. class DeadLockDemo{
  31.        public static void main(String[] args){
  32.             Test a = new Test(true );
  33.             Test b = new Test(false );

  34.             Thread t1 = new Thread(a);
  35.             Thread t2 = new Thread(b);

  36.             t1.start();
  37.             t2.start();
  38.       }
  39. }
复制代码
   运行结果:


~END~



~爱上海,爱黑马~




57 个回复

倒序浏览
哈哈,笑纳了。。
回复 使用道具 举报
好详细.
回复 使用道具 举报
雪域星辰 来自手机 中级黑马 2015-6-6 21:35:10
板凳
大神总结笔记,顶一个。。。
回复 使用道具 举报
总结得很给力,每次看了都会有不同的收获
回复 使用道具 举报
王老师加油!!有你的帖子,比翻书还方便。。。
回复 使用道具 举报
辛苦啦!
回复 使用道具 举报
反复回头总结是挺有用的。
回复 使用道具 举报
提前预习,看看。
回复 使用道具 举报
马上要学到这了,提前预习了
回复 使用道具 举报
多线程貌似面试经常问啊
回复 使用道具 举报
精彩,清晰!
回复 使用道具 举报
正能量   加油
回复 使用道具 举报
小手一抖,笔记带走!
回复 使用道具 举报
赞赞赞!
回复 使用道具 举报
总结得很给力,每次看了都会有不同的收获
回复 使用道具 举报
受益了:):)
回复 使用道具 举报
继续学习,,,加油!!!
回复 使用道具 举报
不错,顶一个
回复 使用道具 举报
在后几个程序代码中,while(true)  这个是什么意思啊   我知道明面的意思就是如果为真  就执行,可是这个怎么判断的啊?
回复 使用道具 举报
123下一页
您需要登录后才可以回帖 登录 | 加入黑马