线程安全
什么是线程安全
权威书籍描述:
《Java Concurrency In Practice》的作者 Brian Goetz 对“线程安全”有一个比较恰当的定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行仼何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全
的。
通俗描述:
不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要做任何额外的处理
(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。
什么情况下会出现线程安全问题,怎么避免?
1.运行结果错误
:a++多线程下出现消失的请求现象。
2.活跃性
问题:死锁、活锁、饥饿
3.对象发布和初始化
的时候的安全问题
代码演示:a++多线程下出现消失的请求现象
/**
* 第一种:运行结果出错。演示计数不准确(减少),找出具体出错的位置。
*/
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);
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++) {
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 (instance) {
if (marked[index] && marked[index - 1]) {
System.out.println("发生错误" + index);
wrongCount.incrementAndGet();
}
marked[index] = true;
}
}
}
}
打印结果:
发生错误6515
发生错误12216
表面上结果是19998
真正运行的次数20000
错误次数2
代码演示:死锁
public class MultiThreadError implements Runnable {
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) {
MultiThreadError r1 = new MultiThreadError();
MultiThreadError r2 = new MultiThreadError();
r1.flag = 1;
r2.flag = 0;
new Thread(r1).start();
new Thread(r2).start();
}
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
}
对象发布和初始化的时候的安全问题
- 什么是发布
- 什么是逸出
- 方法返回一个private对象(private对象本身是不让外部访问)
- 还未完成初始化(构造函数没完全执行完毕)就把对象提供给外界,比如:
- 在构造函数中未初始化完毕就this赋值
- 隐式逸出——注册监听事件
- 构造函数中运行线程
- 如何解决逸出
- 返回“副本”
- 工厂模式
代码演示:方法返回一个private对象
public class MultiThreadsError3 {
private Map<String, String> states;
public MultiThreadsError3() {
states = new HashMap<>();
states.put("1", "周一");
states.put("2", "周二");
states.put("3", "周三");
states.put("4", "周四");
}
public Map<String, String> getStates() {
return states;
}
public Map<String, String> getStatesImproved() {
return new HashMap<>(states);
}
public static void main(String[] args) {
MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
Map<String, String> states = multiThreadsError3.getStates();
// System.out.println(states.get("1"));
// states.remove("1");
// System.out.println(states.get("1"));
System.out.println(multiThreadsError3.getStatesImproved().get("1"));
multiThreadsError3.getStatesImproved().remove("1");
System.out.println(multiThreadsError3.getStatesImproved().get("1"));
}
}
代码演示:在构造函数中未初始化完毕就this赋值
public class MultiThreadsError4 {
static Point point;
public static void main(String[] args) throws InterruptedException {
new PointMaker().start();
// Thread.sleep(10);
Thread.sleep(105);
if (point != null) {
System.out.println(point);
}
}
}
class Point {
private final int x, y;
public Point(int x, int y) throws InterruptedException {
this.x = x;
MultiThreadsError4.point = this;
Thread.sleep(100);
this.y = y;
}
@Override
public String toString() {
return x + "," + y;
}
}
class PointMaker extends Thread {
@Override
public void run() {
try {
new Point(1, 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
代码演示:隐式逸出——注册监听事件
/**
* 观察者模式
*/
public class MultiThreadsError5 {
private int count;
public MultiThreadsError5(MySource source) {
source.registerListener(event -> System.out.println("\n我得到的数字是" + count));
//模拟业务操作
for (int i = 0; i < 10000; i++) {
System.out.print(i);
}
count = 100;
}
public static void main(String[] args) {
MySource mySource = new MySource();
new Thread(() -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
mySource.eventCome(new Event() {
});
}).start();
new MultiThreadsError5(mySource);
}
static class MySource {
private EventListener listener;
void registerListener(EventListener eventListener) {
this.listener = eventListener;
}
void eventCome(Event e) {
if (listener != null) {
listener.onEvent(e);
} else {
System.out.println("还未初始化完毕");
}
}
}
interface EventListener {
void onEvent(Event e);
}
interface Event {
}
}
代码演示:用工厂模式修复上面代码的初始化问题,getInstance()方法
public class MultiThreadsError7 {
private int count;
private EventListener listener;
private MultiThreadsError7(MySource source) {
listener = event -> System.out.println("\n我得到的数字是" + count);
for (int i = 0; i < 10000; i++) {
System.out.print(i);
}
count = 100;
}
public static MultiThreadsError7 getInstance(MySource source) {
MultiThreadsError7 safeListener = new MultiThreadsError7(source);
source.registerListener(safeListener.listener);
return safeListener;
}
public static void main(String[] args) {
MySource mySource = new MySource();
new Thread(() -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
mySource.eventCome(new MultiThreadsError5.Event() {
});
}).start();
new MultiThreadsError7(mySource);
}
static class MySource {
private EventListener listener;
void registerListener(EventListener eventListener) {
this.listener = eventListener;
}
void eventCome(MultiThreadsError5.Event e) {
if (listener != null) {
listener.onEvent(e);
} else {
System.out.println("还未初始化完毕");
}
}
}
interface EventListener {
void onEvent(MultiThreadsError5.Event e);
}
interface Event {
}
}
各种需要考虑线程安全的情况
访问共享的变量或资源
,会有并发风险,比如对象的属性、静态变量共享缓存、数据库等- 所有
依赖时序
的操作,即使每一步操作都是线程安全的,还是存在并发问题:read-modify-write、 check-then-act - 不同的数据之间
存在捆绑关系
的时候 - 我们使用其他类的时候,如果对方
没有声明
自己是线程安全的
多线程会导致的问题
- 性能问题有哪些体现、什么是性能问题
- 为什么多线程会带来性能问题
- 调度:上下文切换
- 协作:内存同步
笔记来源:慕课网悟空老师视频《Java并发核心知识体系精讲》