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