1 多线程导致的问题
1.1 安全问题
1.2 性能问题
2 线程安全问题
- 错误案例
/**
* @Description 第一种:运行结果出错
* 演示计数不准确,找出具体出错的位置
* @Author tzb
* @Date 2021/3/7 12:04
* @Version 1.0
**/
public class MultiThreadsError implements Runnable {
private int index = 0;
private static MultiThreadsError instance = new MultiThreadsError();
@Override
public void run() {
// while (index < 10000) {
// index++;
// }
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(instance.index);
}
}
public class MultiThreadsError implements Runnable {
private int index = 0;
final boolean[] marked = new boolean[10000000];
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongCount = new AtomicInteger();
private static MultiThreadsError instance = new MultiThreadsError();
@Override
public void run() {
// while (index < 10000) {
// index++;
// }
for (int i = 0; i < 10000; i++) {
index++;
realIndex.incrementAndGet();
if (marked[index]) {
System.out.println("发生了错误:" + index);
wrongCount.incrementAndGet();
}
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("表面的结果:"+instance.index);
System.out.println("真正运行的次数:"+ realIndex.get());
System.out.println("错误的次数:"+ wrongCount.get());
}
}
- 原因分析:多个线程同时到达35行。假设线程1到达35行,发它没有被写入,默认是false,跳到39行,线程1准备将它置为true。在它置成功之前,线程突然切换到线程2,线程2到了35行,index是false,所以线程2也跳过。两个线程发生了冲突,if中无法捕捉。
public class MultiThreadsError implements Runnable {
private int index = 0;
final boolean[] marked = new boolean[10000000];
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongCount = new AtomicInteger();
private static MultiThreadsError instance = new MultiThreadsError();
@Override
public void run() {
// while (index < 10000) {
// index++;
// }
for (int i = 0; i < 10000; i++) {
index++;
realIndex.incrementAndGet();
synchronized (instance){
if (marked[index]) {
System.out.println("发生了错误:" + index);
wrongCount.incrementAndGet();
}
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("表面的结果:"+instance.index);
System.out.println("真正运行的次数:"+ realIndex.get());
System.out.println("错误的次数:"+ wrongCount.get());
}
}
- 原因分析:冲突了1次,为何统计到了429次?
- 假设index初始为0,线程1和线程2发生了冲突,index都是1,线程1进入同步代码,将marked第1位置为true。理论上线程2进入同步,发现marked-1已经为true,于是记录下这次错误。但是实际上当第二个线程到29行时,切换到线程1,线程1执行27行,index为2,又切换到线程2,线程2判断30行(index为2),和刚才他们冲突的地点不一样。
- 引入
CyclicBarrier
,作用是让线程在该等待的地方等待。
public class MultiThreadsError implements Runnable {
private int index = 0;
final boolean[] marked = new boolean[10000000];
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongCount = new AtomicInteger();
static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
private static MultiThreadsError instance = new MultiThreadsError();
@Override
public void run() {
// while (index < 10000) {
// index++;
// }
for (int i = 0; i < 10000; i++) {
try {
cyclicBarrier2.reset();
//如果有2个线程都执行过await,则放行
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 (instance){
if (marked[index]) {
System.out.println("发生了错误:" + index);
wrongCount.incrementAndGet();
}
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("表面的结果:"+instance.index);
System.out.println("真正运行的次数:"+ realIndex.get());
System.out.println("错误的次数:"+ wrongCount.get());
}
}
- 原因分析:由于2个栅栏的保护,当2个线程都运行到54行时,在同步代码执行期间,index是不变的。但是由于
synchronized
的可见性。index加完后的情况分析:(1)第一种情况是,虽然2个线程同时运行,但是发生错误的几率不高,大部分情况下不发生碰撞。线程1加完后index变为1,线程2加完后index是2。线程1执行完60行,会将index-2标记为true。由于可见性的保证,线程2进入同步代码后,知道之前的线程做了哪些事,此时线程2的index就不是1了,而是2,而index-2已经被线程1标记过,线程2会认为发生了错误,但是实际上不是错误,而是由于可见性,使得2个线程的结果可以相互通知。(2)正确的判断,假设2个线程同时执行index++,发生了线程安全问题,2个线程执行完后,index是1。线程1抢到锁,将index-1置为true。然后线程2进入同步代码,发现index-1是true,但是需要加一个条件(前一位也是true)。如果前一位是false,就是刚才的正常情况
- 正确代码
public class MultiThreadsError implements Runnable {
private int index = 0;
final boolean[] marked = new boolean[10000000];
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongCount = new AtomicInteger();
static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
private static MultiThreadsError instance = new MultiThreadsError();
@Override
public void run() {
// while (index < 10000) {
// index++;
// }
marked[0] = true;
for (int i = 0; i < 10000; i++) {
try {
cyclicBarrier2.reset();
//如果有2个线程都执行过await,则放行
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 (instance) {
if (marked[index] && marked[index - 1]) {
System.out.println("发生了错误:" + index);
wrongCount.incrementAndGet();
}
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("表面的结果:" + instance.index);
System.out.println("真正运行的次数:" + realIndex.get());
System.out.println("错误的次数:" + wrongCount.get());
}
}