zoukankan      html  css  js  c++  java
  • java线程小结3

    1. 多线程概述

      要实现多线程可以通过继承Thread和实现Runnable接口。不过这两者之间存在一些区别。其中最重要的区别就是,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便地实现资源的共享。其实细说起来并不是能不能资源共享的事情,是因为继承Thread和实现Runnable接口这两种方式新建的任务数本身就是不同的,线程与任务的对应机制也是不同的。

    范例1:继承Thread类

    创建了多个thread,相当于创建了多个任务,每个任务交由一个thread来完成。

     1 package test;
     2 
     3 public class MyThreadDemo1 {
     4     public static void main(String args[]) {
     5         MyThread1 mt1 = new MyThread1();
     6         MyThread1 mt2 = new MyThread1();
     7         MyThread1 mt3 = new MyThread1();
     8         mt1.start();
     9         mt2.start();
    10         mt3.start();
    11     }
    12 
    13     static class MyThread1 extends Thread {
    14         private int ticket = 5;
    15 
    16         @Override
    17         public void run() {
    18             // TODO Auto-generated method stub
    19             for (int i = 0; i < 100; i++)
    20                 if (ticket > 0)// 当余票大于0则买票
    21                 {
    22                     System.out.println("卖一张票:剩余ticket=" + --ticket); // 这里--ticket表示卖了一张票后的余票
    23                 }
    24         }
    25     }
    26 }

    程序运行结果:

    卖票:剩余ticket=5
    卖票:剩余ticket=4
    卖票:剩余ticket=3
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    卖票:剩余ticket=5
    卖票:剩余ticket=4
    卖票:剩余ticket=3
    卖票:剩余ticket=5
    卖票:剩余ticket=4
    卖票:剩余ticket=3
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    View Code

    以上程序通过继承Thread类实现多线程,程序中启动了三个线程,但是三个线程分别买了各自的5张票,并没有达到资源(Ticket)共享的目的。

    范例2:实现Runable接口

    相当于只创建了一个任务,并把这个任务交给三个线程来完成。

     1 package test;
     2 
     3 public class MyRunableThreadDemo1 {
     4     public static void main(String args[]) {
     5         MyRunableThread1 mrt = new MyRunableThread1();
     6         Thread t1 = new Thread(mrt);
     7         Thread t2 = new Thread(mrt);
     8         Thread t3 = new Thread(mrt);
     9         t1.start();
    10         t2.start();
    11         t3.start();
    12     }
    13 
    14     static class MyRunableThread1 implements Runnable {
    15         private int ticket = 5;
    16 
    17         @Override
    18         public void run() {
    19             // TODO Auto-generated method stub
    20             for (int i = 0; i < 100; i++)
    21                 if (ticket > 0)// 当余票大于0则买票
    22                 {
    23                     System.out.println("卖一张票:剩余ticket=" + --ticket); // 这里--ticket表示卖了一张票后的余票
    24                 }
    25         }
    26     }
    27 }

    程序运行结果:

    卖票:剩余ticket=5
    卖票:剩余ticket=4
    卖票:剩余ticket=3
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    View Code

    从程序的运行结果中可以清楚地发现,虽然启动了3个线程, 但是三个线程一共才卖出去5张票,即ticket属性是被所有线程所共享的。

    可见,实现Runnable接口相对于继承Thrad类来说,有如下显著优势:
    1.适合多个相同程序代码的线程去处理同一资源的情况。
    2.可以避免由于java单继承特性带来的局限
    3.增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的。

    2. 多线程的同步

    多次运行范例2我们发现得到的结果可能都不相同。

    范例2可能的输出结果1

    卖票:剩余ticket=4
    卖票:剩余ticket=5
    卖票:剩余ticket=2
    卖票:剩余ticket=3
    卖票:剩余ticket=1
    View Code

    范例2可能的输出结果2

    卖票:剩余ticket=4
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    卖票:剩余ticket=5
    卖票:剩余ticket=1
    View Code

    范例2可能的输出结果3

    卖票:剩余ticket=4
    卖票:剩余ticket=5
    卖票:剩余ticket=3
    卖票:剩余ticket=2
    卖票:剩余ticket=1
    卖票:剩余ticket=0
    卖票:剩余ticket=-1
    View Code

    出现票数为负的情况是因为:

    线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断,这样当线程1 ticket--之后ticket==0了,线程2再次执行ticket--那么ticket==-1。相当于多执行了一次 ticket--

    3.两种线程同步方法

    为了解决范例2中出现的问题,我们通过引入同步机制来解决问题。同步又分为同步代码块和同步方法两种类型。

     1 package test;
     2 
     3 public class MyRunableThreadDemo3 {
     4     public static void main(String args[]) {
     5         MyRunableThread1 mrt = new MyRunableThread1();
     6         Thread t1 = new Thread(mrt, "t1");
     7         Thread t2 = new Thread(mrt, "t2");
     8         Thread t3 = new Thread(mrt, "t3");
     9         t1.start();
    10         t2.start();
    11         t3.start();
    12     }
    13 
    14     static class MyRunableThread1 implements Runnable {
    15         private int ticket = 200;
    16 
    17         @Override
    18         public void run() {
    19 
    20             //错误
    21 //            synchronized (this) {
    22 //                while (ticket > 0) {
    23 //                    
    24 ////                    try {
    25 ////                        Thread.sleep(3);
    26 ////                    } catch (InterruptedException e) {
    27 ////                        e.printStackTrace();
    28 ////                    }
    29 //                    System.out.println(Thread.currentThread().getName()
    30 //                            + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
    31 //                }
    32 //            }
    33             /**
    34              * 同步代码块 之前说到ticket出现负数的原因是线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断, 
    35              * 这样当线程1执行 ticket--之后ticket==0了,线程2再去执行ticket--,那么ticket==-1。相当于多执行了一次ticket--,
    36              * 因此我们将synchronized(this)放在了if(ticket>0)之前。for(int i=0;i<100;i++)用来表示连续执行100次。这里synchronized在
    37              * for循环后面,因此每个线程都执行100次,彼此都有可能锁冲突。
    38              * */
    39              for(int i=0;i<100;i++)
    40                     synchronized(this)
    41                     {
    42                         if(ticket>0)
    43                         {
    44                             try{
    45                                 Thread.sleep(30);
    46                             }catch(InterruptedException e)
    47                             {
    48                                 e.printStackTrace();
    49                             }
    50                             System.out.println(Thread.currentThread().getName()
    51                                     + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
    52                         }
    53                     }    
    54         }
    55     }
    56 }
    View Code

    之前说到ticket出现负数的原因是“线程1在执行 --ticket之前,线程2 进入了 if (ticket > 0) 这个判断, 这样当线程1执行 ticket--之后ticket==0了,线程2再去执行ticket--,那么ticket==-1,相当于多执行了一次ticket--”,

    因此我们将synchronized(this)放在了if(ticket>0)之前。我们还可以发现外部有一个执行一般次的for循环for(int i=0;i<100;i++),这用来表示run方法中的这synchronized代码块会被执行100次。需要注意的是只有执行完synchronized代码块才会释放锁。因此每一个线程都有100次可能出现锁冲突,一个线程需要等待另外一个线程执行完synchronized代码块中的内容以后才可以访问这个代码块。

    范例4:同步方法

     1 package test;
     2 
     3 public class MyRunableThreadDemo4 {
     4     public static void main(String args[]) {
     5         MyRunableThread1 mrt = new MyRunableThread1();
     6         Thread t1 = new Thread(mrt, "t1");
     7         Thread t2 = new Thread(mrt, "t2");
     8         Thread t3 = new Thread(mrt, "t3");
     9         t1.start();
    10         t2.start();
    11         t3.start();
    12     }
    13 
    14     static class MyRunableThread1 implements Runnable {
    15         private int ticket = 200;
    16 
    17         @Override
    18         public void run() {
    19             for (int i = 0; i < 100; i++) {
    20                 sale();
    21             }
    22         }
    23         
    24         public synchronized void sale() {
    25             if (ticket > 0) {
    26                 try {
    27                     Thread.sleep(30);
    28                 } catch (InterruptedException e) {
    29                     e.printStackTrace();
    30                 }
    31                 System.out.println(Thread.currentThread().getName()
    32                         + "卖了一张票票,剩余ticket=" + --ticket);// 这里--ticket表示卖了一张票后的余票
    33             }
    34         }
    35     }
    36 }
    View Code

    这里将操作ticket资源的内容单独抽取出来作为一个方法来调用,然后同步该方法,保证同一时间只有一个进程调用该方法。

    4.生产者消费者案例

     1 package test;
     2 
     3 
     4 
     5 public class ThreadDeadLock {
     6     public static void main(String args[]) {
     7         Info info = new Info();
     8         // info作为参数传入两个线程当中
     9         ProducerThread pt = new ProducerThread(info);
    10         ConsumerThread ct = new ConsumerThread(info);
    11 
    12         Thread producer = new Thread(pt, "producer");
    13         Thread consumer = new Thread(ct, "consumer");
    14         producer.start();
    15         consumer.start();
    16     }
    17     
    18     //资源类
    19     static class Info {
    20         private String name = "name";
    21         private String content = "content";
    22 
    23         public String getName() {
    24             return name;
    25         }
    26 
    27         public void setName(String name) {
    28             this.name = name;
    29         }
    30 
    31         public String getContent() {
    32             return content;
    33         }
    34 
    35         public void setContent(String content) {
    36             this.content = content;
    37         }
    38     }
    39 
    40     // 生产者线程
    41     static class ProducerThread implements Runnable {
    42         private Info info = null;
    43 
    44         // 构造函数,其参数是资源
    45         public ProducerThread(Info info) {
    46             this.info = info;
    47         }
    48 
    49         @Override
    50         public void run() {
    51             // boolean flag=false;
    52             for (int i = 0; i < 10; i++) {
    53                 this.info.setName("name" + i);
    54                 try {
    55                     Thread.sleep(90);
    56                 } catch (InterruptedException e) {
    57                     // TODO Auto-generated catch block
    58                     e.printStackTrace();
    59                 }
    60                 this.info.setContent("content" + i);
    61             }
    62         }
    63     }
    64 
    65     static class ConsumerThread implements Runnable {
    66         private Info info = null;
    67 
    68         // 构造函数,其参数是资源
    69         public ConsumerThread(Info info) {
    70             this.info = info;
    71         }
    72 
    73         @Override
    74         public void run() {
    75             for (int i = 0; i < 10; i++) {
    76                 try {
    77                     Thread.sleep(100);
    78                 } catch (InterruptedException e) {
    79                     // TODO Auto-generated catch block
    80                     e.printStackTrace();
    81                 }
    82                 System.out.println(this.info.getName() + ":-->"
    83                         + this.info.getContent());
    84             }
    85         }
    86     }
    87 
    88 }
    View Code

    程序输出:

    name1:-->content0
    name2:-->content1
    name3:-->content2
    name4:-->content3
    name5:-->content4
    name6:-->content5
    name7:-->content6
    name8:-->content7
    name9:-->content8
    name9:-->content9
    View Code

    范例5存在两个问题:
    1.问题1:假设ProducerThread线程刚设置了name信息,还没有设置content信息,此时程序就切换到了ConsumerThread线程,那么ConsumerThread线程获取的是当前name与之前的content,所以输出结果会出现(比如:name1:-->content0)。这是因为name与content没有一起设置的原因,或者是说name与content信息不同步。
    2.问题2:生产者放了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已去过的数据。(比如出现name1:-->content0 name1:-->content0,这个可以通过调节sleep来控制)

    问题1 解决:加入同步

    如果要为操作加入同步,可以通过定义同步方法的方式完成,即将设置名称和内容定义在一个方法里面,代码如范例6所示。

    范例6

      1 package test;
      2 
      3 public class ThreadDeadLock2 {
      4     public static void main(String args[]) {
      5         Info info = new Info();
      6         // info作为参数传入两个线程当中
      7         ProducerThread pt = new ProducerThread(info);
      8         ConsumerThread ct = new ConsumerThread(info);
      9 
     10         Thread producer = new Thread(pt, "producer");
     11         Thread consumer = new Thread(ct, "consumer");
     12         producer.start();
     13         consumer.start();
     14     }
     15 
     16     // 资源类
     17     static class Info {
     18         private String name;
     19         private String content;
     20 
     21         //getter and setter
     22         public String getName() {
     23             return name;
     24         }
     25         public void setName(String name) {
     26             this.name = name;
     27         }
     28         public String getContent() {
     29             return content;
     30         }
     31         public void setContent(String content) {
     32             this.content = content;
     33         }
     34 
     35         // 获取name与content信息
     36         public synchronized void get() {
     37 
     38             try {
     39                 Thread.sleep(300);
     40             } catch (InterruptedException e) {
     41                 // TODO Auto-generated catch block
     42                 e.printStackTrace();
     43             }
     44             System.out.println(this.getName() + ":-->" + this.getContent());
     45         }
     46 
     47         // 设置name与content信息
     48         public synchronized void set(String name, String content) {
     49 
     50             this.setName(name);
     51             try {
     52                 Thread.sleep(300);
     53             } catch (InterruptedException e) {
     54                 // TODO Auto-generated catch block
     55                 e.printStackTrace();
     56             }
     57             this.setContent(content);
     58         }
     59     }
     60 
     61     // 生产者线程
     62     static class ProducerThread implements Runnable {
     63         private Info info = null;
     64 
     65         // 构造函数,其参数是资源
     66         public ProducerThread(Info info) {
     67             this.info = info;
     68         }
     69 
     70         @Override
     71         public void run() {
     72 
     73             for (int i = 0; i < 10; i++) {
     74                 this.info.set("name" + i, "content" + i);
     75             }
     76         }
     77     }
     78 
     79     static class ConsumerThread implements Runnable {
     80         private Info info = null;
     81 
     82         // 构造函数,其参数是资源
     83         public ConsumerThread(Info info) {
     84             this.info = info;
     85         }
     86 
     87         @Override
     88         public void run() {
     89             for (int i = 0; i < 10; i++) {
     90                 try {
     91                     Thread.sleep(100);
     92                 } catch (InterruptedException e) {
     93                     // TODO Auto-generated catch block
     94                     e.printStackTrace();
     95                 }
     96                 this.info.get();
     97             }
     98         }
     99     }
    100 }
    View Code

    程序运行结果:

    name-0:-->content-0
    name+1:-->content+1
    name-2:-->content-2
    name+3:-->content+3
    name-4:-->content-4
    name-6:-->content-6
    name+7:-->content+7
    name-8:-->content-8
    name+9:-->content+9
    name+9:-->content+9
    View Code

    从程序的运行结果中可以发现,问题1:信息错乱的问题已经解决,但是依然存在问题2:重复读取的问题,以及漏读信息的问题(比如上述输出中name9:-->content9重复读取,而name5:-->content5被漏读了)。既然有重复读取,则肯定会有重复设置的问题,那么对于这样的问题,该如何解决呢?此时,就需要使用Object类。Object类是所有类的父类,在此类中wait、notify是对线程操作有所支持的。

    如果想让生产者不重复生产,消费者不重复消费,可以设置有一个标志位,假设标志位为boolean型变量,如果标志位内容为true,则表示可以生产,但是不能取走,如果标志位内容为false,则表示可以取走,不能生产。操作流程如下:

    问题解决2——加入等待与唤醒

      1 package edu.sjtu.erplab.thread;
      2 
      3 class Info{
      4     private String name="name";
      5     private String content="content";
      6     private boolean flag=true;
      7     public  synchronized void set(String name,String content)
      8     {
      9         if(!flag)//标志位为false,不可以生产
     10         {
     11             try {
     12                 super.wait();
     13             } catch (InterruptedException e) {
     14                 // TODO Auto-generated catch block
     15                 e.printStackTrace();
     16             }
     17         }
     18         this.setName(name);
     19         try {
     20             Thread.sleep(30);
     21         } catch (InterruptedException e) {
     22             // TODO Auto-generated catch block
     23             e.printStackTrace();
     24         }
     25         this.setContent(content);
     26         flag=false;//修改标志位为false,表示生产者已经完成资源,消费者可以消费。
     27         super.notify();//唤醒消费者进程
     28     }
     29     
     30     public synchronized void get()
     31     {
     32         if(flag)
     33         {
     34             try {
     35                 super.wait();
     36             } catch (InterruptedException e) {
     37                 // TODO Auto-generated catch block
     38                 e.printStackTrace();
     39             }
     40         }
     41         try {
     42             Thread.sleep(30);
     43         } catch (InterruptedException e) {
     44             // TODO Auto-generated catch block
     45             e.printStackTrace();
     46         }
     47         System.out.println(this.getName()+":-->"+this.getContent());
     48         flag=true;//修改标志位为true,表示消费者拿走资源,生产者可以生产。
     49         super.notify();//唤醒生产者进程。
     50     }
     51     
     52     
     53     public String getName() {
     54         return name;
     55     }
     56     public void setName(String name) {
     57         this.name = name;
     58     }
     59     public String getContent() {
     60         return content;
     61     }
     62     public void setContent(String content) {
     63         this.content = content;
     64     }
     65     
     66 }
     67 
     68 class Producer implements Runnable{
     69     private Info info=null;
     70     public Producer(Info info)
     71     {
     72         this.info=info;
     73     }
     74     
     75 
     76     @Override
     77     public void run() {
     78         boolean flag=false;
     79         for(int i=0;i<10;i++)
     80             if(flag)
     81             {
     82                 this.info.set("name+"+i, "content+"+i);
     83                 flag=false;
     84             }
     85             else
     86             {
     87                 this.info.set("name-"+i, "content-"+i);
     88                 flag=true;
     89             }
     90     }
     91 }
     92 
     93 class Consumer implements Runnable{
     94     private Info info=null;
     95     public Consumer(Info info)
     96     {
     97         this.info=info;
     98     }
     99     @Override
    100     public void run() {
    101         for(int i=0;i<10;i++)
    102         {
    103             try {
    104                 Thread.sleep(10);
    105             } catch (InterruptedException e) {
    106                 // TODO Auto-generated catch block
    107                 e.printStackTrace();
    108             }
    109             this.info.get();
    110         }
    111         
    112     }
    113 }
    114 
    115 public class ThreadDeadLock {
    116     public static void main(String args[])
    117     {
    118         Info info=new Info();
    119         Producer pro=new Producer(info);
    120         Consumer con=new Consumer(info);
    121         new Thread(pro).start();
    122         new Thread(con).start();
    123     }
    124     
    125 }
    View Code

    程序运行结果:

    name-0:-->content-0
    name+1:-->content+1
    name-2:-->content-2
    name+3:-->content+3
    name-4:-->content-4
    name+5:-->content+5
    name-6:-->content-6
    name+7:-->content+7
    name-8:-->content-8
    name+9:-->content+9
    View Code

    参考:http://www.cnblogs.com/xwdreamer/archive/2011/11/20/2296931.html

  • 相关阅读:
    【面试】Java基础
    GC
    【面试】JVM
    Spooling技术
    数据结构之 平衡二叉树
    二叉树应用
    LINUX设备驱动模型之class
    RTC(x86)
    微内核和单内核
    Linux内核的五大模块
  • 原文地址:https://www.cnblogs.com/Eason-S/p/5895076.html
Copyright © 2011-2022 走看看