Java JUC&多線程 基礎完整版 目錄Java JUC&多線程 基礎完整版1、 多線程的第一種啟動方式之繼承Thread類2、多線程的第二種啟動方式之實現Runnable介面3、多線程的第三種實現方式之實現Callable介面4、多線的常用成員方法5、線程的優先順序6、守護線程7、線程的讓出8、線 ...
Java JUC&多線程 基礎完整版
目錄1、 多線程的第一種啟動方式之繼承Thread類
優點: 比較簡單,可以直接使用Thread類中的方法,缺點: 可以拓展性比較差,不能再繼承其他的類
線程類MyThread
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "Hello World");
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多線程第一種啟動方式
* 1. 自己定義一個類繼承Thread
* 2. 重寫run方法
* 3. 創建啟動對象,並啟動線程
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("線程1");
t2.setName("線程2");
t1.start();
t2.start();
}
}
2、多線程的第二種啟動方式之實現Runnable介面
優點: 拓展性強,實現該介面的同時可以繼承其他的類,缺點: 相對複雜,不能直接使用Thread類中的方法
線程類MyRunnable
public class MyRunnable implements Runnable {
@Override
public void run() {
// 線程要執行的代碼
for (int i = 0; i < 100; i++) {
// 先獲取當前線程的對象
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "Hello World");
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多線程的第二種啟動方式
* 1. 自定義一個類實現Runnable介面
* 2. 重寫裡面的run方法
* 3. 創建自己的類對象
* 4.創建一個Thread類的對象,並開啟線程
*
*/
// 任務對象
MyRunnable myRun = new MyRunnable();
// 線程對象
Thread t1 = new Thread(myRun);
Thread t2 = new Thread(myRun);
// 給線程設置名字
t1.setName("線程1");
t2.setName("線程2");
// 開啟線程
t1.start();
t2.start();
}
}
3、多線程的第三種實現方式之實現Callable介面
優點: 拓展性強,實現該介面的同時可以繼承其他的類,相對複雜,不能直接使用Thread類中的方法
線程類MyCallable
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 求1~100之間的和
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多線程的第三種實現方式
* 特點: 可以獲取到多線程的運行結果
* 1. 創建一個MyCallable類實現Callable介面
* 2. 重寫call (是有返回值的,表示多線程運行的結果)
* 3. 創建MyCallable的對象 (表示多線程要執行的任務)
* 4. 創建FutureTask的對象 (管理多線程運行的結果)
* 5. 創建Thread類的對象 並啟動(表示線程)
*/
// 創建MyCallable對象 (表示要執行的多線程的任務)
MyCallable mc = new MyCallable();
// 創建FutureTask的對象 (作用管理多線程運行的結果)
FutureTask<Integer> ft = new FutureTask<>(mc);
// 創建線程對象
Thread t1 = new Thread(ft);
// 啟動線程
t1.start();
// 獲取多線程運行的結果
Integer result = ft.get();
System.out.println(result);
}
}
4、多線的常用成員方法
- String getName() 返回此線程的名稱
- void setName(String name) 設置線程名稱(構造方法也可以設置名稱)
- 細節
- 1.如果不設置線程名稱,線程也是有預設序號的,從0開始格式為Thread-X
- 2.如果給線程設置名稱可以使用setName和子類的構造方法
- 細節
- 當jvm虛擬機啟動後會自動啟動多條線程,其中就有一個線程名字為main他的作用就是調用main方法,執行裡面的代碼
- 細節
- static void sleep(long time) 讓線程休眠指定的時間, 單位為毫秒
- 細節
- 哪條線程執行到了這個方法,那麼哪條線程就會在這裡停留相對於的時間方法的參數就表示睡眠的時間,單位為毫秒時間到了後線程會自動蘇醒,並繼續執行
- 細節
線程類MyThread
public class MyThread extends Thread {
// 構造方式設置線程名稱
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
// 1.創建線程的對象
MyThread t1 = new MyThread("飛機");
MyThread t2 = new MyThread("坦克");
// 2.開啟線程
t1.start();
t2.start();
// 哪條線程執行到這個方法,此時獲取的就是哪條線程的對象
Thread thread = Thread.currentThread();
System.out.println("main方法線程" + thread.getName());
}
}
5、線程的優先順序
- setPriority(int newPriority) 設置線程優先順序
- final int getPriority() 獲取線程優先順序
線程類MyRunnable
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "===" + i);
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* setPriority(int newPriority) 設置線程優先順序
* final int getPriority() 獲取線程優先順序
*
*/
// 創建線程要執行的參數對象
MyRunnable mr = new MyRunnable();
// 創建線程對象
Thread t1 = new Thread(mr, "飛機");
Thread t2 = new Thread(mr, "坦克");
// 設置優先順序
t1.setPriority(1);
t2.setPriority(10);
// 查看線程優先順序
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
// 啟動線程
t1.start();
t2.start();
}
}
6、守護線程
final void setDaemon(boolean on) 設置為守護線程(備胎線程),當其他的非守護線程執行結束後,守護線程會陸續結束,當非守護線程結束後,守護線程就沒有存在的必要了。
線程類MyThread1
,MyThread2
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("女神");
t2.setName("備胎");
// 線程2設置為守護線程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
7、線程的讓出
public static void yield() 出讓線程/禮讓線程,讓出當前執行線程CPU的執行權
線程類MyThread
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
// 出讓當前CPU的執行權
Thread.yield();
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("飛機");
t2.setName("坦克");
t1.start();
t2.start();
}
}
8、線程插隊
public final void join() 插入線程/插隊線程,講指定的線程插入到main(當前線程)之前執行
線程類MyThread
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.setName("土豆");
t1.start();
// 將t1(土豆) 線程插入到main線程(當前線程)之前
t1.join();
// 執行在main線程中
for (int i = 0; i < 10; i++) {
System.out.println("main線程" + i);
}
}
}
9、同步代碼塊
在需要同步的代碼塊中加入synchronized(當前類位元組碼文件)
線程類MyThread
public class MyThread extends Thread {
/**
* 票號,所有的類都共用該票號
*/
static int ticket = 0;
@Override
public void run() {
while (true) {
// 使用同步代碼塊枷鎖,鎖對象必須是唯一的才行(使用當前類位元組碼)
synchronized (MyThread.class) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (ticket < 100) {
ticket++;
System.out.println(getName() + "正在賣第" + ticket + "張票!");
} else {
break;
}
}
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求
* 某電影院有100張票,只有三個買票視窗,使用多線程設計一個模擬程式來賣票
* 利用同步代碼塊來完成
*/
// 創建線程對象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
// 起名字
t1.setName("視窗1");
t2.setName("視窗2");
t3.setName("視窗3");
// 開啟線程
t1.start();
t2.start();
t3.start();
}
}
10、同步方法
將需要同步的代碼抽取為一個方法,並使用synchronized進行修飾
線程類MyRunnable
public class MyRunnable implements Runnable {
int ticket = 0;
@Override
public void run() {
// 1.寫迴圈
while (true) {
// 2.同步代碼塊(同步方法)
if (method()) {
break;
}
}
}
// 3.同步方法(從同步代碼塊中抽取出來)
private synchronized boolean method() {
// 4.共用代碼是否到了末尾
if (ticket == 1000) {
return true;
} else {
ticket++;
System.out.println(Thread.currentThread().getName() + "在賣第" + ticket + "張票");
}
return false;
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求
* 某電影院有100張票,只有三個買票視窗,使用多線程設計一個模擬程式來賣票
* 利用同步方法來完成
*/
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("視窗1");
t2.setName("視窗2");
t3.setName("視窗3");
t1.start();
t2.start();
t3.start();
}
}
11、線程鎖
lock() 配合使用可以達到synchronized相同操作
線程類MyThread
public class MyThread extends Thread {
static int ticket = 0;
// 只有一把鎖
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 2.同步代碼塊
// synchronized (MyThread.class) {
lock.lock(); // 加鎖
// 3.判斷
try {
if (ticket == 100) {
break;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "在賣第" + ticket + "張票");
}
}finally {
lock.unlock(); // 釋放鎖
}
// }
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:
* 某電影院目前正在上映國產電影,共有100張票,3個視窗
* 使用JDK的lock實現
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("視窗1");
t2.setName("視窗2");
t3.setName("視窗3");
t1.start();
t2.start();
t3.start();
}
}
12、死鎖問題
死鎖原因常見於鎖的嵌套等操作,導致相互獲取不到資源產生的等待問題
線程類MyThread
public class MyThread extends Thread{
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
// 1.迴圈
while (true){
if ("線程A".equals(getName())){
synchronized (objA){
System.out.println("線程A拿到了A鎖,準備拿B鎖");
synchronized (objB){
System.out.println("線程A拿到了B");
}
}
}else if ("線程B".equals(getName())){
if ("線程B".equals(getName())){
synchronized (objB){
System.out.println("線程B拿到了B鎖,準備拿A鎖");
synchronized (objA){
System.out.println("線程B拿到了A鎖,順利執行完了一輪");
}
}
}
}
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:死鎖
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("線程A");
t2.setName("線程B");
t1.start();
t2.start();
}
}
13、等待喚醒機制(消費者模式)
生產者(Cook) => 中間者(Desk) <= 消費者(Foodie)
線程類Cook
,Foodie
/**
* 廚師
*/
public class Cook extends Thread {
/**
* 1.迴圈
* 2.同步代碼塊
* 3.判斷共用數據是否到末尾(到了)
* 4.判斷共用數據是否到末尾(沒有,執行核心邏輯)
*/
@Override
public void run() {
// 1.迴圈
while (true) {
// 2.同步代碼塊
synchronized (Desk.lock) {
// 3.判斷共用數據是否到末尾(到了)
if (Desk.count == 0) {
break;
} else {
// 4.判斷共用數據是否到末尾(沒有,執行核心邏輯)
// 判斷桌子上是否有食物
if (Desk.foodFlag == 1) {
// 如果有 就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
// 如果沒有 就製作
System.out.println("廚師做了一碗麵條");
// 修改桌子上食物狀態
Desk.foodFlag = 1;
// 叫醒等待的消費者開吃
Desk.lock.notifyAll();
}
}
}
}
}
}
/**
* 客人
*/
public class Foodie extends Thread {
/**
* 1.迴圈
* 2.同步代碼塊
* 3.判斷共用數據是否到末尾(到了)
* 4.判斷共用數據是否到末尾(沒有,執行核心邏輯)
*/
@Override
public void run() {
// 1.迴圈
while (true) {
// 2.同步代碼塊
synchronized (Desk.lock) {
// 3.判斷共用數據是否到了末尾(到了末尾,線程執行完畢)
if (Desk.count == 0) {
break;
} else {
// 4.判斷共用數據是否到了末尾(沒有到末尾,執行核心邏輯)
// 先判斷桌子上是否有麵條
if (Desk.foodFlag == 0) {
// 沒有就等待
try {
Desk.lock.wait(); // 讓當前線程跟鎖進行綁定
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 把吃的總數減一
Desk.count--;
// 有就開吃
System.out.println("吃貨正在吃麵條,還能吃" + Desk.count + "碗!");
// 吃完後喚醒廚師繼續製作
Desk.lock.notifyAll();
// 修改桌子狀態
Desk.foodFlag = 0;
}
}
}
}
}
}
/**
* 中間者 桌子
*/
public class Desk {
/*
* 控制生產者和消費者的執行
*/
/**
* 桌子上是否有麵條 0:沒有 1:有
*/
public static int foodFlag = 0;
/**
* 總個數,最多能吃10碗
*/
public static int count = 10;
// 鎖對象
public static Object lock = new Object();
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:完成生產者和消費者(等待喚醒機制)的代碼
* 實現線程輪流交替的執行效果
*
* 生產者 ===> 中間者 <=== 消費者
*
*/
// 創建線程對象
Cook cook = new Cook();
Foodie foodie = new Foodie();
cook.setName("廚師");
foodie.setName("客人");
// 開啟線程
cook.start();
foodie.start();
}
}
14、阻塞隊列下的等待喚醒機制
生產者和消費者必須使用同一個隊列ArrayBlockingQueue
生產者 => ArrayBlockingQueue <= 消費者
線程類Cook
,Foodie
/**
* 廚師
*/
public class Cook extends Thread {
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
// 不斷的把麵條放入阻塞隊列中
try {
// put方法底層實現了鎖操作,所以無需加鎖
queue.put("麵條");
System.out.println("廚師放了一碗麵條");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 客人
*/
public class Foodie extends Thread {
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
// 不斷的從阻塞隊列中獲取麵條
// peek方法底層實現了鎖操作,所以無需加鎖
String food = null;
try {
food = queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("客戶吃了 " + food);
}
}
}
執行類ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需要:利用阻塞隊列完成生產者和消費者(等待喚醒機制)的代碼
* 細節:
* 生產者和消費者必須使用同一個隊列
*/
// 1.創建阻塞隊列(容量位1 )
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
// 2.創建線程對象,並把阻塞隊列傳遞過去
Cook cook = new Cook(queue);
Foodie foodie = new Foodie(queue);
// 3.開啟線程
cook.start();
foodie.start();
}
}
線程池
1、線程池的創建
ExecutorService newCachedThreadPool() 創建一個沒有上限的線程池
ExecutorService new FixedThreadPool(int nThread) 創建有上限的線程池
線程類MyRunnable
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
執行類MyThreadPoolDemo
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
/*
* public static ExecutorService newCachedThreadPool() 創建一個沒有上限的線程池
* public static ExecutorService new FixedThreadPool(int nThread) 創建有上限的線程池
*/
fixedThreadPool();
}
public static void newCachedThreadPool() throws InterruptedException {
// 1.獲取線程池對象
ExecutorService pool1 = Executors.newCachedThreadPool();
// 2.提交任務
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
// 3.銷毀線程池
pool1.shutdown();
}
public static void fixedThreadPool() throws InterruptedException {
// 創建三個線程
ExecutorService pool1 = Executors.newFixedThreadPool(3);
// 2.提交任務
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
// 3.銷毀線程池
pool1.shutdown();
}
}
2、自定義線程池
(核心線程數量, 最大線程數量, 空閑線程最大存活時間, 任務隊列, 創建線程工廠, 任務的拒絕策略)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
參數一: 核心線程數量 不能小於0,
參數二: 最大線程數 不能小於等於0,最大數量 >= 核心線程數量,
參數三: 控線線程最大存活時間 不能小於0,
參數四: 線程存活時間單位 用TimeUnit指定,
參數五: 任務隊列 不能為null,
參數六: 創建線程工廠 不能為null,
參數七: 任務的拒絕策略 不能為null
);
例子,MyThreadPoolDemo1
public class MyThreadPoolDemo1 {
public static void main(String[] args) {
// 自定義線程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
// 核心線程數量,不能小於0
3,
// 最大線程數不能小於等於0,最大數量 >= 核心線程數量
6,
// 控線線程最大存活時間
60,
// 線程存活時間單位,用TimeUnit指定
TimeUnit.SECONDS,
// 任務隊列
new ArrayBlockingQueue<>(3),
// 創建線程工廠
Executors.defaultThreadFactory(),
// 任務的拒絕策略
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 提交任務到線程池
pool.submit(() -> {
System.out.println(Thread.currentThread().getName());
});
}
}