並發編程基礎2
一:線程安全
當多個線程同時訪問一個實例(對象或者方法)時,輸入的行為是正確的,那麽可以認為這個程序是線程安全的。
看下面這段代碼,10個線程同時訪問1個實例,那麽運行結果會怎樣呢?
/** * */ package com.day1; import java.io.UnsupportedEncodingException; /** * @author Administrator 線程安全:多個線程訪問同一個實例時,如果輸入的行為正確,那麽我們可以稱作線程安全, * 下面的實例,10個線程訪問同一個對象,存在線程安全問題,但是如果我們在可能存在安全問題 * 的方法或者代碼片段上加上鎖synchronized,就可以避免線程安全問題 * a:線程獲取cpu的處理權,是隨機的,可以配置優先級,而不是按照程序的書寫順序 * b:在使用synchronized關鍵字時,應該盡量縮小使用範圍,這樣可以提高系統處理效率(前提是避免線程安全問題) */ public class MultiThread2 extends Thread { private int num = 10; public void run() { num--; String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":::" + num); } public static void main(String[] args) throws UnsupportedEncodingException { MultiThread2 task = new MultiThread2(); Thread thread1 = new Thread(task, "線程1"); Thread thread2 = new Thread(task, "線程2"); Thread thread3 = new Thread(task, "線程3"); Thread thread4 = new Thread(task, "線程4"); Thread thread5 = new Thread(task, "線程5"); Thread thread6 = new Thread(task, "線程6"); Thread thread7 = new Thread(task, "線程7"); Thread thread8 = new Thread(task, "線程8"); Thread thread9 = new Thread(task, "線程9"); Thread thread10 = new Thread(task, "線程10"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); thread6.start(); thread7.start(); thread8.start(); thread9.start(); thread10.start(); } }
運行結果:
線程2:::8 線程5:::6 線程1:::7 線程3:::7 線程7:::5 線程4:::4 線程9:::3 線程6:::2 線程8:::1 線程10:::0
通過運行結果,可以看出存在線程安全問題 ,因為10個線程共享一個成員變量num的值,都去操作它
如果想是運行行為正確,可以在方法上面加個同步synchronized關鍵字:
public synchronized void run() { num--; String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":::" + num); }
線程1:::9 線程3:::8 線程4:::7 線程7:::6 線程2:::5 線程5:::4 線程6:::3 線程8:::2 線程9:::1 線程10:::0
這時運行結果就是正確的了,順序num的值是對的,有人會說線程輸出順序不對,這是因為線程
的執行要看cpu的分配,是隨機的,不是按照程序書寫順序。
一般情況下,在保證同步安全的前提下,要盡可能的縮小鎖定的範圍,這樣可以提高系統的處理效率,如下:
public void run() { synchronized (this) { System.out.println(Thread.currentThread().getName() + ":::" + --num); } }
線程1:::9 線程2:::8 線程4:::7 線程6:::6 線程8:::5 線程3:::4 線程10:::3 線程5:::2 線程7:::1 線程9:::0
運行結果依然是正確的。
二:同步與異步
只有多個線程存在同享時,才需要在同步鎖synchronized,不共享的情況下不需要加,一個類中如果有的方法是同步的
,有的方法是異步的,那麽當多個線程訪問該實例時(一個實例),會發生什麽情況呢?
/** * */ package com.day1; /** * @author Administrator * 兩個線程訪問同一個實例,如果訪問的都是同步方法(加synchronized關鍵字,可以是同一個方法,也可能是 * 兩個方法),那麽一個線程正在執行程序時,另一個線程處於阻塞狀態。 * 如果一個線程訪問同步方法,另一個線程訪問異步方法,那麽不存在阻塞問題。 */ public class MultiThread3 { private synchronized void method1() { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } private void method2() { System.out.println("running method2..."); } public static void main(String[] args) { MultiThread3 task = new MultiThread3(); Thread t1 = new Thread(new Runnable(){ @Override public void run() { task.method1(); } }); Thread t2 = new Thread(new Runnable(){ @Override public void run() { task.method2(); } }); t1.start(); t2.start(); } }
兩個線程訪問同一個類時,如果一個線程訪問同步方法,另一個線程訪問異步方法時,那麽不存在線程阻塞。
running method1... running method2...
運行結果:
兩行結果都打印完之後,然後jvm等待1s後停止,說明兩個線程都是正常執行,沒有存在阻塞問題。
如果把第二個方法上面也加上synchronized關鍵字,
private synchronized void method2() { System.out.println("running method2..."); }
在運行:
running method1... running method2...
那麽運行結果是,先輸出第一行,然後等待1s後又輸出了第二行,說明第一個線程訪問method1時,
第二個線程沒有拿到對象鎖,處於線程等待狀態,待一個線程執行完畢後,釋放對象鎖,然後第二個
線程執行method2方法。
三:多個線程多個實例控制並發
如果有多個線程訪問多個 實例,那麽他們之間是沒有任何關系的,不存在線程安全問題,但是如果
也想使用鎖控制它們,則需要類鎖
/** * */ package com.day1; /** * @author Administrator * 兩個線程訪問同一個實例,如果訪問的都是同步方法(加synchronized關鍵字,可以是同一個方法,也可能是 * 兩個方法),那麽一個線程正在執行程序時,另一個線程處於阻塞狀態。 * 如果一個線程訪問同步方法,另一個線程訪問異步方法,那麽不存在阻塞問題。 */ public class MultiThread3 { private synchronized void method1() { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void method2() { System.out.println("running method2..."); } public static void main(String[] args) { final MultiThread3 task1 = new MultiThread3(); final MultiThread3 task2 = new MultiThread3(); Thread t1 = new Thread(new Runnable(){ @Override public void run() { task1.method1(); } }); Thread t2 = new Thread(new Runnable(){ @Override public void run() { task2.method2(); } }); t1.start(); t2.start(); } }
運行結果:
running method1... running method2...
兩行數據同時輸出,等待1s然後停止,說明不存在線程安全問題,因為這裏是兩個實例,不同的鎖
如果也想讓它們串行執行,則需要使用類鎖
1:在兩個方法上加上static,則兩個同步方法的鎖變成了類的字節碼文件
private synchronized static void method1() { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized static void method2() { System.out.println("running method2..."); }
2:使用同步代碼塊,將類的字節碼文件作為鎖
private void method1() { synchronized (this.getClass()) { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } } private void method2() { synchronized (this.getClass()) { System.out.println("running method2..."); } }
3:定義一個靜態的對象,作為類鎖
因為靜態的對象,所有的實例都是共享這個對象,所以在同一時刻只能有一個實例可以持有這個鎖
private static final Object lock = new Object(); private void method1() { synchronized (lock) { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } } private void method2() { synchronized (lock) { System.out.println("running method2..."); } }
以上3種方式都可以實現類鎖!
並發編程基礎2