对于普通的变量,在涉及多线程操做时,会遇到经典的线程安全问题。考虑以下代码:java
private static final int TEST_THREAD_COUNT = 100; private static int counter = 0; public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(TEST_THREAD_COUNT); Thread[] threads = new Thread[TEST_THREAD_COUNT]; for (int i = 0; i < TEST_THREAD_COUNT; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { ++counter; System.out.println("Thread " + Thread.currentThread().getId() + " / Counter : " + counter); latch.countDown(); } }); threads[i].start(); } try { latch.await(); System.out.println("Main Thread " + " / Counter : " + counter); } catch (InterruptedException e) { e.printStackTrace(); } }
屡次执行这段程序,咱们会发现最后counter
的值会出现98
,99
等值,而不是预想中的100
。程序员
... ... Thread 100 / Counter : 90 Thread 101 / Counter : 91 Thread 102 / Counter : 92 Thread 103 / Counter : 93 Thread 104 / Counter : 95 Thread 105 / Counter : 95 Thread 106 / Counter : 96 Thread 107 / Counter : 97 Thread 108 / Counter : 98 Thread 109 / Counter : 99 Main Thread / Counter : 99
这个问题发生的缘由是++counter
不是一个原子性操做。当要对一个变量进行计算的时候,CPU须要先从内存中将该变量的值读取到高速缓存中,再去计算,计算完毕后再将变量同步到主内存中。这在多线程环境中就会遇到问题,试想一下,线程A从主内存中复制了一个变量a
=3到工做内存,而且对变量a
进行了加一操做,a
变成了4,此时线程B也从主内存中复制该变量到它本身的工做内存,它获得的a的值仍是3,a
的值不一致了(这里工做内存就是高速缓存)。缓存
java有个sychronized
关键字,它能后保证同一个时刻只有一条线程可以执行被关键字修饰的代码,其余线程就会在队列中进行等待,等待这条线程执行完毕后,下一条线程才能对执行这段代码。
它的修饰对象有如下几种:安全
如今咱们开始使用咱们的新知识,调整以上代码,在run()
上添加sychronized
关键字。多线程
private static final int TEST_THREAD_COUNT = 100; private static int counter = 0; public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(TEST_THREAD_COUNT); Thread[] threads = new Thread[TEST_THREAD_COUNT]; for (int i = 0; i < TEST_THREAD_COUNT; i++) { threads[i] = new Thread(new Runnable() { @Override public synchronized void run() { ++counter; System.out.println("Thread " + Thread.currentThread().getId() + " / Counter : " + counter); latch.countDown(); } }); threads[i].start(); } try { latch.await(); System.out.println("Main Thread " + " / Counter : " + counter); } catch (InterruptedException e) { e.printStackTrace(); } }
屡次执行新代码,咱们依旧发现结果不正确:ide
... ... Thread 98 / Counter : 87 Thread 97 / Counter : 86 Thread 99 / Counter : 89 Thread 100 / Counter : 89 Thread 101 / Counter : 90 Thread 102 / Counter : 91 Thread 104 / Counter : 95 Thread 108 / Counter : 97 Thread 106 / Counter : 96 Thread 105 / Counter : 95 Thread 103 / Counter : 95 Thread 109 / Counter : 98 Thread 107 / Counter : 97 Main Thread / Counter : 98
这里的缘由在于synchronized
是锁定当前实例对象的代码块。也就是当多条线程操做同一个实例对象的同步方法是时,只有一条线程能够访问,其余线程都须要等待。这里Runnable
实例有多个,因此锁就不起做用。
咱们继续修改代码,使得Runnable
实例只有一个:性能
private static final int TEST_THREAD_COUNT = 100; private static int counter = 0; private final static CountDownLatch latch = new CountDownLatch(TEST_THREAD_COUNT); static class MyRunnable implements Runnable { @Override public synchronized void run() { ++counter; System.out.println("Thread " + Thread.currentThread().getId() + " / Counter : " + counter); latch.countDown(); } } public static void main(String[] args) { Thread[] threads = new Thread[TEST_THREAD_COUNT]; MyRunnable myRun = new MyRunnable(); for (int i = 0; i < TEST_THREAD_COUNT; i++) { threads[i] = new Thread(myRun); threads[i].start(); } try { latch.await(); System.out.println("Main Thread " + " / Counter : " + counter); } catch (InterruptedException e) { e.printStackTrace(); } }
如今咱们发现屡次执行代码后,最后结果都是100
。
咱们能够给counter
变量添加volatile
关键字(这里它对于结果没有影响)。
当一个变量被定义为volatile以后,它对全部的线程就具备了可见性,也就是说当一个线程修改了该变量的值,全部的其它线程均可以当即知道。经过synchronized
和Lock
也可以保证可见性,synchronized
和Lock
能保证同一时刻只有一个线程获取锁而后执行同步代码,而且在释放锁以前会将对变量的修改刷新到主存当中。所以能够保证可见性。优化
synchronized
在发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生。另外在资源竞争不是很激烈的状况下,偶尔会有同步的情形下,synchronized是很合适的。缘由在于,编译程序一般会尽量的进行优化synchronized
,另外可读性很是好,无论用没用过5.0多线程包的程序员都能理解。可是当同步竞争很是激烈的时候,synchronized
的性能一会儿会降低几十倍。还有一个最大的问题就是多线程竞争一个锁时,其他未获得锁的线程只能不停的尝试得到锁,而不能中断。这种状况下就会形成大量的竞争线程性能的降低。atom
针对synchronized的一系列缺点,JDK5提供了Lock
类,目的是为同步机制进行改善。Lock
和synchronized
有一点很是大的不一样,采用synchronized不须要用户手动的去释放锁,当synchronized
方法或者代码块执行完毕以后,系统会自动的让线程释放对锁的占有,而Lock
则必需要用户去手动释放锁,若是没有主动的释放锁,就会可能致使出现死锁的现象。不过这篇文章这里不讨论Lock
类。
在Java 1.5的java.util.concurrent.atomic包下提供了一些原子操做类,即对基本数据类型的 自增(加1操做),自减(减1操做)、以及加法操做(加一个数),减法操做(减一个数)进行了封装,保证这些操做是原子性操做。
咱们这里使用AtomicInteger
类spa
private static final int TEST_THREAD_COUNT = 100; private static AtomicInteger at = new AtomicInteger(0); public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(TEST_THREAD_COUNT); Thread[] threads = new Thread[TEST_THREAD_COUNT]; for (int i = 0; i < TEST_THREAD_COUNT; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { int value = at.incrementAndGet(); System.out.println("Thread " + Thread.currentThread().getId() + " / Counter : " + value); latch.countDown(); } }); threads[i].start(); } try { latch.await(); System.out.println("Main Thread " + " / Counter : " + at.get()); } catch (InterruptedException e) { e.printStackTrace(); } }