对于普通的变量,在涉及多线程操作时,会遇到经典的线程安全问题。考虑如下代码:
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