1、程序(program):是為完成特定任務、用某種語言編寫的一組指令的集合。即指一 段靜態的代碼,靜態對象。
2、進程(process):是程序的一次執行過程,或是正在運行的一個程序。是一個動態的過程:有它自身的產生、存在和消亡的過程。—生命周期
1)進程作為資源分配的單位,系統在運行時會為每個進程分配不同的內存區域
3、線程(thread),進程可進一步細化為線程,是一個程序內部的一條執行路徑。
1)若一個進程同一時間並行執行多個線程,就是支持多線程的
2)線程作為調度和執行的單位,每個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷小
3)一個進程中的多個線程共享相同的內存單元/內存地址空間它們從同一堆中分配對象,可以 訪問相同的變量和對象。這就使得線程間通信更簡便、高效。但多個線程操作共享的系統資源可能就會帶來安全的隱患。

一個Java應用程序java.exe,其實至少有三個線程:main()主線程,gc() 垃圾回收線程,異常處理線程。當然如果發生異常,會影響主線程。
並行與並發:
1) 並行:多個CPU同時執行多個任務。比如:多個人同時做不同的事。
2) 並發:一個CPU(采用時間片)同時執行多個任務。比如:秒殺、多個人做同一件事。
多線程程序的優點:
1) 提高應用程序的響應。對圖形化界面更有意義,可增強用戶體驗。
2) 提高計算機系統CPU的利用率
3) 改善程序結構。將既長又復雜的進程分為多個線程,獨立運行,利於理解和修改
何時需要多線程
1)程序需要同時執行兩個或多個任務。
2)程序需要實現一些需要等待的任務時,如用戶輸入、文件讀寫操作、網絡操作、搜索等。
3)需要一些后台運行的程序時。
線程的創建和啟動
Java語言的JVM允許程序運行多個線程,它通過java.lang.Thread 類來體現。
1)每個線程都是通過某個特定Thread對象的run()方法來完成操作的,經常把run()方法的主體稱為線程體。
2)通過該Thread對象的start()方法來啟動這個線程,而非直接調用run()。
Thread類構造器
1)Thread():創建新的Thread對象;
2)Thread(String threadname):創建線程並指定線程實例名;
3)Thread(Runnable target):指定創建線程的目標對象,它實現了Runnable接 口中的run方法;
4)Thread(Runnable target, String name):創建新的Thread對象。
API中創建線程的兩種方式
JDK1.5之前創建新執行線程有兩種方法:
1)繼承Thread類的方式;
2)實現Runnable接口的方式;
1):繼承Thread類
1) 定義子類繼承Thread類。 2) 子類中重寫Thread類中的run方法。 3) 創建Thread子類對象,即創建了線程對象。 4) 調用線程對象start方法:啟動線程,調用run方法。
package com.thread.test; public class ThreadTest { public static void main(String[] args) { //3 創建Thread子類對象,即創建了線程對象。 MyThread m=new MyThread(); //4 調用線程對象start方法:啟動線程,調用run方法。 m.start(); } } //1 定義子類繼承Thread類。 class MyThread extends Thread{ //2 子類中重寫Thread類中的run方法。 @Override public void run(){ for (int i = 0; i <100 ; i++) { System.out.println("子線程"+i); } } }
2):實現Runnable接口
1) 定義子類,實現Runnable接口。 2) 子類中重寫Runnable接口中的run方法。 3) 通過Thread類含參構造器創建線程對象。 4) 將Runnable接口的子類對象作為實際參數傳遞給Thread類的構造器中。 5) 調用Thread類的start方法:開啟線程,調用Runnable子類接口的run方法。
package com.thread.test; public class ThreadTest { public static void main(String[] args) { // 3) 通過Thread類含參構造器創建線程對象。------------------------------創建實現類的對象 // 4) 將Runnable接口的子類對象作為實際參數傳遞給Thread類的構造器中。-------將此對象作為參數傳遞Thread類構造器中,創建Thread類的對象 // 5) 調用Thread類的start方法:開啟線程,調用Runnable子類接口的run方法。---通過Thread類的對象調用start(); new Thread(new MyThread2()).start(); } } // 1) 定義子類,實現Runnable接口。 class MyThread2 implements Runnable{ //2) 子類中重寫Runnable接口中的run方法。 @Override public void run() { for (int i = 0; i <100 ; i++) { System.out.println("子線程"+i); } } }
Thread類的有關方法
void start(): 啟動線程,並執行對象的run()方法 run(): 線程在被調度時執行的操作 String getName(): 返回線程的名稱 void setName(String name):設置該線程名稱 static Thread currentThread(): 返回當前線程。在Thread子類中就是this,通常用於主線程和Runnable實現類 static void yield():線程讓步 暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程 若隊列中沒有同優先級的線程,忽略此方法 join() :當某個程序執行流中調用其他線程的 join() 方法時,調用線程將 被阻塞,直到 join() 方法加入的 join 線程執行完為止 低優先級的線程也可以獲得執行 static void sleep(long millis):(指定時間:毫秒) 令當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執行,時間到后 重排隊。 拋出InterruptedException異常 stop(): 強制線程生命期結束,不推薦使用 boolean isAlive():返回boolean,判斷線程是否還活着
線程的調度
1)調度策略

2)調度方法
1.同優先級線程組成先進先出隊列(先到先服務),使用時間片策略
2.對高優先級,使用優先調度的搶占式策略
3)線程的優先級
1.等級,方法
MAX_PRIORITY:10 MIN _PRIORITY:1 NORM_PRIORITY:5
getPriority() :返回線程優先值
setPriority(int newPriority) :改變線程的優先級
線程創建時繼承父線程的優先級
低優先級只是獲得調度的概率低,並非一定是在高優先級線程之后才被調用
Java中的線程分為兩類:一種是守護線程,一種是用戶線程。
守護線程是用來服務用戶線程的,通過在start()方法前調用 thread.setDaemon(true)可以把一個用戶線程變成一個守護線程。
Java垃圾回收就是一個典型的守護線程。
線程的生命周期


