Java多线程

最近一段时间在研究java的多线程,在此记录下自己的学习成果,学习过程中参考了大量的博客,

觉得挺好的,都自己做了实验验证了一下,确实挺有意思的,这里感谢他们的研究成果。

从最基本的做起:

1.继承Thread实现多线程

Java代码  收藏代码
  1. /** 
  2.  *   实际上start()方法是创建一个新的线程,而run()只是调用一个单纯的run()方法 
  3.  *    如果要在一个实例上产生多个线程就必须用到了另外一种实现方法:实现Runnable接口 
  4.  *    因为不能tt.start()两次 
  5.  * @author Sa 
  6.  * 
  7.  */  
  8. public class TestThread extends Thread {  
  9.     private int x = 0;  
  10.   
  11.     public void run() {  
  12.   
  13.         for (int i = 0; i < 10; i++) {  
  14.             try {  
  15.                 Thread.sleep(100);  
  16.   
  17.             } catch (Exception e) {  
  18.   
  19.                 e.printStackTrace();  
  20.             }  
  21.             System.out.println(x++);  
  22.         }  
  23.     }  
  24.       
  25.     public static void main(String[] args) {  
  26.          TestThread tt = new TestThread();  
  27.           tt.start();  
  28. //        tt.start();  
  29.           tt.run();  
  30.           
  31.   
  32.     }  
  33.   
  34. }  

这个程序有不足的地方,那就是不能产生多个线程多于类TestThread

为了避免这种情况我们看2:


2.实现Runnable接口实现多线程

如果要产生多个线程我们就引入Runnable接口,如下:

Java代码  收藏代码
  1. public class TestRunnable implements Runnable{  
  2.     private int x = 0;  
  3.     private static int demo = 0;  
  4.       
  5.     public   void run() {  
  6.   
  7.         for (int i = 0; i < 10; i++) {  
  8.             try {  
  9.                 Thread.sleep(100);  
  10.                   
  11.             } catch (Exception e) {  
  12.   
  13.                 e.printStackTrace();  
  14.             }  
  15.             System.out.println(x++);  
  16.       
  17.         }  
  18.           
  19.     }  
  20.       
  21.     public static void main(String[] args) {  
  22.          TestRunnable tt = new TestRunnable();  
  23.          Thread t = new Thread(tt);  
  24.          Thread t2 = new Thread(tt);  
  25.           t.start();  
  26.           t2.start();  
  27.             
  28.   
  29.     }  
  30.   
  31. }  

 
这样就可以克服1中的不足,可以产生对于TestRunnable的多线程

但是同样从结果看出,变量不能同步被操作的问题,出现数据的分差

为了解决这种问题引入同步的概念。如下3

 3.同步代码块和同步方法

用同步的方法封装上面2中的代码块,贴上代码:

Java代码  收藏代码
  1. public class TestSynchronized implements Runnable{  
  2.     private int x = 0;  
  3.     private static int demo = 0;  
  4.       
  5.     public synchronized  void run() {  
  6.   
  7.         for (int i = 0; i < 10; i++) {  
  8.             try {  
  9.                 Thread.sleep(100);  
  10.                   
  11.             } catch (Exception e) {  
  12.   
  13.                 e.printStackTrace();  
  14.             }  
  15.             System.out.println(x++);  
  16.             synchronized (this) {  
  17.                 System.out.println("p() is running...,demo = "+demo);  
  18.                 demo++;  
  19.             }  
  20.             synchronized (this) {  
  21.                 System.out.println("q() is running...,demo = "+demo);  
  22.                 demo--;  
  23.             }  
  24.               
  25. //          p();  
  26. //          q();  
  27.             System.out.println("2次运行结束。");  
  28.         }  
  29.           
  30.           
  31.     }  
  32.     public synchronized void p(){  
  33.         System.out.println("p() is running...,demo = "+demo);  
  34.         demo++;  
  35.     }  
  36.       
  37.     public synchronized void q(){  
  38.         System.out.println("q() is running...,demo = "+demo);  
  39.         demo--;  
  40.     }  
  41.       
  42.   
  43.       
  44.     public static void main(String[] args) {  
  45.          TestSynchronized tt = new TestSynchronized();  
  46.          Thread t = new Thread(tt);  
  47.          Thread t2 = new Thread(tt);  
  48.           t.start();  
  49.           t2.start();  
  50.             
  51.   
  52.     }  
  53.   
  54. }  

