Java多线程-线程安全
# Java多线程08-线程安全
线程安全的定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
# 线程安全问题案例
线程安全主要是两个问题导致的:
- 数据争用:数据读写由于同时写,会造成错误数据
- 竞争条件:即使不是同时写造成的错误数据,由于顺序原因依然会造成错误,例如写入前就去读。
演示两个线程对一个变量进行相加,结果少加的情况
public class MultiThreadsError implements Runnable {
private static int index = 0;
public static void main(String[] args) throws InterruptedException {
MultiThreadsError multiThreadsError = new MultiThreadsError();
Thread thread1 = new Thread(multiThreadsError);
Thread thread2 = new Thread(multiThreadsError);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(index);
System.out.println("程序执行完毕");
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
index++;
}
}
}
假设线程1拿到index,对其进行了++,但是在还没有把++的结果写回,线程2就拿到index,而这时候线程2拿到的index值还是++之前的值。
public class MultiThreadsError implements Runnable {
private static int index = 0;
public static void main(String[] args) throws InterruptedException {
MultiThreadsError multiThreadsError = new MultiThreadsError();
Thread thread1 = new Thread(multiThreadsError);
Thread thread2 = new Thread(multiThreadsError);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(index);
System.out.println("程序执行完毕");
}
@Override
public void run() {
add();
}
private synchronized void add() {
for (int i = 0; i < 10000; i++) {
index++;
}
}
}
# 死锁案例
现在创建两个线程,两个线程在分别拥有自己锁的情况下,尝试去获取另一个线程的锁,就会造成死锁。
public class DeadLock implements Runnable{
private int flag = 1;
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
DeadLock deadLock1 = new DeadLock();
DeadLock deadLock2 = new DeadLock();
deadLock2.flag = 2;
Thread thread1 = new Thread(deadLock1);
Thread thread2 = new Thread(deadLock2);
thread1.start();
thread2.start();
}
@Override
public void run() {
if (flag == 1) {
System.out.println("flag = " + flag);
synchronized (lock1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lock2) {
System.out.println("获取到锁2");
}
}
}
if (flag == 2) {
System.out.println("flag = " + flag);
synchronized (lock2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lock1) {
System.out.println("获取到锁1");
}
}
}
}
}
# 对象溢出问题
1、方法返回了一个private对象导致外部程序可以访问和修改该对象
public class MultiThreadPrivate {
private Map<String, String> initMap;
public MultiThreadPrivate () {
initMap = new HashMap<>();
initMap.put("1", "元素1");
initMap.put("2", "元素2");
initMap.put("3", "元素3");
}
public Map<String, String> getInitMap () {
return initMap;
}
public static void main(String[] args) {
MultiThreadPrivate threadPrivate = new MultiThreadPrivate();
Map<String, String> outerMap = threadPrivate.getInitMap();
System.out.println(outerMap.get("1"));
outerMap.remove("1");
System.out.println(outerMap.get("1"));
}
}
这里就是get方法把private对象暴露给外部了,外部的程序就可以访问和修改该私有对象。
修正方案
通过返回副本的方式来解决。
public class MultiThreadPrivate {
private Map<String, String> initMap;
public MultiThreadPrivate () {
initMap = new HashMap<>();
initMap.put("1", "元素1");
initMap.put("2", "元素2");
initMap.put("3", "元素3");
}
public Map<String, String> getInitMap () {
//return initMap;
// 修正方案 使用副本代替原对象
return new HashMap<>(initMap);
}
public static void main(String[] args) {
MultiThreadPrivate threadPrivate = new MultiThreadPrivate();
Map<String, String> outerMap = threadPrivate.getInitMap();
System.out.println(outerMap.get("1"));
outerMap.remove("1");
System.out.println(threadPrivate.getInitMap().get("1"));
}
}
2、还未完成初始化就把对象提供给外界
public class MultiThreadThis {
static Point myPoint;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new PointMaker());
thread.start();
Thread.sleep(105);
if (myPoint != null) {
System.out.println(myPoint);
}
}
}
class Point {
private final int x, y;
public Point(int x, int y) throws InterruptedException {
this.x = x;
MultiThreadThis.myPoint = this;
Thread.sleep(1000);
this.y = y;
}
@Override
public String toString() {
return x + "," + y;
}
}
class PointMaker implements Runnable {
@Override
public void run() {
try {
new Point(1, 1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
修正方案
避免在未初始化完毕就this赋值
3、隐式逸出 - 注册监听事件
public class MultiThreadObserve {
int count;
public MultiThreadObserve(MySource source) {
source.registerListener(new EventListener() {
@Override
public void onEvent(Event e) {
System.out.println("\n我得到的数字是" + count);
}
});
for (int i = 0; i < 10000; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(i);
}
count = 100;
}
public static void main(String[] args) {
MySource source = new MySource();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
source.eventCome(new Event() {
});
}
}).start();
MultiThreadObserve multiThreadObserve = new MultiThreadObserve(source);
}
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 {
}
}
4、构造函数中运行线程
public class MultiThreadConstruct {
private Map<String, String> initMap;
public MultiThreadConstruct() {
new Thread(new Runnable() {
@Override
public void run() {
initMap = new HashMap<>();
initMap.put("1", "哈哈");
initMap.put("2", "嘿嘿");
initMap.put("3", "嘻嘻");
}
}).start();
}
public Map<String, String> getInitMap() {
return initMap;
}
public static void main(String[] args) {
MultiThreadConstruct threadPrivate = new MultiThreadConstruct();
Map<String, String> outerMap = threadPrivate.getInitMap();
System.out.println(outerMap.get("1"));
}
}
由于是子线程去创建对象,而主线程以为对象已经创建完毕,使用对象就会因为子线程对象没有创建完毕,导致空指针。
修正方案
避免在构造函数中运行线程。
# 考虑线程安全的情况
- 访问共享的变量或资源,会有并发风险,比如对象的属性、静态变量、共享缓存、数据库等
- 所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题:
- 不同的数据之间存在捆绑关系的时候
- 我们使用其他类的时候,如果对方没有声明自己是线程安全的,那么大概率会存在并发问题
上次更新: 2024/06/29, 15:13:44