## 多線程 ### 線程的實現方式 1. 繼承 Thread 類:一旦繼承了 Thread 類,就不能再繼承其他類了,可拓展性差 2. 實現 Runnable 介面:仍然可以繼承其他類,可拓展性較好 3. 使用線程池 #### 繼承Thread 類 不能通過線程對象調用 run() 方法,需要 ...
多線程
線程的實現方式
- 繼承 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;
}
}
線程死鎖