注释掉的部分是同步方法,用synchronized( ){  }包住的部分称为同步块,效果是是一样的,我想大家都知道。

开始的时候我也不明白synchronized( 参数 )里面的 参数是什么意思,现在说明一下:

参数 的类型是对象,说明同步的时候,这个对象不允许被别的线程使用,当然也不能有多个线程同时执行这块代码(称为同步代码块),对于synchronized关键字,我想说三点:

 一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,
       一个时间内只能有一个线程得到执行。
       另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,
      另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。 
 

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,
      其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

    

这也是网上常说的三点。

4.线程的等待和唤醒

      线程的等待使用wait()方法,必须在synchronized(){  }块里面使用wait(),否则无效。

      用notify()或notifyAll()来唤醒等待的线程。详细的注意事项我会一会写出来,先来看一下代码

Java代码  收藏代码
  1. public class ThreadA {  
  2.     public static void main(String args[]) {  
  3.         ThreadB b = new ThreadB();  
  4.         b.start();//b并不是一个线程,b调用start()方法产生一个线程  
  5.       
  6.         synchronized (b) {//意思是获取对象b的资源锁,在使用的时候其他线程不允许操作b这个对象  
  7.             System.out.println("Waiting for b to complete...");  
  8.             try {  
  9.                 Thread.sleep(3000);  
  10.                 b.wait();//将当前的资源锁暂时给别的线程  
  11.                 //可以通过两种方法返回资源锁  
  12.                 //1.设置借出时间,到时间自动得到资源锁  
  13.                 //2.让其他线程自动还回来,通过在其他的代码块里面写notify(),or notifyAll();  
  14.             } catch (InterruptedException e) {  
  15.             }  
  16.             System.out.println("Completed.Now back to main thread");  
  17.         }  
  18.           
  19.         System.out.println("....Total is:" + b.total);  
  20.     }  
  21.   
  22. }  
  23. /** 
  24.  * 这里有一个问题:注释掉notify();  主方法的 线程仍然能够被唤醒 
  25.  * 只能猜想:run()方法调用结束后会去检测有没有其他的线程需要调用,如果没有则把资源锁还给原来的主线程,主线程继续执行。 
  26.  * 具体这个猜想对不对,可以多做几个线程来测试一下。 
  27.  * @author Sa 
  28.  * 
  29.  */  
  30. class ThreadB extends Thread {  
  31.     int total = 0;  
  32.   
  33.     public void run() {  
  34.         synchronized (this) {//this代表当前对象  
  35.             System.out.println("ThreadB is running...");  
  36.             for (int i = 0; i < 100; i++) {  
  37.                 total += i;  
  38.             }  
  39.             System.out.println("total is " + total);  
  40.             // notify();     
  41.             //唤醒正在等待的线程,并不是唤醒了等待的线程自己就会立即结束,  
  42.             //而是唤醒之后把自己的任务执行完,被唤醒的线程才会去执行自己的任务.  
  43.         }  
  44.     }  
  45. }  

这个程序无意在网上看见的,开始不明白作者的意思,理解了一下,大彻大悟,每个语句我都做了注释,我有一个问题:

也想问问大家:注释掉notify();  主方法的 线程仍然能够被唤醒,这是为什么?