多線程出現了安全問題
當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有 執行完,另一個線程參與進來執行。導致共享數據的錯誤。
對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以 參與執行。
線程的同步:Synchronized同步機制

同步機制中的鎖:
在《Thinking in Java》中,是這么說的:對於並發工作,你需要某種方式來防 止兩個任務訪問相同的資源(其實就是共享資源競爭)。 防止這種沖突的方法 就是當資源被一個任務使用時,在其上加鎖。第一個訪問某項資源的任務必須 鎖定這項資源,使其他任務在其被解鎖之前,就無法訪問它了,而在其被解鎖 之時,另一個任務就可以鎖定並使用它了。
synchronized的鎖是什么?
任意對象都可以作為同步鎖。
所有對象都自動含有單一的鎖(監視器)。
同步方法的鎖:靜態方法(類名.class)、非靜態方法(this)
同步代碼塊:自己指定,很多時候也是指定為this或類名.class
必須確保使用同一個資源的多個線程共用一把鎖,這個非常重要,否則就 無法保證共享資源的安全
1)同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。一個線程類中的所有靜態方法共用同一把鎖(類名.class),所有非靜態方法共用同一把鎖(this),
2)同步代碼塊(指定需謹慎)。對於實現 Runnable 接口的方法,由於就一個實現類對象,所以可以用this 當鎖。 對於繼承Thread 類的方法,由於有多個子類的實例,所以需要使用 類名.class 當鎖。
同步的方式,解決了線程的安全問題。---好處
操作同步代碼時,只能有一個線程參與,其他線程等待。相當於是一個單線程的過程,效率低。 ---局限性



線程的通信:
* 涉及到的三個方法:
* wait():一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器。
* notify():一旦執行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優先級高的那個。
* notifyAll():一旦執行此方法,就會喚醒所有被wait的線程。
*
* 說明:
* 1.wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中。
* 2.wait(),notify(),notifyAll()三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器。當前線程必須具有對該對象的監控權(加鎖)
* 否則,會出現IllegalMonitorStateException異常
* 3.wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中。
*
* 面試題:sleep() 和 wait()的異同?
* 1.相同點:一旦執行方法,都可以使得當前的線程進入阻塞狀態。
* 2.不同點:1)兩個方法聲明的位置不同:Thread類中聲明sleep() , Object類中聲明wait()
* 2)調用的要求不同:sleep()可以在任何需要的場景下調用。 wait()必須使用在同步代碼塊或同步方法中
* 3)關於是否釋放同步監視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。
package com.company; // * 線程通信的例子:使用兩個線程打印 1-100。線程1, 線程2 交替打印 public class Comm { public static void main(String[] args) { CommThread th=new CommThread(); Thread t1=new Thread(th,"線程1"); Thread t2=new Thread(th,"線程2"); t1.start(); t2.start(); } } class CommThread implements Runnable{ private int count=100; private Object ob=new Object(); @Override public void run() { while (true) { synchronized (ob) { ob.notify(); if(count>0) { System.out.println("xincheng"+Thread.currentThread().getName()+":"+count); count--; try { ob.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } }
* 解決線程安全問題的方式三:Lock鎖 --- JDK5.0新增
*
* 1. 面試題:synchronized 與 Lock的異同?
* 相同:二者都可以解決線程安全問題
* 不同:synchronized機制在執行完相應的同步代碼以后,自動的釋放同步監視器
* Lock需要手動的啟動同步(lock()),同時結束同步也需要手動的實現(unlock())
*
* 2.優先使用順序:
* Lock 同步代碼塊(已經進入了方法體,分配了相應資源) 同步方法(在方法體之外)
*
*
* 面試題:如何解決線程安全問題?有幾種方式

