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

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


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


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

生產(chǎn)消費(fèi)者模型中的類–存儲(chǔ)類
import java.util.LinkedList;
public class Store {
/*
* 存儲(chǔ) 隊(duì)列實(shí)現(xiàn)
*
* @author gavin
* */
//創(chuàng)建隊(duì)列
LinkedList<Integer> list = new LinkedList<Integer>();
//設(shè)置最大存儲(chǔ)值
int max = 10;
//生產(chǎn)者生產(chǎn) 放入隊(duì)尾
public void push(int n) {
synchronized(list) {
try {
if(list.size()>=max){
System.out.println("存滿了");
//線程掛起
list.wait();
}else {
//隊(duì)列沒有存滿 繼續(xù)存
System.out.println("存入:"+n);
list.add(n);
//放完之后必須有 因此喚醒取的線程
list.notifyAll();
}
}catch (Exception e) {
// TODO 自動(dòng)生成的 catch 塊
e.printStackTrace();
}
}
}
//消費(fèi)者消費(fèi) 從隊(duì)頭取出
public int pop() {
try {
synchronized(list){
if(list.size()<=0) {
System.out.println("隊(duì)列空了。。。。");
//空了之后就不能取了 因此線程掛起
list.wait();
}else {
//從對(duì)頭取出
int n = list.poll();
System.out.println("取出:"+n);
//取出了就一定不會(huì)滿 因此要喚醒線程
list.notifyAll();
return n;
}
}
}catch (Exception e) {
// TODO: handle exception
}
return 0;
}
}
生產(chǎn)消費(fèi)者模型中的類–生產(chǎn)者
/**
* 生產(chǎn)者
*/
public class Producer implements Runnable{
private Store store;
public Producer(Store store) {
// TODO 自動(dòng)生成的構(gòu)造函數(shù)存根
this.store = store;
}
@Override
public void run() {
// TODO 自動(dòng)生成的方法存根
try {
//生產(chǎn)需要事件 休息100毫秒再生產(chǎn)
Thread.sleep(100);
//產(chǎn)生隨機(jī)數(shù)字
store.push((int)(Math.random()*100));
}catch (Exception e) {
// TODO: handle exception
}
}
}
生產(chǎn)消費(fèi)者模型中的類–消費(fèi)者
public class Customer implements Runnable{
private Store store;
public Customer(Store store) {
// TODO 自動(dòng)生成的構(gòu)造函數(shù)存根
this.store = store;
}
@Override
public void run() {
// TODO 自動(dòng)生成的方法存根
try {
//消費(fèi)者消費(fèi)需要時(shí)間 休息200毫秒
Thread.sleep(200);
//從隊(duì)頭取出
store.pop();
}catch (Exception e) {
// TODO: handle exception
}
}
}
測(cè)試類
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是阻塞式同步,稱為重量級(jí)鎖。
而volatile是非阻塞式同步,稱為輕量級(jí)鎖。
被volatile修飾的變量能夠保證每個(gè)線程能夠獲取該變量的最新值,
從而避免出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象。
線程池的概念和使用
線程的創(chuàng)建是比較消耗內(nèi)存的,所以我們要事先創(chuàng)建若干個(gè)可執(zhí)行的線程放進(jìn)一個(gè)“池(容器)”里面,需要的時(shí)候就直接從池里面取出來不需要自己創(chuàng)建,使用完畢也不需要銷毀而是放進(jìn)“池”中,從而減少了創(chuàng)建和銷毀對(duì)象所產(chǎn)生的開銷。
ExecutorService:線程池接口
ExecutorService pool(池名稱) = Executors.常用線程池名;
常用線程池:
newsingleThreadExecutor :?jiǎn)蝹€(gè)線程的線程池,即線程池中每次只有一個(gè)線程在工作,單線程串行執(zhí)行任務(wù)
newfixedThreadExecutor(n):固定數(shù)量的線程池,每提交一個(gè)任務(wù)就是一個(gè)線程,直到達(dá)到線程池的最大數(shù)量,然后在后面等待隊(duì)列前面的線程執(zhí)行或者銷毀()。該方式一般會(huì)使線程具有一定的執(zhí)行順序
newCacheThreadExecutor:一個(gè)可緩存的線程池。當(dāng)線程池超過了處理任務(wù)所需要的線程數(shù),那么就會(huì)回收部分閑置線程(一般是閑置60s)。當(dāng)有任務(wù)來時(shí)而線程不夠時(shí),線程池又會(huì)創(chuàng)建新的線程,當(dāng)線程夠時(shí)就調(diào)用池中線程。適
用于大量的耗時(shí)較少的線程任務(wù)。
newScheduleThreadExecutor:一個(gè)大小無限的線程池,該線程池多用于執(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個(gè)線程的線程池
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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java如何使用ConfigurationProperties獲取yml中的配置
這篇文章主要介紹了Java如何使用ConfigurationProperties獲取yml中的配置,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
Java實(shí)現(xiàn)字符串轉(zhuǎn)為駝峰格式的方法詳解
這篇文章主要介紹了如何利用Java語言實(shí)現(xiàn)字符串轉(zhuǎn)為駝峰格式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-07-07
創(chuàng)建網(wǎng)關(guān)項(xiàng)目(Spring Cloud Gateway)過程詳解
這篇文章主要介紹了創(chuàng)建網(wǎng)關(guān)項(xiàng)目(Spring Cloud Gateway)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
Java方法重寫_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Java進(jìn)階學(xué)習(xí):jar打包詳解
springboot根據(jù)實(shí)體類生成表的實(shí)現(xiàn)方法

