首先我们来看一段代码:
public class SynchronizedDemo implements Runnable {
private static int data = 0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
data++;
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo demo = new SynchronizedDemo();
for (int i = 0; i < 1000; i++) {
new Thread(demo).start();
}
Thread.sleep(5000);
System.out.println(data);
}
}
我们想程序输出10000,但是很遗憾实际运行时很多情况下都不是10000而是小于10000,原因是data++并不是原子操作,而是线程从内存中读取data值,完成自增,然后刷新内存值,在单线程的情况下,这并没有什么问题,然而在多线程的情况下,当data值完成自增后还没来得及刷新内存,这个时候另外一个线程将内存中的data完成了读取自增刷新的操作,这个时候前一个线程才将内存的数据刷新,这两个线程的两次自增只自增了一次。这就造成了线程的安全问题,线程安全问题的本质是共享资源被多个线程访问,造成了数据不一致的情况。
要使上面的demo变成线程安全的程序很简单,这里介绍两种方法。
通过对run方法添加synchronized关键字即可实现线程安全:
@Override
public synchronized void run() {
for (int i = 0; i < 10; i++) {
data++;
}
}
synchronized可以修饰方法和代码块,被synchronized修饰的方法和代码块同一时间下只允许一个线程访问相当于给这部分添加了一个锁,任何线程在没拿到这个锁的情况下是不能访问这部分的。
我们还可以通过显式lock的方式为代码加锁。
通过lock的方式加锁,上面的代码可改为:
private final Lock lock = new ReentrantLock(true);
@Override
public void run() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
data++;
}
} finally {
lock.unlock();
}
}
实例化一个lock,然后为有共享资源访问的代码显式的加锁。Java中的lock有ReentrantLock和ReentrantReadWriteLock两种。
lock:ReentrantLock、ReentrantReadWriteLock、StampedLock(JDK8新增);
原子类:AtomicXXXXXX;
信号量:CountDownLatch、CyclicBarrier、Semaphore;
其他的还有线程池工具,ForkJoin框架;
我们的多线程系列文章就是围绕这些个类和工具展开分析。
原文:https://www.cnblogs.com/Sirius-/p/13934576.html