并发编程之多线程线程安全(上)
1、為什么有線程安全問題?
當多個線程共享同一個全局變量或靜態變量,做寫的操作時,可能會發生數據沖突問題,也就是線程安全問題。但是做讀操作是不會發生數據沖突問題。
案例:現在有100張火車票,有兩個窗口同時搶火車票,請使用多線程模擬搶票效果。
代碼:
public?class?NewThread1?implements?Runnable{????private?int?trainCount?=?100;
????
????public?void?run()?{
????????while?(trainCount>0){
????????????try?{
????????????????Thread.sleep(100);
????????????}catch?(Exception?e){
????????????}
????????????save();
????????}
????}
????private?void?save()?{
????????if?(trainCount?>?0)?{
????????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
????????????trainCount--;
????????}
????}
????public?static?void?main(String[]?args){
????????NewThread1?newThread1?=?new?NewThread1();
????????Thread?thread1?=?new?Thread(newThread1,"①");
????????Thread?thread2?=?new?Thread(newThread1,"②");
????????thread1.start();
????????thread2.start();
????}
}
打印結果:
......②,出售第97張票
①,出售第97張票
①,出售第99張票
②,出售第99張票
一號窗口和二號窗口同時出售火車第九九張,部分火車票會重復出售。
結論發現,多個線程共享同一個全局成員變量時,做寫的操作可能會發生數據沖突問題。
2、線程安全解決方法
2.1、如何解決多線程之間線程安全問題
兩種主流方式:
內置鎖:
內置鎖使用 synchronized 關鍵字實現,synchronized 關鍵字有兩種用法:
修飾需要進行同步的方法(所有訪問狀態變量的方法都必須進行同步),此時充當鎖的對象為調用同步方法的對象
同步代碼塊和直接使用 synchronized 修飾需要同步的方法是一樣的,但是鎖的粒度可以更細,并且充當鎖的對象不一定是 this ,也可以是其它對象,所以使用起來更加靈活。
簡而言之,synchronize 分為同步方法、同步代碼塊。
其中同步方法又分為:非靜態同步方法、靜態同步方法
同步方法 [非靜態同步方法] :
private?synchronized?void?save()?{????if?(trainCount?>?0)?{
????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
????????trainCount--;
????}
}
同步代碼塊:
就是將可能會發生線程安全問題的代碼,給包括起來。synchronized(同一個數據){
????可能會發生線程沖突問題
}
就是同步代碼塊?
synchronized(對象)這個對象可以為任意對象?
{?
????需要被同步的代碼?
}?
對象如同鎖,持有鎖的線程可以在同步中執行;
沒持有鎖的線程即使獲取CPU的執行權,也進不去 ;
同步的前提:
同步的優缺點:
- 好處:解決了多線程的安全問題。
- 弊端:多線程需要判斷鎖,較為消耗資源、搶鎖的資源。
代碼:
private?void?save()?{????synchronized?(this){
????????if?(trainCount?>?0)?{
????????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
????????????trainCount--;
????????}
????}
}
關于 Lock 鎖下次再寫。
2.2、為什么使用線程同步或使用鎖能解決線程安全問題呢?
為了避免線程不安全問題,每次只讓當前一個線程進行執行,代碼執行完成后執行釋放鎖操作,然后才讓其他線程進行執行,這樣就解決了線程安全問題。
2.3、什么是多線程同步
當多個線程共享同一個資源,不會受到其他線程的干擾。
2.4、什么是同步方法?
在方法上修飾 sybchronized 稱為同步方法。
代碼示例:
private?synchronized?void?save()?{????if?(trainCount?>?0)?{
????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
????????trainCount--;
????}
}
2.5、什么是靜態同步函數?
方法加上 static 關鍵字,使用 synchronize 關鍵字修飾 或者使用 類.class 文件。
靜態的同步函數使用的鎖是該函數所屬字節碼文件對象。
可以使用 getClass 方法獲取,也可以用當前 類名.class 表示。
代碼示例:
private?static?void?save()?{????synchronized?(NewThread1.class){
????????if?(trainCount?>?0)?{
????????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
????????????trainCount--;
????????}
????}
}
總結:
synchronize 修飾方法使用鎖是當前 this 鎖。
synchronize 修飾靜態方法使用鎖是當前類的字節碼文件。
3、ThreadLocal
什么是 ThreadLocal ?
ThreadLocal 提高一個線程的局部變量,訪問某個線程擁有自己局部變量。
當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
ThreadLocal 的 4 個方法:
案例:
創建三個線程,每個線程生成自己獨立序列號。
class?Res{????private?ThreadLocal<Integer>?mThreadLocal?=?new?ThreadLocal<Integer>(){
????????
????????protected?Integer?initialValue()?{
????????????return?0;
????????};
????};
????protected?Integer?getNumber()?{
????????int?count?=?mThreadLocal.get()?+?1;
????????mThreadLocal.set(count);
????????return?count;
????};
}
public?class?ThreadDemo1?extends?Thread{
????private?Res?res;
????public?ThreadDemo1(Res?res){
????????this.res?=?res;
????}
????
????public?void?run()?{
????????for?(int?i?=?0;?i?<?3;?i++)?{
????????????System.out.println(Thread.currentThread().getName()+","+res.getNumber());
????????}
????}
????public?static?void?main(String[]?args){
????????Res?res?=?new?Res();
????????ThreadDemo1?threadDemo1?=?new?ThreadDemo1(res);
????????ThreadDemo1?threadDemo2?=?new?ThreadDemo1(res);
????????ThreadDemo1?threadDemo3?=?new?ThreadDemo1(res);
????????threadDemo1.start();
????????threadDemo2.start();
????????threadDemo3.start();
????}
}
4、本章總結
線程安全的兩種主流方式:
ThreadLocal 提高一個線程的局部變量,訪問某個線程擁有自己局部變量。
我創建了一個java相關的公眾號,用來記錄自己的學習之路,感興趣的小伙伴可以關注一下微信公眾號哈:niceyoo
總結
以上是生活随笔為你收集整理的并发编程之多线程线程安全(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多线程编程(三)--创建线程之Threa
- 下一篇: Heap 3214 LIS题解