1.線程與多線程的概念:在一個程序中,能夠獨立運行的程序片段叫作“線程”(Thread)。多線程(multithreading)是指從軟件或者硬件上實現多個線程併發執行的技術。
2.多線程的意義:多線程可以在時間片裏被cpu快速切換,資源能更好被調用、程序設計在某些情況下更簡單、程序響應更快、運行更加流暢。
2.如何啓動一個線程:繼承Thread類、實現Runnable接口、實現Callable接口
3.爲什麼要保證線程的同步?:java允許多線程併發控制,當多個線程同時操作一個可共享的資源變量時(如數據的增刪改查),將會導致數據不準確,相互之間產生衝突,因此加入同步鎖以避免在該線程沒有完成操作之前,被其他線程的調用,從而保證了該變量的唯一性和準確性。
4.基本的線程同步:使用synchronized關鍵字、特殊域變量volatile、wait和notify方法等。
public class T { private int count=10; private Object o=new Object(); public void m(){ synchronized (o){ //任何線程要執行下面的代碼,必須先拿到o的鎖 count--; System.out.println(Thread.currentThread().getName()+"count="+count); } } }
假設這段代碼中有多個線程,當第一個線程執行到m方法來的時候,sync鎖住了堆內存中的o對象,這個時候第二個線程是進不來的,它必須等第一個線程執行完,鎖釋放掉纔可以接着執行。這裏有個鎖的概念叫做互斥鎖,sync是一種互斥鎖。
public class T1 { private static int count =10; public synchronized static void m(){ //這裏等同於synchronized(T3.class) count--; System.out.println(Thread.currentThread().getName()+"count="+count); } public static void mm(){ synchronized (T1.class){ //思考:這裏寫成synchronized(this)是否可以? count--; } } }
思考這裏,注意sync修飾的是一個靜態方法和靜態的屬性,靜態修飾的方法和屬性是不需要new出對象來就可以訪問的,所以這裏沒有new出對象,sync鎖定的是T3.class對象。
public class T2 implements Runnable{ private int count =10; @Override public /*synchronized*/ void run(){ count--; System.out.println(Thread.currentThread().getName()+"count="+count); } public static void main(String[] args) { T2 t=new T2(); for (int i=0;i<5;i++){ new Thread(t,"THREAD"+i).start(); } } }
上面代碼中開啓了五個線程,執行run方法對count進行減一的操作,這裏會出現線程搶佔資源的問題。當第一個線程在執行run方法時,減減的過程中,第二個線程也進入了方法,同樣也在執行減減,可能會出現第二個線程減完的時候,第一個線程才輸出count,這時候就出現了線程重入。處理方法可以加上synchronized關鍵字,只有等第一個線程執行完畢,第二個線程纔可以進入,即同一時刻只能有一個線程來對它進行操作,也就是原子性。
public class Account { /** * 賬號持有人和賬號餘額 */ String name; double balance; /** * 設置賬號餘額,線程睡眠目的是先讓它讀數據 * @param name * @param balance */ public synchronized void set(String name,double balance){ this.name=name; try { Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } this.balance=balance; } public double getBalance(String name){ return this.balance; } public static void main(String[] args) { Account a=new Account(); new Thread(()->a.set("zhangsan",100.0)).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("zhangsan")); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("zhangsan")); } }
這是一個小demo,代碼中只對set方法加了鎖,沒有對get方法加鎖,這個時候會出現髒讀現象。解決方法是讀和寫的方法都加鎖。
public class T3 { synchronized void m1(){ System.out.println("m1 start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } m2(); } synchronized void m2() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m2"); } }
這個案例中,m1和m2方法都已經加上了鎖,當執行m1的時候再去執行m2,這樣是可以的。一個同步方法可以調用另外一個同步方法,也就是說sync獲得的鎖是可重入鎖。還有個概念是死鎖,死鎖是指多個線程搶佔資源而造成的一種互相等待。舉個例子,一個箱子需要兩把鑰匙纔可以打開,鑰匙分別在兩個人手中,這兩個人互相搶佔另外一個人的鑰匙,導致箱子打不開。