(我的猜想:

 * 猜想: 如果锁的是thread对象,当wait()以后;
 * 只要在其他线程里面执行了锁定的这个对象的run方法,就会自动返回对象锁,
 * 要是锁的是一般的对象,必须notify(),才能唤醒。

我想如果大家明白我的意思的话,不妨问问自己。这个问题我看见网上有人在问,也没有问出个所以然来,再此和大家分享下。

5.死锁的模拟和解决

前段时间打算去面试,发现连一个死锁都不会写,实在是很无奈,因为网上一些视频老师说的都是多线程不重要,我是自学的所以也没注意多线程这一块,后来发现要写出死锁现象还必须知道点多线程的知识点,再次和大家分享一下:

Java代码  收藏代码
  1. public class DeadLockDemo implements Runnable{  
  2.       
  3.     private boolean flag = false;  
  4.       
  5.     static public Integer a = Integer.valueOf(1);  
  6.     static public Integer b = Integer.valueOf(2);  
  7.   
  8.     public static void main(String[] args) {  
  9.         DeadLockDemo d1 = new DeadLockDemo();  
  10.         DeadLockDemo d2 = new DeadLockDemo();  
  11.         Thread t1 = new Thread(d1,"线程1");  
  12.         Thread t2 = new Thread(d2,"线程2");  
  13.         d1.flag = true;  
  14.         d2.flag = false;  
  15.         t1.start();  
  16.         t2.start();  
  17.     }  
  18.       
  19.     public void run(){  
  20.         if(flag){  
  21.             System.out.println(Thread.currentThread().getName()+" is running");  
  22.             synchronized (a) {  
  23.                 try {  
  24.                     Thread.sleep(300);  
  25.                 } catch (InterruptedException e) {  
  26.                     // TODO Auto-generated catch block  
  27.                     e.printStackTrace();  
  28.                 }  
  29.                 synchronized (b) {  
  30.                     System.out.println("b = "+b);  
  31.                 }  
  32.             }  
  33.         }else{  
  34.             System.out.println(Thread.currentThread().getName()+" is running");  
  35.             synchronized (b) {  
  36.                 try {  
  37.                     Thread.sleep(300);  
  38.                 } catch (InterruptedException e) {  
  39.                     // TODO Auto-generated catch block  
  40.                     e.printStackTrace();  
  41.                 }  
  42.                 synchronized(a){  
  43.                     System.out.println("a = "+a);  
  44.                 }  
  45.             }  
  46.         }  
  47.     }  
  48.   
  49. }  

死锁自己能写一个这样的出来就差不多了,不必了解太深,对于面试已经足够

6.生产者和消费者经典问题

这个问题大一的时候就困扰我了,一直忙着找借口不去理解这东西,乘着东风之势,也理解了这个经典的问题,也算是给面试增加自信心吧,再次和大家分享一番,上代码:

Java代码  收藏代码
  1. public class ProducerConsumer {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         ProductStack ps = new ProductStack();  
  6.   
  7.         Producer p = new Producer(ps, "生产者1");  
  8.   
  9.         Consumer c = new Consumer(ps, "消费者1");  
  10.   
  11.         new Thread(p).start();  
  12.   
  13.         new Thread(c).start();  
  14.   
  15.     }  
  16.   
  17. }  
  18.   
  19. class Product {  
  20.   
  21.     int id;  
  22.     private String producedBy = "N/A";  
  23.   
  24.     private String consumedBy = "N/A";  
  25.   
  26.     // 构造函数,指明产品ID以及生产者名字。  
  27.   
  28.     Product(int id, String producedBy) {  
  29.   
  30.         this.id = id;  
  31.   
  32.         this.producedBy = producedBy;  
  33.   
  34.     }  
  35.   
  36.     // 消费,需要指明消费者名字  
  37.   
  38.     public void consume(String consumedBy) {  
  39.   
  40.         this.consumedBy = consumedBy;  
  41.   
  42.     }  
  43.   
  44.     public String toString() {  
  45.   
  46.         return "Product : " + id + ", produced by " + producedBy  
  47.   
  48.         + ", consumed by " + consumedBy;  
  49.   
  50.     }  
  51.   
  52.     public String getProducedBy() {  
  53.   
  54.         return producedBy;  
  55.   
  56.     }  
  57.   
  58.     public void setProducedBy(String producedBy) {  
  59.   
  60.         this.producedBy = producedBy;  
  61.   
  62.     }  
  63.   
  64.     public String getConsumedBy() {  
  65.   
  66.         return consumedBy;  
  67.   
  68.     }  
  69.   
  70.     public void setConsumedBy(String consumedBy) {  
  71.   
  72.         this.consumedBy = consumedBy;  
  73.   
  74.     }  
  75. }  
  76.   
  77. // 这个class就是仓库,是生产者跟消费者共同争夺控制权的同步资源  
  78.   
  79. class ProductStack {  
  80.   
  81.     int index = 0;  
  82.   
  83.     Product[] arrProduct = new Product[6];  
  84.   
  85.     // push使用来让生产者放置产品的  
  86.   
  87.     public synchronized void push(Product product) {  
  88.   
  89.         // 如果仓库满了  
  90.   
  91.         while (index == arrProduct.length) // 这里本来可以用if(),但是如果catch  
  92.   
  93.         // exception会出问题,让满的index越界  
  94.   
  95.         {  
  96.   
  97.             try {  
  98.   
  99.                 // here, "this" means the thread that is using "push"  
  100.   
  101.                 // so in this case it's a producer thread instance.  
  102.   
  103.                 // the BIG difference between sleep() and wait() is, once  
  104.   
  105.                 // wait(),  
  106.   
  107.                 // the thread won't have the lock anymore  
  108.   
  109.                 // so when a producer wait() here, it will lost the lock of  
  110.   
  111.                 // "push()"  
  112.   
  113.                 // While sleep() is still keeping this lock  
  114.   
  115.                 // Important: wait() and notify() should be in "synchronized"  
  116.   
  117.                 // block  
  118.   
  119.                 System.out.println(product.getProducedBy() + " is waiting.");  
  120.   
  121.                 // 等待,并且从这里退出push()  
  122.   
  123.                 wait();  
  124.   
  125.             } catch (InterruptedException e) {  
  126.   
  127.                 e.printStackTrace();  
  128.   
  129.             }  
  130.   
  131.         }  
  132.   
  133.         System.out.println(product.getProducedBy() + " sent a notifyAll().");  
  134.   
  135.         // 因为我们不确定有没有线程在wait(),所以我们既然生产了产品,就唤醒有可能等待的消费者,让他们醒来,准备消费  
  136.   
  137.         notifyAll();  
  138.   
  139.         // 注意,notifyAll()以后,并没有退出,而是继续执行直到完成。  
  140.   
  141.         arrProduct[index] = product;  
  142.   
  143.         index++;  
  144.   
  145.         System.out.println(product.getProducedBy() + " 生产了: " + product);  
  146.   
  147.     }  
  148.   
  149.     // pop用来让消费者取出产品的  
  150.   
  151.     public synchronized Product pop(String consumerName) {  
  152.   
  153.         // 如果仓库空了  
  154.   
  155.         while (index == 0) {  
  156.   
  157.             try {  
  158.   
  159.                 // here will be the consumer thread instance will be waiting ,  
  160.   
  161.                 // because empty  
  162.   
  163.                 System.out.println(consumerName + " is waiting.");  
  164.   
  165.                 // 等待,并且从这里退出pop()  
  166.   
  167.                 wait();  
  168.   
  169.             } catch (InterruptedException e) {  
  170.   
  171.                 e.printStackTrace();  
  172.   
  173.             }  
  174.   
  175.         }  
  176.   
  177.         System.out.println(consumerName + " sent a notifyAll().");  
  178.   
  179.         // 因为我们不确定有没有线程在wait(),所以我们既然消费了产品,就唤醒有可能等待的生产者,让他们醒来,准备生产  
  180.   
  181.         notifyAll();  
  182.   
  183.         // 注意,notifyAll()以后,并没有退出,而是继续执行直到完成。  
  184.   
  185.         // 取出产品  
  186.   
  187.         index--;  
  188.   
  189.         Product product = arrProduct[index];  
  190.   
  191.         product.consume(consumerName);//添加产品消费者的名字  
  192.   
  193.         System.out.println(product.getConsumedBy() + " 消费了: " + product);  
  194.   
  195.         return product;  
  196.   
  197.     }  
  198.   
  199. }  
  200.   
  201. class Producer implements Runnable {  
  202.   
  203.     String name;  
  204.     ProductStack ps = null;  
  205.   
  206.     Producer(ProductStack ps, String name) {  
  207.   
  208.         this.ps = ps;  
  209.   
  210.         this.name = name;  
  211.   
  212.     }  
  213.   
  214.     public void run() {  
  215.   
  216.         for (int i = 0; i < 20; i++) {  
  217.   
  218.             Product product = new Product(i, name);  
  219.   
  220.             ps.push(product);  
  221.   
  222.             try {  
  223.   
  224.                 Thread.sleep((int) (Math.random() * 200));  
  225.   
  226.             } catch (InterruptedException e) {  
  227.   
  228.                 e.printStackTrace();  
  229.   
  230.             }  
  231.   
  232.         }  
  233.   
  234.     }  
  235.   
  236. }  
  237.   
  238. class Consumer implements Runnable {  
  239.   
  240.     String name;  
  241.     ProductStack ps = null;  
  242.   
  243.     Consumer(ProductStack ps, String name) {  
  244.   
  245.         this.ps = ps;  
  246.   
  247.         this.name = name;  
  248.   
  249.     }  
  250.   
  251.     public void run() {  
  252.   
  253.         for (int i = 0; i < 20; i++) {  
  254.   
  255.             ps.pop(name);  
  256.   
  257.             try {  
  258.   
  259.                 Thread.sleep((int) (Math.random() * 1000));  
  260.   
  261.             } catch (InterruptedException e) {  
  262.   
  263.                 e.printStackTrace();  
  264.   
  265.             }  
  266.   
  267.         }  
  268.   
  269.     }  
  270.   
  271. }  

