你讲讲你理解的“线程安全”是什么?
《Java Concurrency In Practice》的作者Brian Goetz对“线程安全”有一个比较恰当的定义:“当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的”。
这句话的意思是:不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。
相反,如果在编程的时候,需要考虑这些线程在运行时的调度和交替(例如在get()调用到期间不能调用set()),或者需要进行额外的同步(比如使用synchronized关键字等),那么就是线程不安全的。
引发思考:java中那些类是线程安全的,那些是线程不安全的?实际业务中用了多线程不安全的类,就可能导致数据出错。
第一种:运行结果错误
/**
* 描述: 第一种:运行结果出错。 演示计数不准确(减少)
*/
public class MultiThreadsError implements Runnable{
static MultiThreadsError instance = new MultiThreadsError();
int index = 0;
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(instance.index);
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
index++;
}
}
}
抓出错误代码(很有意思,学到很多)
/**
* 描述: 第一种:运行结果出错。 演示计数不准确(减少),找出具体出错的位置。
*/
public class MultiThreadsError implements Runnable{
static MultiThreadsError instance = new MultiThreadsError();
int index = 0;
//原子计数
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongCount = new AtomicInteger();
static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
//用一个marked[]数组标记是否已加过
final boolean[] marked = new boolean[10000000];
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("表面上结果是:"+instance.index);
System.out.println("真正运行的次数" + realIndex.get());
System.out.println("错误次数" + wrongCount.get());
}
@Override
public void run() {
marked[0] = true;
for (int i = 0; i < 10000; i++) {
/**第二个细节:线程执行的先后顺序执行不了,情况描述:线程1进入synchronized准备做判断的时候,可能CPU切换回去,
index就会被线程2执行index++修改了index的值,线程1判断会导致后面的值直接被标记为true,线程2执行index++后进入
synchronized判断发现为true,输出发生错误,但实际线程1并没有在线程2之前执行index++的写入修改*/
//cyclicBarrier2、cyclicBarrier1可理解为两个大闸,
//第一个闸让两个线程每一次都是同时进行index++(这里的同时不代表一定冲突),冲突是小概率事件
//第二个闸是让两个线程都完成index++后再执行后续判断操作
try {
cyclicBarrier2.reset();
cyclicBarrier1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
index++;
try {
cyclicBarrier1.reset();
cyclicBarrier2.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
realIndex.incrementAndGet();
/**第一个细节:如果不加synchronized (){},会出现一个问题,少统计错误,index的变化导致*/
//这种问题的情况是当两个线程,线程1先判断了marked[index]值为false,准备执行marked[index] = true的时候
//线程2抢先判断了marked[index]值还是为false(线程1还没来得及执行marked[index] = true)
//这是其实这已经出现问题,但却没有统计到这样的错误
synchronized (instance){
/**第三个细节 marked[index - 1]是因为synchronized可见性,一个线程的对值的修改,对另一个线程是可见的*/
//情况描述:某一时刻两个线程没有发生冲0突,比如从index == 0开始,线程1的index为1,线程2的index为2,线程2先进入synchronized
//由于synchronized的可见性,此时线程1可以感知到index值已经是2,所以后面线程1进入synchronized之后
//判断的index值不是1,而是2,因为线程2已经已经把marked[2] = true了,所以就会输入报错。
if (marked[index] && marked[index - 1]){
System.out.println("发生了错误"+index);
wrongCount.incrementAndGet();
}
marked[index] = true;
}
}
}
}