多執行緒
執行緒的實作方式
- 繼承 Thread 類:一旦繼承了 Thread 類,就不能再繼承其他類了,可拓展性差
- 實作 Runnable 介面:仍然可以繼承其他類,可拓展性較好
- 使用執行緒池
繼承Thread 類
? 不能通過執行緒物件呼叫 run() 方法,需要通過 t1.start() 方法,使執行緒進入到就緒狀態,只要進入到就緒狀態的執行緒才有機會被JVM調度選中
// 這是一個簡單的栗子
public class StudentThread extends Thread{
public StudentThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println("This is a test thread!");
System.out.println(this.getName());
}
}
}
// 啟動類
public static void main(String[] args) {
Thread t1 = new StudentThread();
// 不能通過執行緒物件呼叫run()方法
// 通過 t1.start() 方法,使執行緒進入到就緒狀態,只要進入到就緒狀態的執行緒才有機會被JVM調度選中
t1.start();
}
實作 Runable 介面
? 實作方式需要借助 Thread 類的建構式,才能完成執行緒物件的實體化
// 介還是一個簡單的栗子
public class StudentThreadRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println("This is a test thread!");
System.out.println(Thread.currentThread().getName());
}
}
}
// 啟動類
public static void main(String[] args) {
// 實作方式需要借助 Thread 類的建構式,才能完成執行緒物件的實體化
StudentThreadRunnable studentThreadRunnable = new StudentThreadRunnable();
Thread t01 = new Thread(studentThreadRunnable);
t01.setName("robot010");
t01.start();
}
匿名內部類實作
? 在類中直接書寫一個當前類的子類,這個類默認不需要提供名稱,類名由JVM臨時分配
public static void main(String[] args) {
Thread t01 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println("This is a test thread!");
}
System.out.println(Thread.currentThread().getName()); // 執行緒名
System.out.println(this.getClass().getName()); // 匿名執行緒類類名
}
};
t01.start();
}
執行緒的休眠(sleep方法)
? sleep方法,會使當前執行緒暫停運行指定時間,單位為毫秒(ms),其他執行緒可以在sleep時間內,獲取JVM的調度資源
// 這是一個計時器
public class TimeCount implements Runnable{
@Override
public void run() {
int count = 0;
while(true){
System.out.println(count);
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 測驗類
public static void main(String[] args) {
System.out.println("這是main方法運行的時候,開啟的主執行緒~~~");
TimeCount timeCount = new TimeCount();
Thread timeThread = new Thread(timeCount);
System.out.println("開啟計時器");
timeThread.start();
System.out.println("主執行緒即將休眠>>>>>>>>>>>");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(">>>>>>>>>>>主執行緒休眠結束~~~~~");
}
執行緒的加入(join方法)
? 被 join 的執行緒會等待 join 的執行緒運行結束之后,才能繼續運行自己的代碼
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("This is thread-01!");
}
}
};
thread01.start();
try {
thread01.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread02 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("This is thread-02!");
}
}
};
thread02.start();
}
// thread02 會等待 thread01 完全跑完,才會開始自己的執行緒
執行緒的優先級(priority方法)
? 優先級高的執行緒會有更大的幾率競爭到JVM的調度資源,但是高優先級并不代表絕對,充滿玄學?
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("This is thread-01! " + Thread.currentThread().getPriority());
}
}
};
Thread thread02 = new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("This is thread-02! " + Thread.currentThread().getPriority());
}
}
};
thread01.setPriority(1);
thread02.setPriority(10);
// 盡管thread02優先級高于thread01,但是也有可能
thread01.start();
thread02.start();
}
執行緒的讓步(yield方法)
? 立刻讓出JVM的調度資源,并且重新參與到競爭中
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run(){
for (int i = 1; i <= 10; i++) {
System.out.println("This is thread-01! " + i);
}
}
};
Thread thread02 = new Thread(){
@Override
public void run(){
for (int i = 1; i <= 10; i++) {
System.out.println("This is thread-02! " + i);
Thread.yield();
}
}
};
thread01.start();
thread02.start();
}
守護執行緒(Deamon)
? 會在其他非守護執行緒都運行結束之后,自身停止運行,(GC垃圾回識訓制就是一個典型的守護執行緒)
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run(){
int times = 0;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("time pass " + ++times + "second");
}
}
};
Thread thread02 = new Thread(){
@Override
public void run(){
for (int i = 1; i <= 10; i++) {
System.out.println("This is thread-02! " + i);
}
}
};
// 將t1設定為守護執行緒
thread01.setDaemon(true);
thread01.start();
thread02.start();
// 延長主執行緒運行,便于觀察結果
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("main thread end \\(-_-)/");
}
執行緒同步
資料操作的原子性
? 具有原子性的操作,不會被其他執行緒打斷,類似(a++)的操作是不具備原子性的,因此很容易在多執行緒場景中出現誤差
synchronized 悲觀鎖(互斥性)
優缺點:保證了資料在多執行緒場景下的安全(保證執行緒安全),犧牲的是效率,鎖的獲取和釋放,其他執行緒被阻塞都會額外消耗性能
同步物件:被多個執行緒所競爭的資源物件叫做同步物件
核心作用: 確保執行緒在持有鎖的期間內,其他執行緒無法操作和修改指定資料(同步物件)
? 每一個同步物件都會持有一把執行緒鎖,當執行緒運行到synchronized 修飾的方法或代碼時,執行緒會自動獲取當前同步物件的執行緒鎖,在synchronized 修飾的方法或代碼塊運行結束后,該執行緒會自動釋放此執行緒鎖,在持有執行緒鎖的這段時間里,其他執行緒是無法執行synchronized 所修飾的代碼塊的,其他執行緒會被阻塞在synchronized 代碼塊之外,直到這把鎖被釋放,,,
// synchronized 的兩種寫法:
// 1. 寫在方法之前,修飾整個方法
public synchronized Ticket getTicket(){
// 取票
Ticket ticketTmp = null;
if(!tickets.isEmpty()){
ticketTmp = tickets.removeLast();
}
return ticketTmp;
}
// 2. 代碼塊,修飾代碼塊所包含的部分
public Ticket getTicket(){
// 取票
synchronized(this){
Ticket ticketTmp = null;
if(!tickets.isEmpty()){
ticketTmp = tickets.removeLast();
}
return ticketTmp;
}
}
執行緒死鎖
??一個執行緒可以持有多個不同的同步物件的鎖!!!當兩個執行緒同時想要持有相同一把鎖時,就會產生死鎖現象
wait notify notifyAll
? 這三個方法并不是通過執行緒呼叫,而是通過同步物件第哦啊用,所有物件都可以當作是同步物件,這三個方法是在Object類中定義的
| 方法 | 作用 |
|---|---|
| wait | 讓占用當前同步物件鎖的執行緒進行等待,并且釋放鎖,直到被喚醒時才會被恢復 |
| notify | 通知一個在當前同步物件上等待的執行緒 進行喚醒,讓其重新競爭鎖,并運行代碼 |
| notifyAll | 通知在當前同步物件上等待的所有執行緒,進行喚醒 |
生產者消費者模型
- 生產者執行緒負責提供用戶請求
- 消費者執行緒負責處理用戶請求
// 模擬生產者消費者
// 球(ball) => 資料
public class Ball {
private int ballNum;
}
// 籃子(basket) => 資料容器
public class Basket {
// 指標,指向最新放入的球
private int index = 0;
// 容器
private Ball[] balls = new Ball[5];
// 存操作
public synchronized void ballPut(Ball ballTmp){
// 不斷的判斷容器是否放滿,如果執行緒被喚醒,需要再此判斷,如果此時容器還是滿的,應該繼續等待
while(index == balls.length){
System.out.println("The basket is full~~");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 行程能推進到這里,說明容器并沒有滿,可以進行存操作
balls[index++] = ballTmp;
System.out.println("ballPut : " + index);
// 此時可以喚醒等待的取操作執行緒
this.notifyAll();
}
// 取操作
public synchronized void ballGet(){
// 不斷的判斷容器是否為空,如果執行緒被喚醒,需要再此判斷,如果此時容器還是空的,應該繼續等待
while(index == 0){
System.out.println("The basket is empty~~");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 行程能推進到這里,說明容器沒有被取空,可以進行取操作
System.out.println("ballGet : " + (index - 1));
balls[--index] = null;
// 此時可以喚醒等待的存操作執行緒
this.notifyAll();
}
}
// 放球(ballPut) => 生產者
public class Producer implements Runnable{
// 為了保證生產者和消費者系結的是同一個容器
Basket basket;
// 構造方法,方便傳參
public Producer(Basket basket){
this.basket = basket;
}
// 存操作
@Override
public void run() {
// 模擬一共放10個球
for (int i = 0; i < 20; i++) {
Ball ballTmp = new Ball(i);
basket.ballPut(ballTmp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 拿球(ballOut) => 消費者
public class Consumer implements Runnable{
// 為了保證生產者和消費者系結的是同一個容器
Basket basket;
// 構造方法,方便傳參
public Consumer(Basket basket){
this.basket = basket;
}
// 取操作
@Override
public void run() {
for (int i = 0; i < 20; i++) {
basket.ballGet();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 測驗啟動類
public static void main(String[] args) {
// 實體化容器
Basket basket = new Basket();
// 實體化生產者,消費者
Producer producer = new Producer(basket);
Consumer consumer = new Consumer(basket);
// 生產者執行緒
new Thread(producer).start();
// 等待,容器被裝滿
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 消費者執行緒
new Thread(consumer).start();
}
執行緒池和自定義執行緒池
優點:節約執行緒資源,讓執行緒池中的消費者執行緒不斷的去執行任務
- 需要準備一個存放業務的容器
- 只啟動固定數量的消費執行緒
- 生產者執行緒負責向任務容器中提交任務
- 程式開始時,任務容器為空,所有消費者執行緒都處于 wait 狀態
- 直到有一個生產者向任務容器中投入了一個任務,那么就會有一個消費者執行緒被 notify 喚醒,并執行此任務
- 任務執行完畢之后,該消費者執行緒會重新處于 wait 狀態,等待下一次任務到來
- ?(執行緒復用)整個任務流程,都不需要創建新的執行緒,而是對已經存在的執行緒回圈使用
模擬執行緒池基礎功能實作(沒有實作自動擴容)
public class ThreadPool {
// 執行緒池的大小
int threadPoolSize;
// 任務容器中的任務 --> 執行緒
LinkedList<Runnable> tasks = new LinkedList<>();
// 提供一個 add 方法,用于向任務容器中添加任務
public void add(Runnable r){
synchronized (tasks){
tasks.add(r);
// 喚醒消費者執行緒,取走任務容器中的任務
tasks.notifyAll();
}
}
//建構式--初始化執行緒池
public ThreadPool() {
// 初始化執行緒池大小
threadPoolSize = 10;
// 定義10個消費者執行緒,并且將其實體化后start
synchronized (tasks){
for (int i = 0; i < threadPoolSize; i++) {
new TaskConsumeThread("ThreadRobot-0" + i+1).start();
}
}
}
// 內部類--消費者執行緒
class TaskConsumeThread extends Thread{
// 重寫 Thread 類構造方法,方便給執行緒定義 name 屬性
public TaskConsumeThread(String name) {
super(name);
}
// 需要被執行的任務
Runnable task;
// 重寫 run 方法
@Override
public void run() {
System.out.println(this.getName() + " running!");
while (true){
// 獲取任務容器 tasks 的鎖,避免多個消費者執行緒同時操作任務容器,保證任務容器的執行緒安全
synchronized (tasks){
// 只要任務容器為空,需要 wait 方法,讓所有消費者執行緒等待
while (tasks.isEmpty()){
try {
tasks.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 脫離回圈能運行到這里,說明任務容器不為空!
// 當前消費者從任務容器中取出任務,并存再自己的 task 參考中
task = tasks.removeLast();
// 喚醒添加任務的執行緒
tasks.notifyAll();
System.out.println(this.getName() + " get task!");
// 啟動任務執行緒
new Thread(task).start();
}
}
}
}
}
// 啟動測驗類01
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool();
Scanner sc = new Scanner(System.in);
String tmp;
while (true) {
tmp = sc.nextLine();
Thread task = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
threadPool.add(task);
}
}
// 啟動測驗類02
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool();
int sleepTime = 1000;
// 任務計數
int[] count = {0};
while (true) {
Runnable runnableTmp = new Runnable() {
@Override
public void run() {
// 通過訪問外部陣列地址,來實作控制
System.out.println("執行任務" + (++count[0]));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
threadPool.add(runnableTmp);
try {
Thread.sleep(sleepTime);
sleepTime = sleepTime > 100 ? sleepTime - 100 : sleepTime;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Java自帶的執行緒池使用
public static void main(String[] args) {
// 引數含義:
// corePoolSize : 執行緒池中初始執行緒數量
// maximumPoolSize : 執行緒池中最大執行緒數量
// keepAliveTime : 臨時執行緒的最大存活時間
// TimeUnit.SECONDS : 存活時間的單位
// new LinkedBlockingDeque<Runnable>() : 存放任務的任務容器
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
// 創建測驗任務執行緒
Runnable runnableTmp = new Runnable() {
@Override
public void run() {
System.out.println("This is a testRunnable!");
}
};
// 將測驗任務拋入執行緒池即可
threadPoolExecutor.execute(runnableTmp);
}
資料庫連接池
? 如果每有一個用戶使用連接,就新建一個連接的話,創建連接和關閉連接的程序是非常消耗性能的,且單一資料庫支持的連接總數是有上限的,如果短時間內并發量過大,資料庫的連接總數就會被消耗光,后續執行緒發起的資料庫連接就會失敗,那么連接池的作用就是:它會在使用之前,就創建好一定數量的連接,如果有執行緒需要使用連接,就從連接池中借用,而不是重新創建連接,使用完此連接之后,再將此連接歸還給連接池,整個程序中,連接池中的連接都不會被關閉,而是被重復使用,
模擬資料庫連接池實作
public class ConnectionPool {
List<Connection> connections = new ArrayList<>();
int size;
// 準備建構式
public ConnectionPool(int size) {
this.size = size;
init();
}
// 初始化連接池
public void init(){
try {
Class.forName("com.mysql.jdbc.Driver");
for (int i = 0; i < size; i++) {
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/iweb?characterEncoding=utf8","root","123456");
connections.add(c);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 獲取一個資料庫連接
public synchronized Connection getConnection(){
while (connections.isEmpty()){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return connections.remove(0);
}
// 歸還一個資料庫連接
public synchronized void returnConnection(Connection connectionTmp){
connections.add(connectionTmp);
this.notifyAll();
}
}
// 測驗啟動類
public class Application {
public static void main(String[] args) {
ConnectionPool connectionPool = new ConnectionPool(3);
// 創建100個執行緒用于測驗
for (int i = 0; i < 100; i++) {
new WorkingThread("Thread0" + i,connectionPool).start();
}
}
static class WorkingThread extends Thread{
private ConnectionPool connectionPool;
public WorkingThread(String name,ConnectionPool cp){
super(name);
this.connectionPool = cp;
}
@Override
public void run() {
Connection connection = connectionPool.getConnection();
System.out.println(this.getName() + " get the mysql connection!");
try(Statement statement = connection.createStatement()){
Thread.sleep(1000);
statement.execute("select * from user");
}catch (Exception e){
e.printStackTrace();
}
connectionPool.returnConnection(connection);
}
}
}
druid 德魯伊資料庫連接池(明天再加)
Reentrantlock 悲觀鎖的另一種實作方式(明天再加)
volatile 樂觀鎖 Java記憶體模型(明天再加)
記憶體私有公有 指令重排序 可見性(明天再加)
一致性協議(明天再加)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/557101.html
標籤:其他
上一篇:python學習筆記:繼承與超類
下一篇:返回列表