7.JDK新的线程库

jdk5以后提供了一个新的线程库

java.util.concurrent并发包

但是好像现在用的很少,可能是开发中多线程没有想象中用的那么多吧

8.用新的线程库模拟交通灯系统(传智博客版本共享)

这里有一份用新的并发包开发的一套小的模拟系统,我在网上看见的,特地和大家分享:

代码可以直接运行:

交通灯用枚举类型:

Java代码  收藏代码
  1. public enum Lamp {  
  2.       
  3.     S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false),  
  4.     N2S(null,null,false),N2E(null,null,false),W2E(null,null,false),W2N(null,null,false),  
  5.     S2E(null,null,true),E2N(null,null,true),N2W(null,null,true),W2S(null,null,true);  
  6.       
  7.     private String opposite;  
  8.     private String next;  
  9.     private boolean lighted;  
  10.       
  11.     private Lamp(String opposite,String next,boolean lighted){  
  12.         this.opposite = opposite;  
  13.         this.next = next;  
  14.         this.lighted = lighted;  
  15.     }  
  16.       
  17.     public boolean isLighted(){  
  18.         return lighted;  
  19.     }  
  20.       
  21.     public void light(){  
  22.         this.lighted = true;  
  23.         if(this.opposite!=null){  
  24.             Lamp.valueOf(opposite).light();  
  25.         }  
  26.         System.out.println(name() + " lamp is green,下面总共应该有6个方向能看到汽车穿过!");  
  27.     }  
  28.       
  29.     public Lamp black(){  
  30.         this.lighted = false;  
  31.         if(this.opposite!=null){  
  32.             Lamp.valueOf(opposite).black();  
  33.         }  
  34.         Lamp nextLamp = null;  
  35.         if(next!=null){  
  36.             nextLamp = Lamp.valueOf(next);  
  37.             System.out.println("绿灯从" + name() + "-------->切换为" + next);  
  38.             nextLamp.light();  
  39.         }  
  40.         return nextLamp;  
  41.     }  
  42. }  

