Categories
程式開發

淺析:線程安全


思考:

一共有哪幾類線程安全問題那些場景需要額外注意線程安全問題什麼是多線程帶來的上下文切換?什麼是多線程的上下文切換?

線程安全

什麼是線程安全

淺析:線程安全 1

不管業務中遇到怎樣的多個線程訪問某對像或某方法的情況,而在編程這個業務邏輯的時候,都不需要額外做任何額外的處理(也就是可以像單線程編程一樣),程序也可以正常運行(不會因為多線程而出錯),就可以稱為線程安全。

主要是兩個問題

數據爭用:數據讀寫由於同時寫,會造成錯誤數據競爭條件:即使不是同時寫造成的錯誤數據,由於順序原因依然會造成錯誤,例如在寫入前就讀取了

如何避免線程安全問題

運行結果錯誤:a++ 多線程下出現消失的請求現象

活躍性問題:死鎖、活鎖、飢餓

對象發布和初始化的時候的安全問題

a++ 問題

public class MultiThreadsError implements Runnable {

int index = 0;
static MultiThreadsError instance = new MultiThreadsError();

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++; } } }

運行結果錯誤:沒有原子性

a ++

淺析:線程安全 2

思考:

如何找到上一個案例中出錯的值

public class MultiThreadsError implements Runnable {

int index = 0;
static MultiThreadsError instance = new MultiThreadsError();

// 原子计数器功能
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongIndex = new AtomicInteger();

// 由于线程的执行的先后顺序无法确定,所以加入栅栏,让他们同时出发
static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
// 同时释放
static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);

// 使用 boolean 数组标记到错误的值
final boolean[] marked = new boolean[1000000];

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("错误的次数 " + wrongIndex.get());
}

@Override
public void run() {
marked[0] = true;
for (int i = 0; i < 10000; i++) { try { cyclicBarrier1.await(); // 栅栏(当有两个线程执行过它,放行) } catch (Exception e) { e.printStackTrace(); } index++; try { cyclicBarrier1.reset(); cyclicBarrier2.await(); } catch (Exception e) { e.printStackTrace(); } realIndex.incrementAndGet(); synchronized (instance) { if (marked[index] && marked[index - 1]) { System.out.println("发生了错误" + index); wrongIndex.incrementAndGet(); } } marked[index] = true; } } }

死鎖問題

public class MultiThreadError implements Runnable {

int flag = 1;

static Object object1 = new Object();
static Object object2 = 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 (object1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2) {
System.out.println("object 1");
}
}
}

if (flag == 0) {
synchronized (object2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object1) {
System.out.println("object 2");
}
}
}
}
}

對象發布和初始化的時候的安全問題

什麼是發布

聲明為publicreturn 一個對象把對像作為參數傳遞到其他類的方法中

什麼是逸出

方法返回一個private 對象(private 的本意是不讓外部訪問)

public class MultiThreadsError3 {

private Map states;

public MultiThreadsError3() {
states = new HashMap();
states.put("1", "周一");
states.put("2", "周二");
states.put("3", "周三");
states.put("4", "周四");
}

public Map getStates() {
return states;
}

public Map getStatesImproved() {
return new HashMap(states);
}

public static void main(String[] args) {
MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
Map states = multiThreadsError3.getStates();

System.out.println(states.get("1"));
states.remove("1");
System.out.println(states.get("1"));

}
}

還未完成初始化(構造函數還沒完全執行完畢)就把對象提供個外界

在構造函數中未初始化完畢就this 賦值

public class MultiThreadsError4 {

static Point point;

public static void main(String[] args) throws InterruptedException {
new PointMaker().start();

Thread.sleep(505);

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); // MultiThreadsError4 中会根据 sleep 的大于或小于的阻塞时间而变化
this.y = y;
}

@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}

class PointMaker extends Thread {

@Override
public void run() {
try {
new Point(1, 1);
} catch (Exception e) {
e.printStackTrace();
}
}
}

隱式逸出—— 註冊監聽事件(觀察者模式)

public class MultiThreadsError5 {

int count;

public MultiThreadsError5(MySource source) {
source.registerListener(new EventListener() {
@Override
public void onEvent(Event e) {
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(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } mySource.eventCome(new Event() { }); } }).start(); MultiThreadsError5 multiThreadsError5 = 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 { } }

解決:

public class MultiThreadsError7 {

int count;
private EventListener listener;

private MultiThreadsError7(MySource source) {
listener = new EventListener() {
@Override
public void onEvent(MultiThreadsError5.Event e) {
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(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } mySource.eventCome(new MultiThreadsError5.Event() { }); } }).start(); MultiThreadsError7 multiThreadsError7 = 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 { } }

構造函數中運行線程

public class MultiThreadsError6 {

private Map states;

public MultiThreadsError6() {
new Thread(new Runnable() {
@Override
public void run() {
states = new HashMap();
states.put("1", "周一");
states.put("2", "周二");
states.put("3", "周三");
states.put("4", "周四");
}
}).start();
}

public Map getStates() {
return states;
}

public static void main(String[] args) throws InterruptedException {
MultiThreadsError6 multiThreadsError6 = new MultiThreadsError6();
// 造成时间不同执行不同
Thread.sleep(1000);
System.out.println(multiThreadsError6.getStates().get("1"));
}
}

如何解決逸出

副本工廠模式

各種需要考慮線程安全的情況

訪問共享變量或資源,會有並發風險,比如對象的屬性、靜態變量、共享緩存、數據庫等所有依賴時序的操作,即使每一步操作都是線程安全的,還是存在並發問題read-modify-writer 操作:一個線程讀取了一個共享數據,並在此基礎上更新該數據。該例子在上面的a++ 已展示。 check-then-act 操作:一個線程讀取了一個共享數據,並在此基礎上決定其下一個的操作不同的數據之間存在綁定關係的時候IP 和端口號我們使用其他類的時候,如果對方沒有聲明自己是線程安全的,那麼大概率會存在並發問題hashmap 沒有聲明知己是並發安全的,所以我們並發調用hashmap 的時候會出錯

多線程會導致的問題

什麼是性能問題、性能問題有哪些體現?

為什麼多線程會帶來性能問題

調度:上下文切換協作:內存同步

調度:上下文切換

什麼是上下文? :保存現場

緩存開銷:緩存失效

何時會導緻密集的上下文切換:搶鎖、IO

參考

https://coding.imooc.com/class/362.html