package com.company; import java.util.concurrent.locks.ReentrantLock; public class MainLock { public static void main(String[] args) { Test th=new Test(); Thread t1=new Thread(th,"窗口1"); Thread t2=new Thread(th,"窗口2"); Thread t3=new Thread(th,"窗口3"); t1.start(); t2.start(); t3.start(); } } class Test implements Runnable{ private int ticket=100; ////1.實例化ReentrantLock private ReentrantLock Lock=new ReentrantLock(); @Override public void run() { while (true) { ////2.調用鎖定方法lock() Lock.lock(); try { { if(ticket>0){ System.out.println(Thread.currentThread().getName()+"買票:"+ticket); ticket--; } else { break; } } } finally { // //3.調用解鎖方法:unlock() Lock.unlock(); } } } }
package com.company; /* * 線程通信的應用:經典例題:生產者/消費者問題 * * 生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品, * 店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員 * 會叫生產者停一下,如果店中有空位放產品了再通知生產者繼續生產;如果店中沒有產品 * 了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來取走產品。 * * 分析: * 1. 是否是多線程問題?是,生產者線程,消費者線程 * 2. 是否有共享數據?是,店員(或產品) * 3. 如何解決線程的安全問題?同步機制,有三種方法 * 4. 是否涉及線程的通信?是*/ public class Product { public static void main(String[] args) { Clerk clerk=new Clerk(); Producer p1=new Producer(clerk); Consumer c1=new Consumer(clerk); p1.start(); c1.start(); } } //共享數據可以認為是Clerk或者producecount class Clerk{ private int producecount=0; public synchronized void produceProduct(){ if(producecount<20) { producecount++; System.out.println(Thread.currentThread().getName()+":生產"+producecount); notify(); } else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void comsumProduct(){ if(producecount>0) { producecount--; System.out.println(Thread.currentThread().getName()+":消費"+producecount); 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) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } 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) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } clerk.comsumProduct(); } } }
* 創建線程的方式三:實現Callable接口。 --- JDK 5.0新增
*
*
* 如何理解實現Callable接口的方式創建多線程比實現Runnable接口創建多線程方式強大?
* 1. call()可以有返回值的。
* 2. call()可以拋出異常,被外面的操作捕獲,獲取異常的信息
* 3. Callable是支持泛型的
//1.創建一個實現Callable的實現類 class NumThread implements Callable{ //2.實現call方法,將此線程需要執行的操作聲明在call()中 @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { if(i % 2 == 0){ System.out.println(i); 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的對象作為參數傳遞到Thread類的構造器中,創建Thread對象,並調用start() new Thread(futureTask).start(); try { //6.獲取Callable中call方法的返回值 //get()返回值即為FutureTask構造器參數Callable實現類重寫的call()的返回值。 Object sum = futureTask.get(); System.out.println("總和為:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
* 創建線程的方式四:使用線程池
*
* 好處:
* 1.提高響應速度(減少了創建新線程的時間)
* 2.降低資源消耗(重復利用線程池中線程,不需要每次都創建)
* 3.便於線程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大線程數
* keepAliveTime:線程沒有任務時最多保持多長時間后會終止

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 ThreadPool { public static void main(String[] args) { //1. 提供指定線程數量的線程池 ExecutorService service = Executors.newFixedThreadPool(10); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //設置線程池的屬性 // System.out.println(service.getClass()); // service1.setCorePoolSize(15); // service1.setKeepAliveTime(); //2.執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象 service.execute(new NumberThread());//適合適用於Runnable service.execute(new NumberThread1());//適合適用於Runnable // service.submit(Callable callable);//適合使用於Callable //3.關閉連接池 service.shutdown(); } }
互斥同步
synchronized 和 ReentrantLock。
互斥同步最主要的問題就是線程阻塞和喚醒所帶來的性能問題,因此這種同步也稱為阻塞同步。
互斥同步屬於一種悲觀的並發策略,總是認為只要不去做正確的同步措施,那就肯定會出現問題。無論共享數據是否真的會出現競爭,它都要進行加鎖(這里討論的是概念模型,實際上虛擬機會優化掉很大一部分不必要的加鎖)、用戶態核心態轉換、維護鎖計數器和檢查是否有被阻塞的線程需要喚醒等操作。
隨着硬件指令集的發展,我們可以使用基於沖突檢測的樂觀並發策略:先進行操作,如果沒有其它線程爭用共享數據,那操作就成功了,否則采取補償措施(不斷地重試,直到成功為止)。這種樂觀的並發策略的許多實現都不需要將線程阻塞,因此這種同步操作稱為非阻塞同步。
樂觀鎖需要操作和沖突檢測這兩個步驟具備原子性,這里就不能再使用互斥同步來保證了,只能靠硬件來完成。硬件支持的原子性操作最典型的是:比較並交換(Compare-and-Swap,CAS)。CAS 指令需要有 3 個操作數,分別是內存地址 V、舊的預期值 A 和新值 B。當執行操作時,只有當 V 的值等於 A,才將 V 的值更新為 B。
鎖優化
公平鎖/非公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖。
非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。
對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。
對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。
獨享鎖/共享鎖
獨享鎖是指該鎖一次只能被一個線程所持有。
共享鎖是指該鎖可被多個線程所持有。
對於Java ReentrantLock而言,其是獨享鎖。但是對於Lock的另一個實現類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。
讀鎖的共享鎖可保證並發讀是非常高效的,讀寫,寫讀 ,寫寫的過程是互斥的。
獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。
對於Synchronized而言,當然是獨享鎖。
互斥鎖/讀寫鎖
上面講的獨享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實現。
互斥鎖在Java中的具體實現就是ReentrantLock
讀寫鎖在Java中的具體實現就是ReadWriteLock
樂觀鎖/悲觀鎖
樂觀鎖與悲觀鎖不是指具體的什么類型的鎖,而是指看待並發同步的角度。
悲觀鎖認為對於同一個數據的並發操作,一定是會發生修改的,哪怕沒有修改,也會認為修改。因此對於同一個數據的並發操作,悲觀鎖采取加鎖的形式。悲觀的認為,不加鎖的並發操作一定會出問題。
樂觀鎖則認為對於同一個數據的並發操作,是不會發生修改的。在更新數據的時候,會采用嘗試更新,不斷重新的方式更新數據。樂觀的認為,不加鎖的並發操作是沒有事情的。
從上面的描述我們可以看出,悲觀鎖適合寫操作非常多的場景,樂觀鎖適合讀操作非常多的場景,不加鎖會帶來大量的性能提升。
悲觀鎖在Java中的使用,就是利用各種鎖。
樂觀鎖在Java中的使用,是無鎖編程,常常采用的是CAS算法,典型的例子就是原子類,通過CAS自旋實現原子操作的更新。
分段鎖
分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其並發的實現就是通過分段鎖的形式來實現高效的並發操作。
我們以ConcurrentHashMap來說一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱為Segment,它即類似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當需要put元素的時候,並不是對整個hashmap進行加鎖,而是先通過hashcode來知道他要放在那一個分段中,然后對這個分段進行加鎖,所以當多線程put的時候,只要不是放在一個分段中,就實現了真正的並行的插入。
但是,在統計size的時候,可就是獲取hashmap全局信息的時候,就需要獲取所有的分段鎖才能統計。
分段鎖的設計目的是細化鎖的粒度,當操作不需要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操作。
偏向鎖/輕量級鎖/重量級鎖
這三種鎖是指鎖的狀態,並且是針對Synchronized。在Java 5通過引入鎖升級的機制來實現高效Synchronized。這三種鎖的狀態是通過對象監視器在對象頭中的字段來表明的。
偏向鎖是指一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖。降低獲取鎖的代價。
輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。
重量級鎖是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓其他申請的線程進入阻塞,性能降低。
自旋鎖
互斥同步進入阻塞狀態的開銷都很大,應該盡量避免。在許多應用中,共享數據的鎖定狀態只會持續很短的一段時間。自旋鎖的思想是讓一個線程在請求一個共享數據的鎖時執行忙循環(自旋)一段時間,如果在這段時間內能獲得鎖,就可以避免進入阻塞狀態。
自旋鎖雖然能避免進入阻塞狀態從而減少開銷,但是它需要進行忙循環操作占用 CPU 時間,它只適用於共享數據的鎖定狀態很短的場景。
在 JDK 1.6 中引入了自適應的自旋鎖。自適應意味着自旋的次數不再固定了,而是由前一次在同一個鎖上的自旋次數及鎖的擁有者的狀態來決定。
鎖消除
鎖消除是指對於被檢測出不可能存在競爭的共享數據的鎖進行消除。
鎖消除主要是通過逃逸分析來支持,如果堆上的共享數據不可能逃逸出去被其它線程訪問到,那么就可以把它們當成私有數據對待,也就可以將它們的鎖進行消除。
鎖粗化
如果一系列的連續操作都對同一個對象反復加鎖和解鎖,頻繁的加鎖操作就會導致性能損耗。
如果虛擬機探測到由這樣的一串零碎的操作都對同一個對象加鎖,將會把加鎖的范圍擴展(粗化)到整個操作序列的外部。
