一、概念
進程
進程:具有一定獨立功能的程序關于某個數據集合上的一次運行活動,進程是系統(tǒng)進行資源分配和調度的一個獨立單位.
線程
線程:是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源.
進程和線程的關系
進程(process)和線程(thread)是操作系統(tǒng)的基本概念试浙,但是它們比較抽象董瞻,不容易掌握。
最近田巴,我讀到一篇材料力细,發(fā)現有一個很好的類比,可以把它們解釋地清晰易懂固额。
計算機的核心是CPU眠蚂,它承擔了所有的計算任務。它就像一座工廠斗躏,時刻在運行逝慧。
假定工廠的電力有限,一次只能供給一個車間使用啄糙。也就是說笛臣,一個車間開工的時候,其他車間都必須停工隧饼。背后的含義就是沈堡,單個CPU一次只能運行一個任務。
進程就好比工廠的車間燕雁,它代表CPU所能處理的單個任務诞丽。任一時刻鲸拥,CPU總是運行一個進程,其他進程處于非運行狀態(tài)僧免。
一個車間里刑赶,可以有很多工人。他們協(xié)同完成一個任務懂衩。
線程就好比車間里的工人撞叨。一個進程可以包括多個線程。
車間的空間是工人們共享的浊洞,比如許多房間是每個工人都可以進出的牵敷。這象征一個進程的內存空間是共享的,每個線程都可以使用這些共享內存法希。
可是劣领,每間房間的大小不同,有些房間最多只能容納一個人铁材,比如廁所尖淘。里面有人的時候,其他人就不能進去了著觉。這代表一個線程使用某些共享內存時村生,其他線程必須等它結束,才能使用這一塊內存饼丘。
一個防止他人進入的簡單方法趁桃,就是門口加一把鎖。先到的人鎖上門肄鸽,后到的人看到上鎖卫病,就在門口排隊,等鎖打開再進去典徘。這就叫"互斥鎖"(Mutual exclusion蟀苛,縮寫 Mutex),防止多個線程同時讀寫某一塊內存區(qū)域逮诲。
還有些房間帜平,可以同時容納n個人,比如廚房梅鹦。也就是說裆甩,如果人數大于n,多出來的人只能在外面等著齐唆。這好比某些內存區(qū)域嗤栓,只能供給固定數目的線程使用。
這時的解決方法箍邮,就是在門口掛n把鑰匙茉帅。進去的人就取一把鑰匙叨叙,出來時再把鑰匙掛回原處。后到的人發(fā)現鑰匙架空了担敌,就知道必須在門口排隊等著了摔敛。這種做法叫做"信號量"(Semaphore)廷蓉,用來保證多個線程不會互相沖突全封。
不難看出,mutex是semaphore的一種特殊情況(n=1時)桃犬。也就是說刹悴,完全可以用后者替代前者。但是攒暇,因為mutex較為簡單土匀,且效率高,所以在必須保證資源獨占的情況下形用,還是采用這種設計就轧。
操作系統(tǒng)的設計,因此可以歸結為三點:
(1)以多進程形式田度,允許多個任務同時運行妒御;
(2)以多線程形式,允許單個任務分成不同的部分運行镇饺;
(3)提供協(xié)調機制乎莉,一方面防止進程之間和線程之間產生沖突,另一方面允許進程之間和線程之間共享資源奸笤。
以上轉自阮一峰的個人網站
二惋啃、創(chuàng)建線程的方式
創(chuàng)建線程共有三種方式,一是繼承Thread類重寫run方法监右、二是實現Runnable接口重寫run方法(推薦使用)边灭、三是實現Callable接口重寫run方法
1. 繼承Thread類
package com.yjj.test01;
/**
* @Description:
* @Author: YinJunjie
* @CreateDate: 2018/9/21 17:53
* @Version: 1.0
*/
class Thread1 extends Thread {
/**
* 實現多線程第一種方式,繼承Thread健盒,實現run方法
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "運行" + i);
}
}
}
public class ThreadMain {
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
thread1.setName("A");
Thread1 thread2 = new Thread1();
thread2.setName("B");
thread1.start();
thread2.start();
}
}
輸出
B運行0
A運行0
A運行1
B運行1
A運行2
B運行2
A運行3
B運行3
A運行4
B運行4
2. 實現Runnable接口
package com.yjj.test01;
/**
* @Description:
* @Author: YinJunjie
* @CreateDate: 2018/9/21 18:03
* @Version: 1.0
*/
class Thread2 implements Runnable {
private String name;
public Thread2(String name) {
this.name = name;
}
/**
* 實現多線程第二種方式存筏,實現Runnable,實現run方法
*/
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(this.name + "運行" + i);
}
}
}
public class ThreadMain2 {
public static void main(String[] args) {
Thread thread1=new Thread(new Thread2("A"));
Thread thread2=new Thread(new Thread2("b"));
thread1.start();
thread2.start();
}
}
輸出
A運行0
b運行0
b運行1
b運行2
b運行3
b運行4
A運行1
A運行2
A運行3
A運行4
注意:
- 啟動一個線程只能通過start()方法味榛,而不能直接調用run方法椭坚,調用run方法相當于執(zhí)行了一個普通的方法,而沒有啟動線程搏色,代碼將會同步執(zhí)行善茎。
- 如果多次調用start()方法,則會出現異常Exception in thread "main" java.lang.IllegalThreadStateException频轿。
- 執(zhí)行start()方法的順序并不代表線程啟動的順序垂涯,線程執(zhí)行的順序是隨機的烁焙,某個線程搶到cpu資源就會執(zhí)行,其他則等待
三耕赘、線程安全
例:
public class MyThread extends Thread {
private int count=5;
public MyThread() {
}
public MyThread(String name) {
this.setName(name);
}
@Override
public void run() {
while (count>0){
count--;
System.out.println(Thread.currentThread().getName()+":"+count);
}
}
}
public class TestMain {
public static void main(String[] args) {
shareVariable();
}
/**
* 不共享變量 各自減各自的
*/
public static void notShareVariable (){
MyThread a=new MyThread("A");
MyThread b=new MyThread("B");
MyThread c=new MyThread("C");
a.start();
b.start();
c.start();
}
/**
* 共享變量
*
*/
public static void shareVariable(){
MyThread myThread=new MyThread();
Thread a=new Thread(myThread,"A");
Thread b=new Thread(myThread,"B");
Thread c=new Thread(myThread,"C");
a.start();
b.start();
c.start();
}
}
輸出
A:3
B:3
C:2
A:1
B:0
從輸出可看出骄蝇,A和B打印的都是3,說明A和B同事對count進行處理操骡,產生了非線程安全問題九火。
在某些JVM中,i--其實是由如下3步組成:
- 讀取原有的i值
- 計算i-1
- 對i賦新值
在這3個步驟中册招,如果有多個線程同事訪問岔激,那么就會出現非線程安全問題。
四是掰、常用方法
- currentThread()
返回代碼段正在被哪個線程調用 - this()和Thread.currentThread()的區(qū)別
package com.yjj.chapter01.test05;
/**
* @Description:
* @Author: YinJunjie
* @CreateDate: 2018/9/22 18:07
* @Version: 1.0
*/
public class MyThread extends Thread {
public MyThread() {
System.out.println("構造方法----begin");
System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
System.out.println("Thread.currentThread().isAlive()="+Thread.currentThread().isAlive());
System.out.println("this.getName()="+this.getName());
System.out.println("this.isAlive()="+this.isAlive());
System.out.println("構造方法----end");
}
@Override
public void run() {
System.out.println("run方法-----begin");
System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
System.out.println("Thread.currentThread().isAlive()="+Thread.currentThread().isAlive());
System.out.println("this.getName()="+this.getName());
System.out.println("this.isAlive()="+this.isAlive());
System.out.println("run方法----end");
}
}
public class TestMain {
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.setName("myThread");
Thread thread=new Thread(myThread);
System.out.println("main begin thread is Alive="+thread.isAlive());
thread.setName("thread");
thread.start();
System.out.println("main end thread is Alive="+thread.isAlive());
}
}
當一個Thread子類被當成參數傳入Thread對象時虑鼎,this和currentThread指向不同的對象,輸出如下
構造方法----begin
Thread.currentThread().getName()=main
Thread.currentThread().isAlive()=true
this.getName()=Thread-0
this.isAlive()=false
構造方法----end
main begin thread is Alive=false
main end thread is Alive=true
run方法-----begin
Thread.currentThread().getName()=thread
Thread.currentThread().isAlive()=true
this.getName()=myThread
this.isAlive()=false
run方法----end
- isAlive()是否活動狀態(tài)
- sleep()方法
讓當前“正在執(zhí)行的線程”休眠指定毫秒數键痛,這個“正在執(zhí)行的線程”是指this.currentThread()返回的線程 - getId()獲取線程的唯一標識
- 停止線程
有3種方法可以終止正在運行的線程
1). 使用退出標志炫彩,使線程正常退出,也就是當run方法完成后線程終止
2). 使用stop方法強行終止線程強烈不推薦使用絮短,stop和suspend及resume都是作廢過期的方法江兢,使用它們可能會產生不可預料的結果。
3). 使用interrupt方法中斷線程
- yield()方法
yield()方法的作用是放棄當前的CPU資源戚丸,將它讓給其他的任務划址,但放棄時間不確定,有可能剛剛放棄限府,馬上又獲得CPU時間片夺颤。 - 線程優(yōu)先級
在操作系統(tǒng)中,線程可以劃分優(yōu)先級胁勺,優(yōu)先級高的線程得到的CPU資源較多世澜,也就CPU有限執(zhí)行優(yōu)先級較高的線程對象中的任務。
設置優(yōu)先級有助于線程規(guī)劃期確定下次選擇哪一個線程來優(yōu)先執(zhí)行署穗,但是也并不是優(yōu)先級越高就一定越先執(zhí)行寥裂。
設置線程優(yōu)先級使用setPriority()方法,線程優(yōu)先級分為1~10這10個等級
Java中線程優(yōu)先級具有如下幾個特性
- 優(yōu)先級具有繼承性
在Java中案疲,線程的優(yōu)先級具有繼承性封恰,比如A線程啟動B線程,則B線程優(yōu)先級與A相同 - 優(yōu)先級具有規(guī)則性
MyThread myThread=new MyThread();
myThread.setPriority(10);
MyThread2 myThread2=new MyThread2();
myThread2.setPriority(1);
myThread2.start();
myThread.start();
雖然我是先啟動的myThread2褐啡,再啟動myThread诺舔,但是最先執(zhí)行完的總是myThread,因為他的優(yōu)先級較高,這是前幾次的輸出低飒,可以看到许昨,雖然先啟動的mythread2但是myThread的優(yōu)先級更高,所以搶到CPU的概率也越大
MyThread i=0
MyThread2 i=0
MyThread i=1
MyThread2 i=1
MyThread i=2
MyThread i=3
MyThread i=4
MyThread i=5
MyThread i=6
MyThread2 i=2
MyThread i=7
MyThread2 i=3
MyThread i=8
MyThread2 i=4
MyThread i=9
MyThread i=10
- 優(yōu)先級具有隨機性
優(yōu)先級高的并不一定每一次都先執(zhí)行完褥赊,不一定不一定哦~
- 守護線程
守護線程就是守護非守護線程的糕档。。emm拌喉。速那。典型的就是GC辣雞回收器線程