進程:就是一個正在運行的程序,分配內存讓應用程序可以運行。java
Windows系統號稱多任務(能夠同時運行多個應用程序)。安全
宏觀上看:Windows確實是運行多個程序。多線程
微觀上看:CPU快速切換執行任務,因爲速度特別快,咱們人感受不到這個切換的過程。jvm
線程:在一個進程中負責代碼的執行,就是進程中的執行路徑。ide
疑問:沒有主線程,爲何代碼能夠執行?函數
java程序在運行的時候,jvm會幫咱們建立一個主線程來執行代碼。主線程主要負責main方法中的代碼執行。this
一個java程序中至少有2個線程:spa
一個主線程主要負責main方法中的代碼執行,一個垃圾回收器線程,負責垃圾回收。線程
多線程:在一個進程中多個線程同時執行不一樣的任務。code
「同時」:單核CPU快速切換多個線程執行任務
速度特別快,人感受不到切換。
多線程的好處:
1.解決一個進程中同時執行多個任務的問題、
2.提升資源的利用率。
多線程的弊端:
1.增長CPU的負擔。線程不是越多越好。
2.下降了一個進程中線程的執行效率。
3.容易引起線程安全問題。
4.出現死鎖現象。
java中建立線程有2種方式:
線程的定義方式一:Thread (線程類)
1.須要定義一個類來繼承Thread類。
2.重寫thread類中run方法,把自定義線程的任務代碼寫在run方法中。
* 每個線程都有本身的任務代碼,jvm建立的主線程任務代碼就是main方法,
* 自定義的線程的任務代碼就寫在run方法中,自定義的線程就須要執行run方法中的代碼。
3.建立Thread的子類,而且調用start方法開啓線程。
注意點:一旦線程開啓了,會默認執行線程對象中的run方法,可是千萬不要本身直接調用run方法,若是直接調用了run方法,就和普通方法沒有區別。
Java 是單線程,若是一個類已經繼承了別的類,這個時候就不可以經過繼承thread類來建立線程了。
線程的使用細節:
1.線程的啓動使用父類的start()方法
2.若是線程對象直接調用run(),那麼JVm不會看成線程來運行,會認爲是普通的方法調用。
3.線程的啓動只能由一次,不然拋出異常
4.能夠直接建立Thread類的對象並啓動該線程,可是若是沒有重寫run(),什麼也不執行。
5.匿名內部類的線程實現方式
線程的定義方式二:
1.自定義一個類實現Runable接口 , 接口中就會提供一個run方法
2.實現Runable接口中的run方法。將線程中的任務寫在run方法中
3.建立Runable接口的實現類對象
注意點:實現runnable接口的類不是一個實現類,他是沒有能力開啓線程的只有是thread或者他的子類纔是線程類,只有他們纔有開啓線程的能力。
4.建立一個Thread對象,並把Runable實現類建立的對象做爲參數。
5.調用Thread對象的start方法來開啓線程
問題 : 爲何要將Runable接口實現類的對象做爲參數傳遞?
爲了讓對象中的run方法可以在線程中的run方法中執行。也就是可以將對象中的run方法最爲線程中的任務來執行
* 推薦使用 :第二種方式 實現Runable.
* java 是單繼承 ,多實現的
售票的線程方式一(thread):
class SaleTickets extends Thread { static int num = 50; // 總票數 共享的數據,三個窗口同時操做同一份數據 //static Object o = new Object(); public SaleTickets (String name) { super(name); } // 重寫run方法:賣票的任務 public void run() { while (true) { synchronized ("hh") { //任意類型的對象,鎖對象應該是同一個對象 try { Thread.sleep(500); } catch (InterruptedException e) { // TODO: handle exception e.printStackTrace(); } if(num > 0) { System.out.println(this.getName() + "賣了第" + num + "票"); num--; } else { System.out.println("票已售完"); break; } } } } } public class Demo4 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub SaleTickets t1 = new SaleTickets("窗口1"); SaleTickets t2 = new SaleTickets("窗口2"); SaleTickets t3 = new SaleTickets("窗口3"); t1.start(); t2.start(); t3.start(); } }
售票的線程方式二(Runnable):
class SaleTickets1 implements Runnable { static int num = 50; public void run() { while (true) { synchronized ("djj") { if (num > 0) { System.out.println(Thread.currentThread().getName() + "賣出第" + num + "張票"); num--; } else { System.out.println("票已售盡"); break; } } } } } public class Demo8 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //建立實現類對象 SaleTickets1 st = new SaleTickets1(); // 建立三個線程對象 Thread t = new Thread(st, "窗口1"); Thread t1 = new Thread(st, "窗口2"); Thread t2 = new Thread(st, "窗口3"); t.start(); t1.start(); t2.start(); } }
java給線程加鎖 :
解決線程安全問題的手段:
經過同步代碼的方式:
方式一:同步代碼塊
鎖對象能夠是任意一個java中的對象
java中的任意一個對象都會有一個對象的狀態 ,就能夠經過對象的狀態來做爲鎖的一個標識符。
state = 0 表示鎖是關閉 state = 1 表示鎖打開。
synchronized (鎖對象) {
}
同步代碼塊的使用注意點 :
1.任意一個對象均可以作鎖對象
2.若是你在同步代碼塊中調用了sleep方法 ,不會釋放鎖對象
3.只有真正存在線程安全的時候才須要使用同步代碼塊,不然會下降執行效率
4.多線程操做鎖對象必須是惟一的 ,不然無效
方式二:同步函數
寫法:用synchronize來修飾方法。
若是同步函數是一個非靜態的函數:鎖對象就是調用者對象。
若是同步函數是一個靜態的函數:鎖對象同步函數是所在類的字節碼(Class對象)
同步函數和同步代碼塊的區別:
1.同步代碼塊,能夠隨意肯定同步代碼的範圍,同步函數只能同步整個函數的代碼。
2.同步代碼塊的鎖對象能夠本身任意指定,同步函數的鎖對象是固定的。
不知道何時安全何時不安全。
出現線程安全的問題根本緣由:
1.存在兩個或兩個以上的線程。而且線程之間共享着一個資源。
2.多個語句操做了共享資源
線程死鎖:
可是若是使用不當會致使線程死鎖問題:
A線程等B線程完成, B線程又在等A線程 結果兩我的就一直等下去了 ,這個時候就形成了線程死鎖。
線程死鎖不必定會出現,有可能會出現。
死鎖現象的解決方案 : 沒有方案 ,儘可能避免發生。
實例:
class DeadLock extends Thread { public DeadLock(String name){ super(name); } @Override public void run() { if("張三".equals(Thread.currentThread().getName())){ synchronized ("遙控器") { //鎖對象就鎖住了 System.out.println("張三拿到了遙控器 ,準備去拿電池"); synchronized ("電池") {//已經被鎖住了 System.out.println("張三拿到了遙控器和電池,開着空調,在也 不冷了"); } } }else if("老王".equals(Thread.currentThread().getName())){ synchronized ("電池") { //鎖也被鎖住了 電池對象的狀態 變爲鎖住的狀態 System.out.println("老王拿到電池,準備去拿遙控器"); synchronized ("遙控器") { //遙控器也被鎖住了 System.out.println("老王拿到了遙控器和電池,開着空調,在也 不冷了"); } } } } } public class Demo6 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub DeadLock t1 = new DeadLock("張三"); t1.start(); DeadLock t2 = new DeadLock("老王"); t2.start();
線程的通信:
線程的通信:一個線程完成本身的任務,去通知另一個線程去完成另一個任務。
wait(); 等待 若是線程執行了wait方法 ,那麼該線程就會處於一個等待狀態,等待狀態的線程必需要經過其餘線程來調用
notify()方法來喚醒。
notify();喚醒 隨機喚醒線程池中的一個線程。
notifyAll(); 喚醒全部等待的線程。
wait和notify的使用注意點 :
1.wait方法和notify方法是屬性Object對象
2.wait方法和notify方法必須在同步線程中執行
3.wait方法和notify方法必須有鎖對象來調用
消費者和生產者之間的通信:
class Product { String name; double price; } class Producter extends Thread{ //生產產品 Product p; public Producter(Product p){ this.p = p; } @Override public void run() { // TODO Auto-generated method stub for(int i = 0;i<50;i++){ synchronized (p) { if(i % 2 == 0){ p.name = "蘋果"; p.price = 3.5; System.out.println(p.name+":"+p.price); }else { p.name = "香蕉"; p.price = 2.0; System.out.println(p.name+":"+p.price); } //產品以經生產出來 喚醒消費者來消費 p.notify(); try { p.wait(); // 等待消費者提醒 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } class Custem extends Thread{ Product p; // 消費者消費的產品 public Custem(Product p){ this.p = p; } @Override public void run() { // TODO Auto-generated method stub for (int i = 0;i<50;i++){ synchronized (p) { //經過鎖對象來調用 if(p.name != null){ System.out.println("吃"+p.name+":"+p.price); } // 沒有產品 告訴生產者須要生產產品 //喚醒生產者 p.notify(); try { p.wait(); //等待產品建立 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } public class Demo9 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //建立一個產品對象 Product p = new Product(); //建立一個生產者 Producter t1 = new Producter(p); //建立一個消費者 Custem t2 = new Custem(p); t1.start(); t2.start(); } }