对于普通的变量,在涉及多线程操作时,会遇到经典的线程安全问题。考虑如下代码:
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
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(); } }
多次执行新代码,我们依旧发现结果不正确:
... ... 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