程序(program): 是爲完成特定任務、用某種語言編寫的一組指令的集合。即指一 段靜態的代碼,靜態對象。java
進程(process):是程序的一次執行過程,或是正在運行的一個程序。是一個動態 的過程:有它自身的產生、存在和消亡的過程。——生命週期面試
線程(thread):進程可進一步細化爲線程,是一個程序內部的一條執行路徑。安全
並行:多個CPU同時執行多個任務。好比:多我的同時作不一樣的事服務器
併發:一個CPU(採用時間片)同時執行多個任務。好比:秒殺、多我的作同一件事網絡
在Java之中,若是要想實現多線程的程序,那麼就必須依靠一個線程的主體類(就比如主類的概念同樣,表示的是一個線程的主類),可是這個線程的主體類在定義的時候也須要有一些特殊的要求,這個類能夠繼承Thread類或實現Runnable接口來完成定義。多線程
要想啓動線程必須依靠Thread類的start()方法執行,線程啓動以後會默認調用了run()方法、併發
package com.atguigu.java; /** * @author MD * @create 2020-07-10 16:58 */ // 1. class Mythread extends Thread{ // 2. @Override public void run() { for (int i = 0; i < 50 ; i++) { if (i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":"+i); } } } } public class ThreadTest { public static void main(String[] args) { // 3. Mythread mythread = new Mythread(); //4. mythread.start(); // 這個仍然是在main線程中執行 for (int i = 0; i < 50 ; i++) { if (i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ":"+i); } } } } /* 注意: 1. 若是本身手動調用run()方法,那麼就只是普通方法,沒有啓動多線程模式。 2. run()方法由JVM調用,何時調用,執行的過程控制都有操做系統的CPU調度決定。 3. 想要啓動多線程,必須調用start方法。 4. 一個線程對象只能調用一次start()方法啓動,若是重複調用了,則將拋出以上 的異常「IllegalThreadStateException」 */ // 也可使用匿名內部類簡單的寫 new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":" + i); } } }.start();
/** * 測試Thread中經常使用的方法 * 1. start():啓動當前進程,而且調用當前進程的run()方法 * 2. run():一般須要重寫Thread類中的此方法,將建立的線程要執行的操做聲明在這個方法中 * 3. currentThread(): 靜態方法,返回當前代碼的線程 * 4. getName():獲取當前線程的名字 * 5. setName(): 設置當前線程的名字 * 6. yield(): 釋放當前cpu的執行權 * 7. join(): 在線程A中調用線程B的join(),此時線程A就進入到了阻塞的狀態,直到線程B執行完成,線程A才結束阻塞狀態 * 8. sleep(): 讓當前的線程睡眠指定的時間 * * @author MD * @create 2020-07-10 20:34 */ class Method extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":" + i); if (i % 4 == 0) yield(); } } } public class ThreadMethodTest { public static void main(String[] args) { Method method = new Method(); method.setName("線程一:"); method.start(); // 給主線程命名 Thread.currentThread().setName("主線程:"); for (int i = 0; i < 100; i++) { if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":" + i); if (i == 10) { try { method.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
線程的優先級等級app
方法:ide
注意:工具
mythread.setPriority(Thread.MAX_PRIORITY); mythread.start(); // 設置主線程的優先級,最低 Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
package com.atguigu.java; /** * @author MD * @create 2020-07-10 21:53 */ // 1. 定義子類,實現Runnable接口。 class Mthread implements Runnable{ // 2. 子類中重寫Runnable接口中的run方法。 @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) System.out.println(Thread.currentThread().getName() +":"+i); } } } public class ThreadTest1 { public static void main(String[] args) { // 3. 經過Thread類含參構造器建立線程對象。 Mthread mthread = new Mthread(); // 4. 將Runnable接口的子類對象做爲實際參數傳遞給Thread類的構造器中。 Thread t1 = new Thread(mthread); //5. 調用Thread類的start方法:開啓線程,調用Runnable子類接口的run方法 t1.start(); // 再啓動一個線程 Thread t2 = new Thread(mthread); t2.start(); } }
class MyThread implements Runnable { @Override public void run() { // 線程的主方法 // 線程操做方法 } } MyThread mt = new MyThread(); new Thread(mt).start(); // class MyThread extends Thread { @Override public void run() { // 線程的主方法 // 線程操做方法 } } MyThread mt = new MyThread(); mt.start();
要想實現多線程,必須在主線程中建立新的線程對象。Java語言使用Thread類 及其子類的對象來表示線程,在它的一個完整的生命週期中一般要經歷以下的五 種狀態:
多個線程執行的不肯定性引發執行結果的不穩定
多個線程對帳本的共享,會形成操做的不完整性,會破壞數據
模擬火車站售票程序,開啓三個窗口售票
package com.atguigu.java; /** * @author MD * @create 2020-07-11 9:25 */ class Window implements Runnable{ private int ticket = 100; @Override public void run() { while (true){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket > 0){ System.out.println(Thread.currentThread().getName() +":賣票,票號爲" + ticket); ticket--; }else { break; } } } } public class WindowTest { public static void main(String[] args) { Window w = new Window(); new Thread(w, "窗口1").start(); new Thread(w, "窗口2").start(); new Thread(w, "窗口3").start(); } }
問題的緣由:
解決辦法:
在程序之中就能夠經過兩種方式完成:一種是同步代碼塊,另一種就是同步方法。最後還有新增的Lock鎖
使用synchronized關鍵字定義的代碼塊就稱爲同步代碼塊,可是在進行同步的操做之中必須設置一個要同步的對象,而這個對象應該理解爲當前對象:this,必須保證惟一
//同步代碼塊: synchronized (對象){ // 須要被同步的代碼; } //synchronized還能夠放在方法聲明中,表示整個方法爲同步方法。 //例如: public synchronized void show (String name){ …. }
操做共享數據的代碼,就是須要同步的代碼
共享數據:多個線程共同操做的變量
使用同步代碼塊解決實現Runnable接口的方式的線程安全問題
多個線程要公用一把鎖,這裏用的是this,由於是實現這個接口,後面就new了一個對象
/** * @author MD * @create 2020-07-11 9:25 */ class Window implements Runnable{ private int ticket = 100; @Override public void run() { while (true){ synchronized (this){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket > 0){ System.out.println(Thread.currentThread().getName() +":賣票,票號爲" + ticket); ticket--; }else { break; } } } } } public class WindowTest { public static void main(String[] args) { Window w = new Window(); new Thread(w, "窗口1").start(); new Thread(w, "窗口2").start(); new Thread(w, "窗口3").start(); } }
同步的方式,解決了線程安全的問題
可是操做同步代碼的時候,只能有一個進程參與,其餘的進程等待,至關於一個單線程的過程,這個時候效率低
使用同步代碼塊解決繼承Thread類的方式的線程安全問題
這裏的同步鎖不能再用this了,由於後面new了多個對象
注意聲明爲靜態的
class Window2 extends Thread { // 這裏要注意聲明爲靜態的 private static int ticket = 100; private static Object obj = new Object(); @Override public void run() { while (true) { // 也能夠寫成這樣 // synchronized (Window2.class) synchronized (obj){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket > 0) { System.out.println(Thread.currentThread().getName() + ":賣票,票號爲" + ticket); ticket--; } else { break; } } } } } public class WindowTest2 { public static void main(String[] args) { Window2 w1 = new Window2(); Window2 w2 = new Window2(); Window2 w3 = new Window2(); w1.setName("窗口1"); w1.start(); w2.setName("窗口2"); w2.start(); w3.setName("窗口3"); w3.start(); } }
若是操做共享數據的代碼完整的聲明在一個方法中,能夠將這個方法聲明爲同步的,這個方法就是同步方法
使用同步方法解決實現Runnable接口的線程問題
將方法聲明爲同步就行使用synchronized
package com.atguigu.java; /** * @author MD * @create 2020-07-11 10:32 */ class Window3 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { show(); } } // 直接把這個方法聲明爲這個就能夠了 // 這個同步監視器就是this public synchronized void show(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket > 0) { System.out.println(Thread.currentThread().getName() + ":賣票,票號爲" + ticket); ticket--; } } } public class WindowTest3 { public static void main(String[] args) { Window3 w = new Window3(); new Thread(w, "窗口1").start(); new Thread(w, "窗口2").start(); new Thread(w, "窗口3").start(); } }
使用同步方法解決繼承Thread的線程問題
將方法聲明爲同步使用synchronized,而且該方法爲靜態的使用static
package com.atguigu.java; /** * @author MD * @create 2020-07-11 10:39 */ class Window4 extends Thread { private static int ticket = 100; @Override public void run() { while (true) { show(); } } // 此時的同步鎖是當前的類 private static synchronized void show(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket > 0) { System.out.println(Thread.currentThread().getName() + ":賣票,票號爲" + ticket); ticket--; } } } public class WindowTest4 { public static void main(String[] args) { Window4 w1 = new Window4(); Window4 w2 = new Window4(); Window4 w3 = new Window4(); w1.setName("窗口1"); w1.start(); w2.setName("窗口2"); w2.start(); w3.setName("窗口3"); w3.start(); } }
同步就是指一個線程要等待另一個線程執行完畢纔會繼續執行的一種操做形式,可是若是在一個操做之中都是在互相等着的話,那麼就會出現死鎖問題。
面試題:請問多個線程操做同一資源的時候要考慮到那些,會帶來那些問題?
多個線程訪問同一資源的時候必定要考慮到同步的問題,可是過多的同步會帶來死鎖。
package com.atguigu.java1; /** * @author MD * @create 2020-07-11 11:01 */ public class ThreadTest { public static void main(String[] args) { StringBuilder s1 = new StringBuilder(); StringBuilder s2 = new StringBuilder(); new Thread(){ @Override public void run() { synchronized (s1){ s1.append("a"); s2.append("1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2){ s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start(); new Thread(new Runnable() { @Override public void run() { synchronized (s2) { s1.append("c"); s2.append("3"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1) { s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
class A{ private final ReentrantLock lock = new ReenTrantLock(); public void m(){ lock.lock(); try{ //保證線程安全的代碼; } finally{ lock.unlock(); } } } //注意:若是同步代碼有異常,要將unlock()寫入finally語句塊
面試題:synchronized和Lock的異同
注意:若是是使用繼承Thread的方式,lock鎖得加一個static,這樣才能保證是同一把鎖
package com.atguigu.java1; import java.util.concurrent.locks.ReentrantLock; /** * @author MD * @create 2020-07-11 16:00 */ class Window implements Runnable{ private int ticket = 100; // 1. 實例化ReentrantLock private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try { // 2. 調用鎖定方法lock() lock.lock(); if (ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " :售票,票號爲:" + ticket); ticket--; }else { break; } }finally { //3. 調用解鎖方法 unlock() lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { Window w1 = new Window(); new Thread(w1,"窗口1").start(); new Thread(w1,"窗口2").start(); new Thread(w1,"窗口3").start(); } }
如何解決線程安全問題?有幾種方式?
同步機制,三種方式
使用兩個線程交替打印1-100
package com.atguigu.java2; /** * @author MD * @create 2020-07-11 16:44 */ class Number implements Runnable{ private int number = 1; @Override public void run() { while (true){ synchronized (this){ notify(); if (number <= 100){ System.out.println(Thread.currentThread().getName() + " : "+number); number++; // 使得調用wait()方法的線程進入到了阻塞狀態 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { break; } } } } } public class CommunicationTest { public static void main(String[] args) { Number n = new Number(); new Thread(n,"A").start(); new Thread(n,"B").start(); } }
wait() 與 notify() 和 notifyAll()
注意
package com.atguigu.exer; /** * @author MD * @create 2020-07-11 17:14 */ class Clerk{ private int productCount = 0; // 生產產品 public synchronized void produceProduct() { if (productCount < 20){ productCount++; System.out.println(Thread.currentThread().getName() + ":開始生產第 "+productCount+"個產品"); notify(); }else{ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } // 消費產品 public synchronized void consumeProduct() { if (productCount > 0){ System.out.println(Thread.currentThread().getName() +":開始消費產品第" + productCount+"個產品"); productCount--; notify(); }else{ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } // 生產者 class Producer extends Thread{ private Clerk clerk; public Producer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(getName()+": 開始生成產品"); while (true){ clerk.produceProduct(); } } } // 消費者 class Consumer extends Thread{ private Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(getName()+": 開始消費產品"); while (true){ clerk.consumeProduct(); } } } public class ProdectTest { public static void main(String[] args) { Clerk clerk = new Clerk(); Producer p1 = new Producer(clerk); p1.setName("生產者1"); p1.start(); Consumer c1 = new Consumer(clerk); c1.setName("消費者1"); c1.start(); } }
與使用Runnable相比, Callable功能更強大些
具體步驟以下:
package com.atguigu.java2; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author MD * @create 2020-07-11 17:43 */ // 1. 建立一個實現Callable接口的實現類 class NumThread implements Callable{ //2. 實現call()方法,將此線程須要執行的操做聲明在call()方法中 @Override public Object call() throws Exception { int sum = 0; for (int i = 0; i <= 100; i++) { if (i % 2 == 0){ sum += i; } } return sum; } } public class ThreadNew { public static void main(String[] args) { //3. 建立Callable接口實現類的對象 NumThread numThread = new NumThread(); // 4. 將Callable接口實現類的對象做爲參數傳遞到FutureTask構造器中,建立FutureTask的對象 FutureTask futureTask = new FutureTask(numThread); //5. 將FutureTask的對象做爲參數傳遞到Threa類的構造器中,建立Thread對象,並調用start()方法 new Thread(futureTask).start(); //6. 若是須要返回值就寫下面的get()方法, try { // get()返回值就是FutureTask構造器參數Callable實現重寫call()的返回值 Object sum = futureTask.get(); System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
背景:常常建立和銷燬、使用量特別大的資源,好比並髮狀況下的線程, 對性能影響很大。
思路:提早建立好多個線程,放入線程池中,使用時直接獲取,使用完 放回池中。能夠避免頻繁建立銷燬、實現重複利用。相似生活中的公共交 通工具。
好處:
package com.atguigu.java2; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author MD * @create 2020-07-11 18:06 */ class NumberThread implements Runnable{ @Override public void run() { for (int i = 0; i <= 100; i++) { if (i % 2 == 0){ System.out.println(Thread.currentThread().getName()+ " : "+i ); } } } } class NumberThread1 implements Runnable{ @Override public void run() { for (int i = 0; i <= 100; i++) { if (i % 2 != 0){ System.out.println(Thread.currentThread().getName()+ " : "+i ); } } } } public class ThreadTool { public static void main(String[] args) { // 1. 提供指定線程數量的線程池 ExecutorService service = Executors.newFixedThreadPool(10); // 設置線程池的屬性 //2. 執行指定的線程的操做,須要提供實現Runnable接口或實現Callable接口 // 適合用於Runnable service.execute(new NumberThread()); service.execute(new NumberThread1()); // 適合用於Callable //service.submit(); // 3. 最後關閉 service.shutdown(); } }