当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
通俗地说就是,不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。相反,如果在编程的时候,需要考虑这些线程在运行时的调度和交替(例如在get()调用到期间不能调用set()),或者需要进行额外的同步(比如使用synchronized关键字等),那么就是线程不安全的。下面我们来看看几个常见的线程不安全的实例。
运行结果错误
这种情况就是我们常见的a++导致的线程不安全的问题,由于a++并不是原子性的,所以当多个线程同时进行a++时就会导致运行结果不正确。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class MultiThreadsError implements Runnable {
private static int index = 0; private static MultiThreadsError instance = new MultiThreadsError();
@Override public void run() { for (int i = 0; i < 10000; i++) { index++; } }
public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("表面上的结果是: " + index); } }
|
我们可以把上面的的程序升级一下,看看a++具体消失在哪里。CyclicBarrier强行让两个线程都执行完a++操作后才继续往下执行。AtomicInteger是原子类是线程安全的,用来记录a++的正确结果和错误次数。marked来记录某个值的自增操作是否已经执行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicInteger;
public class MultiThreadsError1 implements Runnable {
private static int index = 0; private static MultiThreadsError1 instance = new MultiThreadsError1(); private static AtomicInteger realIndex = new AtomicInteger(); private static AtomicInteger wrongCount = new AtomicInteger(); private final boolean[] marked = new boolean[100000]; private static CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2); private static CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
@Override public void run() { marked[0] = true; for (int i = 0; i < 10000; i++) { try { cyclicBarrier1.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } index++; try { cyclicBarrier2.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } realIndex.incrementAndGet(); synchronized (instance) { if (marked[index] && marked[index - 1]) { wrongCount.incrementAndGet(); System.out.println("发生错误: " + index); } marked[index] = true; } }
}
public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("表面上的结果是: " + index); System.out.println("真正的结果是: " + realIndex); System.out.println("错误次数: " + wrongCount); } }
|
死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
public class MultiThreadsError2 implements Runnable {
private int flag = 0; private static final Object o1 = new Object(); private static final Object o2 = new Object();
@Override public void run() { System.out.println("flag = " + flag); if (flag == 0) { synchronized (o1) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2) { System.out.println(0); } } } if (flag == 1) { synchronized (o2) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1) { System.out.println(1); } } } }
public static void main(String[] args) { MultiThreadsError2 r1 = new MultiThreadsError2(); MultiThreadsError2 r2 = new MultiThreadsError2(); r1.flag = 0; r2.flag = 1; new Thread(r1).start(); new Thread(r2).start(); } }
|
总结
访问共享的变量或资源,会有并发风险,比如对象的属性、静态变量、共享缓存、数据库等。所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题。我们使用其他类的时候,如果对方没有声明自己是线程安全的,那么大概率会存在并发问题。