-
進程是指運行中的程序
-
進程是程序的一次執行過程,或是正在運行的一個程序。是動態過程:有它自身的產生、存在和消亡的過程
解釋:電腦打開一個程序,程序一運行就是進程,進程會占用內存空間,關閉程序。內存釋放
什么是線程
-
線程時有進程創建的,是進程的一個實體
-
一個進程可以擁有多個線程
解釋:打開迅雷是一個進程,迅雷同時下載多個資源,為多個線程
什么是單線程和多線程
-
單線程:同一時刻,只允許執行一個線程
-
解釋:迅雷同時下載多個資源,為多個線程---多線程
什么是並發和並行
-
並發:同一時刻,多個任務交替執行。也就是單核CPU實現的多任務就是並發
-
並行:同一時刻,多個任務同時執行,多核CPU可以實現並行
解釋
-
並發:一個CPU來回執行多個程序
-
並行:兩個CPU同時各自實現自己的一個程序
-
並發和並行同時存在:兩個CPU同時各自實現自己的多個程序
擴展:
獲取自己電腦上有幾個CPU
Runtime runtime = Runtime.getRuntime();
int cpunums = runtime.availableProcessors();
創建線程
-
繼承Thread類,重寫run方法
-
實現Runnable接口,重寫run()方法
入門案例1---繼承Thread類
要求:1. 編寫程序,開啟一個線程,該線程每個1秒。在控制台輸出“喵喵,我是小貓咪”,當輸出80次后,結束該進程
package threaduse;
//通過繼承Thread類,創建線程
public class Thread01 {
public static void main(String[] args) {
cat cat = new cat();
cat.start();
}
}
class cat extends Thread {
多線程機制
-
執行代碼:開啟進程
-
執行main方法:開啟main線程
-
main方法執行線程類的start方法:開啟線程類的線程
-
main線程啟動了線程類,不會發生阻塞,會和線程類的線程交替執行
需知點
-
main線程會和線程類線程同時運行
-
main線程可以繼續開其他線程,線程類也可以開其他線程
-
main線程結束、線程類線程不會結束
-
進程中的全部線程結束,進程才會消失
為什么調用是start方法,而不是run方法
-
調用start方法是啟動線程---》最終會調用線程類的run方法
-
直接調用run方法的話,run方法其實就是一個普通的方法,沒有真正的啟動線程,會把run方法執行完,才會先下執行
-
真正實現多線程的效果是start0方法,而不run方法
步驟是:
-
start方法
-
start0方法 start0()是本地方法,由JVM調用
-
實際上是:在start0方法里面用多線程的機制來調用run方法
入門案例---實現Runnable接口
說明:java是單繼承的,在某些情況下,一個繼承了某個父類,就不能再繼承Thread類,這就用到了接口
需求:請編寫程序,該程序可以每隔1秒,在控制台輸出hi,輸出10次后,自動退出,用Runnable接口方式實現
package threaduse;
public class Runnable01 {
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable{
需知點:
-
因為Dog類沒辦法調用start方法
-
所以創建了Thread類,把dog對象放入Thread對象中,就可以調用run方法了,因為這里底層使用設計模式【代理模式】
代碼模擬 了解代理模式
解釋為啥可以把dog對象放入Thread對象中,就可以調用run方法了
package threaduse;
public class Runnable01 {
public static void main(String[] args)
Tiger tiger = new Tiger();
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();//1.
}
}
class Animal{}
class Tiger extends Animal implements Runnable{
入門案例---多線程執行
需求:編寫一個程序,創建兩個線程,一個線程每隔1秒輸出“HelloWorld”,輸出10次,退出。一個線程每隔1秒輸出“Hi",輸出5次退出
package threaduse;
public class Thread02 {
public static void main(String[] args) {
T t = new T();
TT tt = new TT();
Thread thread1 = new Thread(t);
Thread thread2 = new Thread(tt);
thread1.start();
thread2.start();
}
}
class T implements Runnable{
線程終止
-
當線程完成任務后,會自動退出
-
通過使用變量來控制run方法退出的方式停止線程,即通知方式
package threaduse;
public class ThreadExit {
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
Thread thread = new Thread(t1);
thread.start();
Thread.sleep(10*1000);
//如果希望main線程去控制t1 線程的終止,必須修改a的值
t1.setA(false);
}
}
class T1 implements Runnable{
int b = 0;
private boolean a = true;
線程常用方法
-
常用方法1
-
setName 設置線程名稱,使之與參數的name相同
-
getName 返回線程的名稱
-
start 使線程開始執行;java虛擬機底層調用該線程的start0方法
-
run 調用線程對象run方法
-
setPriority 更改線程的優先級
-
getPriority 獲取線程的優先級
-
sleep 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)
-
interrupt 中斷線程
-
-
常用方法1注意點
-
start底層會創建新的線程,調用run,run就是一個簡單的方法調用,不會啟動新線程
-
線程的優先級范圍 MAX_PRIORITY 10 MIN_PRIORITY 1 NORM_PRIORITY 5
-
interrupt,中斷線程,但並沒有真正的結束線程。一般用於中斷正在休眠的線程
-
sleep:線程的靜態方法,使當前線程休眠
-
-
常用方法1代碼實現
package threaduse;
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.setName("常世超");
t2.setPriority(Thread.MIN_PRIORITY);
t2.start();//啟動子線程
//讓主線程打印5次,中斷子線程的休眠
for (int i = 0; i <5 ; i++) {
Thread.sleep(1000);
System.out.println("hi"+i);
}
t2.getPriority();
t2.interrupt();//當執行到這里 就會中斷t2線程的休眠
}
}
class T2 extends Thread{
-
常用方法2
-
yield :線程的禮讓,讓其他線程執行,但禮讓的時間不確定,所以不一定禮讓成功
-
join :線程插隊,插隊的線程一旦插隊成功,則肯定先執行完插入的線程所有任務
-
-
常用方法2代碼實現
package threaduse;
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T3 t3 = new T3();
Thread thread = new Thread(t3);
thread.start();
for (int i = 1; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("main正在進行第" + i + "次沖刺");
if (i == 10) {
thread.yield();//線程的禮讓
// thread.join();//線程插隊
}
}
}
}
class T3 implements Runnable {
-
常用方法3
-
用戶線程:也叫工作線程,當線程的任務執行完或者通知方式結束
-
守護線程:一般是為工作線程服務的,當所有的用戶線程結束,守護線程自動結束
-
常見的守護線程:垃圾回收機制
-
-
代碼實現
package threaduse;
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
A2 a2 = new A2();
a2.setDaemon(true);
a2.start();
for (int i = 1; i <=10 ; i++) {
System.out.println("我是用戶線程"+i);
Thread.sleep(1000);
}
}
}
class A2 extends Thread{
@Override
public void run() {
for ( ; ;) {
System.out.println("我是守護線程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
常用方法練習題
package threaduse;
/*1. 主線程每個1秒,輸出hi,一共10次
* 2.當輸出到 hi 5時 ,啟動一個子線程(要求實現Runnable接口),每個1s輸出hello,等該線程輸出10次后,退出
* 3.主線程繼續輸出hi,直到線程結束*/
public class ThreadMethodTest {
public static void main(String[] args) throws InterruptedException {
A a = new A();
Thread thread = new Thread(a);
for (int i = 1;i<=10;i++){
Thread.sleep(1000);
System.out.println("hi"+i);
if(i==5){
thread.start();
thread.join();
}
}
}
}
class A implements Runnable{
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
System.out.println("hello"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
線程的生命周期
-
-
-
Ready----就緒狀態
-
Running----運行狀態
-
-
-
-
-
售票案例引出線程同步鎖
出現的問題:
-
問題1.重賣:一張票賣給了多個人
-
問題2.超賣:出現了票數為0甚至是負數的情況
package threaduse;
public class Mai {
public static void main(String[] args) {
Csc csc = new Csc();
Thread thread1 = new Thread(csc);
Thread thread2 = new Thread(csc);
Thread thread3 = new Thread(csc);
thread1.start();
thread2.start();
thread3.start();
}
}
class Csc implements Runnable{
int a = 100;
@Override
public void run() {
while (true){
if(a<=0) {
System.out.println("售票結束");
break;
}
try {
//讓程序休眠后出現的兩個問題:
//問題1.重賣:一張票賣給了多個人
//問題2.超賣:出現了票數為0甚至是負數的情況
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"\t"+(--a));
}
}
}
同步鎖
-
判斷程序有沒有出現線程安全問題:
在多線程程序中 + 有共享數據 + 多條語句操作共享數據
-
同步與異步:
-
同步:體現了排隊的效果,同一時刻只能有一個線程占用資源,其他沒有權限的線程排隊
壞處:效率低,
好處:安全
-
異步:體現了多線程搶占資源的效果,線程間互想不等待,互相搶占資源
壞處:有安全隱患
好處:效率高
-
-
同步鎖關鍵字:synchronized
-
實現同步具體方法
-
同步代碼塊
synchronized (鎖對象){//使用的鎖對象類型任意,但注意:必須唯一!
//需要同步的代碼(也就是可能出現問題的操作共享數據的多條語句);
} -
同步方法
public synchronized void m(){
//需要同步的代碼
}
-
-
使用同步鎖的前提
-
同步需要兩個或者兩個以上的線程(單線程無需考慮多線程安全問題)
-
多個線程間必須使用同一個鎖(我上鎖后其他人也能看到這個鎖,不然我的鎖鎖不住其他人,就沒有了上鎖的效果)
-
-
鎖對象
-
(非靜態的)同步方法的鎖可以是this,也可以是其他對象(要求是同一對象)
public synchronized void aVoid(){
System.out.println("我愛你");
}
//解釋(非靜態的)同步方法的鎖可以是this,也可以是其他對象(要求是同一對象)
public void a(){
Object o = new Object();
synchronized (this){
// synchronized (o){
System.out.println("我愛你");
}
} -
(靜態的)同步方法的鎖對象是當前類本身
public static synchronized void a(){
System.out.println("我愛你");
}
//解釋靜態的同步方法鎖對象時類本身
public static void a(){
synchronized (Csc.class){
System.out.println("我愛你");
}
} -
同步代碼塊的鎖:
@Override
public void run() {
Object o = new Object();
synchronized (o) {}
}
@Override
public void run() {
synchronized (this) {}
}
-
-
線程的死鎖
-
介紹
多個線程都占用了對方的鎖資源、但不肯想讓,導致了死鎖,在編程是一定要避免死鎖的發生
-
應用案例
媽媽:你先寫作業,我讓你玩手機
兒子:你先讓我玩手機,我就寫作業
-
代碼實現
package threaduse;
public class DeadLock {
public static void main(String[] args) {
DeadLockDemo A = new DeadLockDemo(true);
DeadLockDemo B = new DeadLockDemo(false);
A.setName("線程A");
B.setName("線程B");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag ;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
/*下面有業務邏輯
* 1.如果flag 為T ,線程A 就會先得到/持有 o1 對象鎖,然后嘗試去獲取o2 對象鎖
* 2.如果線程A 得不到 o2 對象鎖,就會Blocked
* 3.如果flag 為F , 線程B就會就會先得到/持有 o2 對象鎖,然后嘗試去獲取o1 對象鎖
* 4.如果線程B 得不到 o1 對象鎖,就會Blocked*/
if(flag) {
synchronized (o1) {
System.out.println(Thread.currentThread().getName()+"進入狀態1");
synchronized (o2) {
System.out.println(Thread.currentThread().getName()+"進入狀態2");
}
}
}else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName()+"進入狀態3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName()+"進入狀態4");
}
}
}
}
}
釋放鎖
-
下面操作會釋放鎖
-
當前線程的同步方法、同步代碼塊執行結束
案例:上廁所,完事出來
-
當前線程在同步方法、同步代碼塊中遇到break、return。
案例:沒有正常完事,經理叫他修BUG,不得不出來
-
當前線程在同步方法、同步代碼塊中出現了未處理的Error或Exception,導致異常結束
案例:沒有正常完事,發現為帶紙,不得不出來
-
當前線程在同步方法、同步代碼塊中執行了wait()方法,當前線程暫停,並釋放鎖
案例:沒有正常完事,覺得需要醞釀一下,所以出來等會再進去
-
-
下面操作不會釋放鎖
-
線程執行同步方法、代碼塊時,程序調用了Thread.sleep()、Thread.yield()方法暫停當前線程的執行、不會釋放鎖
案例:上廁所,眯一會
-
線程執行同步方法、代碼塊時,其他線程調用了suspend()方法將線程掛起,該線程不會釋放鎖
提示:應盡量避免使用suspend()和resume()來控制線程,方法不再推薦使用
-
-