一文精通Java 多線程之全方位解讀
并行和并發(fā)
并行:多個CPU實例或是多臺機器同時執(zhí)行一段處理邏輯,是真正的同時。
并發(fā):一個CPU或一臺機器,通過CPU調(diào)度算法,讓用戶看上去同時去執(zhí)行,實際上從CPU操作層面并不是真正的同時。并發(fā)往往需要公共的資源,對公共資源的處理和線程之間的協(xié)調(diào)是并發(fā)的難點。
線程基礎(chǔ)概念
線程和進程
進程就是程序,有獨立的運行內(nèi)存空間,比如應(yīng)用和后臺服務(wù),windows是一個支持多進程的操作系統(tǒng)。內(nèi)存越大能同時運行的程序越多,在Java里一個進程指的是一個運行在獨立JVM的程序。
線程:一個程序里運行的多個任務(wù),每個任務(wù)就是一個線程,線程是共享內(nèi)存的在QQ、微信、釘釘?shù)溶浖忻總€聊天窗口都是一個線程,可以同時接收消息和發(fā)送消息,但只有一個內(nèi)存占用。
多線程的好處
◆ 同時運行多個任務(wù),提升CPU的使用效率
◆ 共享內(nèi)存,占用資源更少,線程間可以通信
◆ 異步調(diào)用,避免阻塞
◆ 用戶體驗感更好
線程的狀態(tài)
線程包括5種狀態(tài):
1、新建(New):線程對象被創(chuàng)建時,它只會短暫地處于這種狀態(tài)。此時它已經(jīng)分配了必須的系統(tǒng)資源,并執(zhí)行了初始化。例如,Thread thread = new Thread()。
2、就緒(Runnable):稱為“可執(zhí)行狀態(tài)”。線程對象被創(chuàng)建后,其它線程調(diào)用了該對象的start()方法,從而來啟動該線程。例如,thread.start()。處于就緒狀態(tài)的線程,隨時可能被CPU調(diào)度執(zhí)行。
3、運行(Running):線程獲取CPU權(quán)限進行執(zhí)行。注意:線程只能從就緒狀態(tài)進入運行狀態(tài)。
4、阻塞(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán),暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)。阻塞的情況分為三種:
(1)等待阻塞:通過調(diào)用線程的wait()方法,讓線程等待某工作的完成。
(2)同步阻塞:線程在獲取synchronized同步鎖失?。ㄒ驗殒i被其他線程占用),它會進入同步阻塞狀態(tài)。
(3)其他阻塞:通過調(diào)用線程的sleep()或發(fā)出了I/O請求時,線程會進入到阻塞狀態(tài)。當sleep()狀態(tài)超時、join()等待線程終止或是超時?;蚴荌/O處理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。
5.死亡(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。

實現(xiàn)多線程的兩種方式
繼承Thread類
第一步 繼承Therad父類
第二步 重寫run方法
第三步 實例化線程類
第四步 啟動線程
public class ThreadDemo extends Thread{
/**
* 繼承Thread
*
* @author gavin
*
*/
@Override
public void run() {
// TODO 自動生成的方法存根
//打印10次世界
try {
for(int i=0;i<10;i++) {
System.out.print("世界");
Thread.sleep(200);//阻塞態(tài) 休息200毫秒
}
} catch (InterruptedException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
System.out.println("線程執(zhí)行完成!");
}
public static void main(String[] args) {
//初始化線程t1 t2
ThreadDemo t1 = new ThreadDemo();
ThreadDemo t2 = new ThreadDemo();
//啟動線程
t1.start();
t2.start();
//注意不是調(diào)用run方法 而是啟動線程 調(diào)用run方法只是執(zhí)行了run方法但不是多線程執(zhí)行
//打印200次你好
for(int i = 0;i<20;i++) {
System.out.print("你好");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
}
//結(jié)束態(tài)
System.out.println("程序運行完成");
}
}


注意:多線程每次運行得到的結(jié)果是不相同的
Thread.sleep();方法可以使線程阻塞
調(diào)用start();方法才能啟動多線程
調(diào)用run方法和普通方法效果相同
實現(xiàn)Runnable接口
第一步 實現(xiàn)Runnable接口
第二步 重寫run方法
第三步 實例化當前類(任務(wù)類)
第四步 將任務(wù)類對象作為參數(shù)傳入Thread中進行實列化
第五步 啟動多線程
/**
* 實現(xiàn)二: 實現(xiàn)Runnable接口
* 當類本身有父類的時候 使用實現(xiàn)Runnable接口
* @author gavin
*
*/
public class RunnableDemo implements Runnable {
@Override
public void run() {
// TODO 自動生成的方法存根
//打印10次世界
try {
for(int i=0;i<10;i++) {
System.out.print(Thread.currentThread().getName()+"世界\t");
if(i%3==0) {
System.out.println();
}
Thread.sleep(200);//阻塞態(tài) 休息200毫秒
}
} catch (InterruptedException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"線程執(zhí)行完成!");
}
public static void main(String[] args) {
//實列化當前類
RunnableDemo runnableDemo1 = new RunnableDemo();
//創(chuàng)建線程 傳入任務(wù)類
Thread t1 = new Thread(runnableDemo1);
Thread t2 = new Thread(runnableDemo1);
Thread t3 = new Thread(runnableDemo1);
//設(shè)置線程優(yōu)先級
t1.setPriority(Thread.MAX_PRIORITY);//最高優(yōu)先級
t2.setPriority(Thread.MIN_PRIORITY);//最低優(yōu)先級
t3.setPriority(5);//設(shè)置默認優(yōu)先級 優(yōu)先級為5 優(yōu)先級從0-10最高為10
//啟動線程
t1.start();
t2.start();
t3.start();
}
}


注意 setPriority 可以設(shè)置線程的優(yōu)先級
但并不代表線程一定優(yōu)先執(zhí)行完
線程的安全性和原子性
由于線程之間可以共享內(nèi)存,則某個對象(變量)是可以被多個線程共享的,是可以被多個線程同時訪問的。當多個線程訪問某個類時,不管運行時環(huán)境采用何種調(diào)度方式或者這些進程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個類都能表現(xiàn)出正確的行為,那么就稱這個類是線程安全的。
舉個栗子:甲乙兩個人,甲負責向筐里放蘋果,乙負責從筐里數(shù)蘋果,甲乙同時進行,問乙如何操作才能正確?
不幸的是,以上代碼不是線程安全的,因為count++并非是原子操作,實際上,它包含了三個獨立的操作:讀取count的值,將值加1,然后將計算結(jié)果寫入count。如果線程A讀到count為10,馬上線程B讀到count也為10,線程A加1寫入后為11,線程B由于已經(jīng)讀過count值為10,執(zhí)行加1寫入后依然為11,這樣就丟失了一次計數(shù)。在并發(fā)編程中,這種由于不恰當?shù)膱?zhí)行時序而出現(xiàn)不正確的結(jié)果是一種非常重要的情況,它有一個正式的名字:
競態(tài)條件(Race Condition)。
Java 內(nèi)存模型中的可見性、原子性和有序性。
可見性:當多個線程訪問同一個變量x時,線程1修改了變量x的值,線程1、線程2…線程n能夠立即讀
取到線程1修改后的值。
有序性:即程序執(zhí)行時按照代碼書寫的先后順序執(zhí)行。在Java內(nèi)存模型中,允許編譯器和處理器對指
令進行重排序,但是重排序過程不會影響到單線程程序的執(zhí)行,卻會影響到多線程并發(fā)執(zhí)行的正確性。
原子性:原子性通常指多個操作不存在只執(zhí)行一部分的情況,要么全部執(zhí)行、要么全部不執(zhí)行。
鎖的概念和使用
竟態(tài)條件會使運行結(jié)果變得不可靠,程序的運行結(jié)果取決于方法的調(diào)用順序,將方法以串行的
方式來訪問,我們稱這種方式為同步鎖(synchronized)。
Java實現(xiàn)同步鎖的方式有:
▶同步方法synchronized method
▶ 同步代碼塊 synchronized(Lock)
▶ 等待與喚醒 wait 和 notify
▶ 使用特殊域變量(volatile)實現(xiàn)線程同步
▶ 使用重入鎖實現(xiàn)線程同步ReentrantLock
▶ 使用局部變量實現(xiàn)線程同步ThreadLocal
synchronized
synchronized是Java 的內(nèi)置鎖機制,是在JVM上的
可以同步代碼塊,也可以同步方法
//同步代碼塊
synchronized(object){
}
//同步方法
public synchronized void method() {
// do something
}
注:同步是一種高開銷的操作,因此應(yīng)該盡量減少同步的內(nèi)容。通常沒有必要同步整個方法。
ReentrantLock
可重入鎖,是一種顯示鎖,在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。
ReentrantLock類是可重入、互斥、實現(xiàn)了Lock接口的鎖,它與使用synchronized方法和快具有相同
的基本行為和語義,并且擴展了其能力。
ReentrantLock() : 創(chuàng)建一個ReentrantLock實例
lock() : 獲得鎖
unlock() : 釋放鎖
可重入: 甲獲得鎖后釋放鎖或鎖失效,乙可繼續(xù)獲得這個鎖
生產(chǎn)消費者模型
生產(chǎn)消費者模型是一個非常典型的多線程并發(fā)處理的模型,在實際的生產(chǎn)應(yīng)用中也有非常廣泛的使用。