公路类:

Java代码  收藏代码
  1. public class Road {  
  2.       
  3.     private String name;  
  4.     private List<String> vehicles = new ArrayList<String>();  
  5.       
  6.     public Road(String name){  
  7.         this.name = name;  
  8.           
  9.         //产生车辆用一个线程  
  10.         ExecutorService pool =  Executors.newSingleThreadExecutor();  
  11.           
  12.         pool.execute(new Runnable() {  
  13.               
  14.             @Override  
  15.             public void run() {  
  16.                 // TODO Auto-generated method stub  
  17.                 for(int i = 0 ; i < 1000 ; i++){  
  18.                     try {  
  19.                         Thread.sleep((new Random().nextInt(10) + 1)*1000);  
  20.                     } catch (InterruptedException e) {  
  21.                         // TODO Auto-generated catch block  
  22.                         e.printStackTrace();  
  23.                     }  
  24.                     //每隔1-10s随机产生一辆车  
  25.                     vehicles.add(Road.this.name+" : 第"+i+"辆车");  
  26.                     System.out.println(Road.this.name+" : 第"+i+"辆车出现");  
  27.                 }  
  28.             }  
  29.         });  
  30.           
  31.         //做一个定时器,在绿灯的情况下每隔1秒通车  
  32.         ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);  
  33.         timer.scheduleAtFixedRate(  
  34.                 new Runnable(){  
  35.                     public void run(){  
  36.                         if(vehicles.size()>0){  
  37.                         boolean lighted = Lamp.valueOf(Road.this.name).isLighted();  
  38.                         if(lighted){  
  39.                             System.out.println(vehicles.remove(0)+" is passing");  
  40.                         }  
  41.                         }  
  42.                     }  
  43.                 },  
  44.                 1,  
  45.                 1,  
  46.                 TimeUnit.SECONDS);  
  47.     }  
  48.       
  49. }  

