下面从线程安全概念、什么情况下会产生多线程安全问题、多线程问题的原因、多线程问题的解决方法、java对多线程的支持、例子等方面来总结多线程。
1、线程安全概念
线程运行过程中,对共享对象的访问和预期或理论上的值有差别,这时就产生了线程安全问题,下面的例子就是三个线程对变量num产生了并发访问。
package com.yangjianzhou.multiThread.test; public class ThreadUnsafeTest { public static int num = 0; public static void main(String[] args) { Thread t1 = new Thread(new SubRunnable()); Thread t2 = new Thread(new SubRunnable()); t1.start(); t2.start(); System.out.println(num); } static class SubRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { num++; } } } }
输出结果:有时为0,有时为10,有时为20.
输出结果的期望值是10,这时就产生了多线程的安全问题,这里有三个线程:t1、t2、main线程同时对num进行访问(读取和设值),这个变量num在这个多线程环境下就是不安全的。
2.什么情况下会产生多线程安全问题
上面我们已经通过一个例子展示了多线程的安全问题了,那么什么情况下产生多线程的安全问题呢?
多个线程对同一个对象的并发访问(读取和设值),这时就有可能产生多线程安全问题。可能是一个线程在读取共享变量值的时候,另外一个线程已经改变了这个变量的值了,这时候读取的值就不是预期的值了,这就产生了多线程安全问题了。
3.多线程问题的原因
多线程产生的原因看似很简单,但是要弄懂还得从线程执行原理上分析。
上图中展示了两个线程对一个共享变量的存储访问。线程在执行时,首先会将主内存区的共享变量拷贝一个副本到自己的工作线程区,执行的过程中,只会对自己工作线程区中的副本进行操作,什么时候讲共享变量的副本同步到主内存区是不确定的,拿1中的例子来说吧,线程t1执行完后(循环执行完)把num副本同步到主内存,在这之前,其他线程(t2和main线程都没同步主内存num的值),这时,主内存的值就是10,由于线程执行的不确定性,假设线程t1和t2开始时都把num的值读取到自己的工作区,然后t1执行,直到结束,把工作内存的num值同步到主内存,这时线程t2的工作区num副本的值为0,然后t2执行,直到完成,然后同步主内存num的值,这时num的值被覆盖为10,而不是期望的20.这就是多线程并发存储对象产生线程安全问题的实质。
4.多线程问题的解决方法
多线程安全问题产生了,就需要找到解决方案,我们已经知道了多线程安全问题的实质,线程读取了共享变量错误的值,或者线程设置了共享变量的值而没有及时写入主内存。这里有对象锁、可重入锁、读写锁、信号量等措施来防止多线程安全问题。
5.java对多线程的支持
6.生产者消费者例子
队列大小为2,两个生产者,一个消费者,因此生产者一般都处于阻塞状态,队列一般都处于满的状态。
生产者: package com.yangjianzhou.multiThread.test; import java.util.concurrent.BlockingQueue; public class Producer implements Runnable { private BlockingQueue<String> queue; public Producer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println("Producer "+Thread.currentThread().getName()+" , queue.size = "+queue.size()); queue.put("break" + Thread.currentThread().getName() + i); } } catch (Exception e) { } } } 消费者: package com.yangjianzhou.multiThread.test; import java.util.concurrent.BlockingQueue; public class Customer implements Runnable { private BlockingQueue<String> queue; public Customer(BlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println("customer "+Thread.currentThread().getName()+" , queue.size = "+queue.size()); queue.take(); } } catch (Exception e) { } } } 测试程序: package com.yangjianzhou.multiThread.test; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class Test { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<String>(2); Thread customer = new Thread(new Customer(queue)); Thread producer1 = new Thread(new Producer(queue)); Thread producer2 = new Thread(new Producer(queue)); customer.start(); producer1.start(); producer2.start(); } } 运行结果: customer Thread-0 , queue.size = 0 Producer Thread-1 , queue.size = 0 Producer Thread-2 , queue.size = 1 Producer Thread-2 , queue.size = 2 Producer Thread-1 , queue.size = 2 Producer Thread-2 , queue.size = 2 customer Thread-0 , queue.size = 1 customer Thread-0 , queue.size = 2 Producer Thread-1 , queue.size = 2 Producer Thread-2 , queue.size = 2 customer Thread-0 , queue.size = 1 Producer Thread-1 , queue.size = 2 customer Thread-0 , queue.size = 2 customer Thread-0 , queue.size = 2 Producer Thread-2 , queue.size = 2 customer Thread-0 , queue.size = 2 Producer Thread-1 , queue.size = 2 customer Thread-0 , queue.size = 1 Producer Thread-2 , queue.size = 2 Producer Thread-1 , queue.size = 2 customer Thread-0 , queue.size = 2 customer Thread-0 , queue.size = 1 Producer Thread-2 , queue.size = 2 Producer Thread-1 , queue.size = 2