生產(chǎn)消費者模型中的類–存儲類
import java.util.LinkedList;
public class Store {
/*
* 存儲 隊列實現(xiàn)
*
* @author gavin
* */
//創(chuàng)建隊列
LinkedList<Integer> list = new LinkedList<Integer>();
//設(shè)置最大存儲值
int max = 10;
//生產(chǎn)者生產(chǎn) 放入隊尾
public void push(int n) {
synchronized(list) {
try {
if(list.size()>=max){
System.out.println("存滿了");
//線程掛起
list.wait();
}else {
//隊列沒有存滿 繼續(xù)存
System.out.println("存入:"+n);
list.add(n);
//放完之后必須有 因此喚醒取的線程
list.notifyAll();
}
}catch (Exception e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
}
}
//消費者消費 從隊頭取出
public int pop() {
try {
synchronized(list){
if(list.size()<=0) {
System.out.println("隊列空了。。。。");
//空了之后就不能取了 因此線程掛起
list.wait();
}else {
//從對頭取出
int n = list.poll();
System.out.println("取出:"+n);
//取出了就一定不會滿 因此要喚醒線程
list.notifyAll();
return n;
}
}
}catch (Exception e) {
// TODO: handle exception
}
return 0;
}
}
生產(chǎn)消費者模型中的類–生產(chǎn)者
/**
* 生產(chǎn)者
*/
public class Producer implements Runnable{
private Store store;
public Producer(Store store) {
// TODO 自動生成的構(gòu)造函數(shù)存根
this.store = store;
}
@Override
public void run() {
// TODO 自動生成的方法存根
try {
//生產(chǎn)需要事件 休息100毫秒再生產(chǎn)
Thread.sleep(100);
//產(chǎn)生隨機數(shù)字
store.push((int)(Math.random()*100));
}catch (Exception e) {
// TODO: handle exception
}
}
}
生產(chǎn)消費者模型中的類–消費者
public class Customer implements Runnable{
private Store store;
public Customer(Store store) {
// TODO 自動生成的構(gòu)造函數(shù)存根
this.store = store;
}
@Override
public void run() {
// TODO 自動生成的方法存根
try {
//消費者消費需要時間 休息200毫秒
Thread.sleep(200);
//從隊頭取出
store.pop();
}catch (Exception e) {
// TODO: handle exception
}
}
}
測試類
package com.qingsu.pcm;
public class TestPcm {
public static void main(String[] args) {
Store store = new Store();
while(true) {
Producer producer = new Producer(store);
Customer customer = new Customer(store);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(customer);
t1.start();
t2.start();
}
}
}
效果

volatile變量
volatile具有可見性、有序性,不具備原子性。
我們了解到synchronized是阻塞式同步,稱為重量級鎖。
而volatile是非阻塞式同步,稱為輕量級鎖。
被volatile修飾的變量能夠保證每個線程能夠獲取該變量的最新值,
從而避免出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象。
線程池的概念和使用
線程的創(chuàng)建是比較消耗內(nèi)存的,所以我們要事先創(chuàng)建若干個可執(zhí)行的線程放進一個“池(容器)”里面,需要的時候就直接從池里面取出來不需要自己創(chuàng)建,使用完畢也不需要銷毀而是放進“池”中,從而減少了創(chuàng)建和銷毀對象所產(chǎn)生的開銷。
ExecutorService:線程池接口
ExecutorService pool(池名稱) = Executors.常用線程池名;
常用線程池:
newsingleThreadExecutor :單個線程的線程池,即線程池中每次只有一個線程在工作,單線程串行執(zhí)行任務(wù)
newfixedThreadExecutor(n):固定數(shù)量的線程池,每提交一個任務(wù)就是一個線程,直到達到線程池的最大數(shù)量,然后在后面等待隊列前面的線程執(zhí)行或者銷毀()。該方式一般會使線程具有一定的執(zhí)行順序
newCacheThreadExecutor:一個可緩存的線程池。當線程池超過了處理任務(wù)所需要的線程數(shù),那么就會回收部分閑置線程(一般是閑置60s)。當有任務(wù)來時而線程不夠時,線程池又會創(chuàng)建新的線程,當線程夠時就調(diào)用池中線程。適
用于大量的耗時較少的線程任務(wù)。
newScheduleThreadExecutor:一個大小無限的線程池,該線程池多用于執(zhí)行延遲任務(wù)或者固定周期的任務(wù)。
示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPcmTwo {
/*
* 利用線程池創(chuàng)建線程
*/
public static void main(String[] args) {
Store store = new Store();
//創(chuàng)建10個線程的線程池
ExecutorService service = Executors.newFixedThreadPool(10);
ExecutorService serviceTwo = Executors.newFixedThreadPool(10);
//創(chuàng)建可緩存的線程池
//可緩存的
//ExecutorService service = Executors.newCachedThreadPool();
//ExecutorService serviceTwo = Executors.newCachedThreadPool();
for(int i =0 ;i<11;i++) {
service.execute(new Producer(store));
}
for(int i =0 ;i<11;i++) {
serviceTwo.execute(new Customer(store));
}
//關(guān)閉線程池
service.shutdown();
serviceTwo.shutdown();
}
}

到此這篇關(guān)于一文精通Java 多線程之全方位解讀的文章就介紹到這了,更多相關(guān)Java 多線程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java如何使用ConfigurationProperties獲取yml中的配置
這篇文章主要介紹了Java如何使用ConfigurationProperties獲取yml中的配置,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
Java實現(xiàn)字符串轉(zhuǎn)為駝峰格式的方法詳解
這篇文章主要介紹了如何利用Java語言實現(xiàn)字符串轉(zhuǎn)為駝峰格式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2022-07-07
創(chuàng)建網(wǎng)關(guān)項目(Spring Cloud Gateway)過程詳解
這篇文章主要介紹了創(chuàng)建網(wǎng)關(guān)項目(Spring Cloud Gateway)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-09-09
springboot根據(jù)實體類生成表的實現(xiàn)方法