灯的控制器:

Java代码  收藏代码
  1. import java.util.concurrent.Executors;  
  2. import java.util.concurrent.ScheduledExecutorService;  
  3. import java.util.concurrent.TimeUnit;  
  4.   
  5. public class LampController {  
  6.       
  7.     private Lamp currentLamp;  
  8.       
  9.     public LampController(){  
  10.         currentLamp = Lamp.S2N;  
  11.         currentLamp.light();  
  12.         //定时器,每隔10秒钟切换红绿灯  
  13.         ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);  
  14.         timer.scheduleAtFixedRate(  
  15.                 new Runnable() {  
  16.                     @Override  
  17.                     public void run() {  
  18.                         // TODO Auto-generated method stub  
  19.                         currentLamp = currentLamp.black();  
  20.                           
  21.                     }  
  22.                 },  
  23.                 10,  
  24.                 10,  
  25.                 TimeUnit.SECONDS);  
  26.     }  
  27. }  

主类(运行主方法即可)

Java代码  收藏代码
  1. public class MainClass {  
  2.       
  3.     public static void main(String[] args) {  
  4.           
  5.         String []directions = new String[]{"S2N","S2W","E2W","E2S",  
  6.                 "N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S"};  
  7.           
  8.         for(int i = 0;i<directions.length;i++){  
  9.             new Road(directions[i]);  
  10.         }  
  11.           
  12.         new LampController();  
  13.     }  
  14. }  

我大致解释一下:

          马路上穿梭的线路一共有12条,分别是:向左拐的四条,向右拐的有四条,直线四条;

          每条路上用线程随机产生车辆,向右拐的情况不受灯的控制,

          灯为true的时候,代表这个灯控制的两个方向可以通车;

          每隔十秒,会转到下一组灯亮,总共有三组灯,循环亮着,循环通车

高山仰止, 景行行止。 四牡鲱鲱, 六辔如琴。 觏尔新婚, 以慰我心。
原文地址:https://www.cnblogs.com/davidshi/p/3338226.html