Java多線程技術概述
介紹多線程之前要介紹線程,介紹線程則離不開進程。
首先 ,
進程 :是一個正在執行中的程序,每一個進程執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元;
線程:就是進程中的一個獨立控制單元,線程在控制着進程的執行。一個進程中至少有一個進程。
多線程:一個進程中不只有一個線程。
一、線程與進程
進程:通俗來解釋就是一個程序,一個App,打開任務管理器可以看到當前電腦中正在運行的進程。
線程:一個進程中一般包含多個線程,打開任務管理器也可以看到當前電腦中正在運行的線程每個各自執行自己的任務來實現進程的運行,當一個進程中的最后一個線程結束時,整個進程就結束了。
線程的6種狀態:
- NEW(未啟動的線程)
- RUNNABLE(執行的線程)
- BLOCKED(被阻塞的線程)
- WAITING(無限期等待的線程)
- TIMED_WAITING(有限期等待的線程)
- TERMINATED(已結束的線程)
二、Java中線程創建的三種方式
1.Thread類: 通過創建一個類來繼承Thread類,在這個類中重寫run()方法,通過這個類創建的對象就是一個線程。
class MyThread extends Thread{
@Override
public void run() {
//執行的Java語句
}
}
public static void main(String[] args) {
MyThread t = new MyThread();
//線程啟動
t.start();
}
2.Runnable接口:通過創建一個類來實現Runnable接口,在這個類中重寫run()方法,通過這個類創建的對象是一個線程任務,我們將這個任務傳給一個Thread對象即可執行這個線程任務。(推薦使用這種方式,傳入一個Runnable任務即可執行線程,跟使用線程池有關)
class MyRunnable implements Runnable{
@Override
public void run() {
//執行的Java語句
}
}
public static void main(String[] args) {
MyRunnable r = new MyRunnable1();
Thread t = new Thread(r);
t.start();
}
3.Callable接口(很少用的方式):創建方式與Runnable相似。創建此類線程會產生一個返回值,如果主程序要獲取這個返回值,則主程序會在Callable線程運行結束后再運行,不獲取的話,則兩個線程並行。
三、線程中一些常用的方法
Thread的常用方法
1.String getName() //獲取該線程的名字
2.void setName(String name) //設置該線程的名字
3.void start() //線程開始
4.static Thread currentThread() //獲取當前線程對象
5.static void sleep(long millis) //讓當前線程休眠,進入阻塞狀態,傳入的參數單位為毫秒
6.void setDaemon(boolean on) //將線程設置為守護線程或者用戶線程
7.void interrupt() //中斷此線程
8.int getPriority() //返回此線程的優先級
9.void setPriority(int newPriority) //更改此線程的優先級
10.Thread(Runnable target) //(構造方法)傳入一個Runnable任務
11.Thread(String name) //(構造方法)為線程取一個名字
1、interrupt方法和stop方法
線程在運行的過程中兩種中斷線程的方法,一個是stop()方法,一個是interrupt()方法。
Sun公司在JDK1.2版本的時候將stop()方法改為過時了,因為這種中斷線程的方式是在外部強制的,這可能會導致在中斷過程數據丟失,所以是不安全的。
使用interrupt()方法則是一種安全的方式,當在線程外部調用interrupt()方法時,JVM會給運行的線程傳遞一個異常對象,當線程接收到異常對象時,線程就會終止。
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
t.interrupt();
}
2、守護線程和用戶線程的設置
void setDaemon(boolean on)
使用setDaemon()方法可以將一個線程設置為守護線程或者用戶線程(參數為TRUE時,是守護線程,參數為FALSE時為用戶線程),一個線程被創建時默認是用戶線程。
當用戶線程和守護線程一起運行,當用戶線程結束時,則守護線程就結束。
四、線程安全
線程異步:即多條線程同時執行一個任務時,這種情況下往往是出現數據錯亂的情況,例如兩個人同時對一個銀行賬戶進行取錢,賬戶中有10000元,兩個人同時取走5000元,結果賬戶中還剩余5000元。所以這種線程異步的情況是非常不安全的。
線程同步:即多條線程同時執行一個任務時,,當一個線程開始執行任務線程后,為這個任務加鎖,其他線程等待次線程執行完任務線程后再進行搶奪執行任務的時間片。
1、實現線程安全的方法(以售票窗口買票為例)
1.1、synchronized方法(顯式鎖)
同步代碼塊
當一個售票窗口在賣票時,其他窗口等待。
語法:
synchronized (加鎖對象){
//Java語句
}
class Runnable implements Runnable{
private int count = 10;
private Object o = new Object();
public void run() {
while(true){
synchronized (o){
if(count>0){
System.out.println(Thread.currentThread().getName()+"買票中");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("余票:"+count);
}else{
break;
}
}
}
}
}
同步方法
當時用同步方法時,當前加鎖的對象默認為當前對象this
class Runnable implements Runnable{
private int count = 10;
public void run() {
while(true){
boolean flag = sale();
if(!flag){
break;
}
}
}
public synchronized boolean sale(){
//判斷票數是否大於0,是返回true,否返回false
if(count>0){
System.out.println(Thread.currentThread().getName()+"買票中");
try {
//此處休眠0.5秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票賣出,票數減一
count--;
System.out.println("余票:"+count);
return true;
}else{
return false;
}
}
}
1.2、Lock方法(隱式鎖)
使用Lock方法需要創建Lock對象,並在需要加鎖是手動加鎖,在需要解鎖時手動解鎖
class Runnable implements Runnable{
private int count = 10;
private Lock l = new ReentrantLock();
public void run() {
while(true){
l.lock();
if(count>0){
System.out.println(Thread.currentThread().getName()+"買票中");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("余票:"+count);
l.unlock();
}else{
l.unlock();
break;
}
}
}
}
1.3、顯式鎖和隱式鎖的區別
(1)Sync:Java中的關鍵字,是由JVM來維護的。是JVM層面的鎖。
(2)Lock:是JDK5以后才出現的具體的類。使用lock是調用對應的API。是API層面的鎖。
(3)在使用sync關鍵字的時候,我們使用者根本不用寫其他的代碼,然后程序就能夠獲取和釋放鎖了。那是因為當sync代碼塊執行完成之后,系統會自動的讓程序釋放占用的鎖。Sync是由系統維護的,如果非邏輯問題的話話,是不會出現死鎖的。
(4)在使用lock的時候,我們使用者需要手動的獲取和釋放鎖。如果沒有釋放鎖,就有可能導致出現死鎖的現象。手動獲取鎖方法:lock.lock()。釋放鎖:unlock方法。需要配合tyr/finaly語句塊來完成。
(5)Sync是不可中斷的。除非拋出異常或者正常運行完成
(6)Lock可以中斷的。
(7)Sync:非公平鎖
(8)lock:兩者都可以的。默認是非公平鎖。ReentrantLock(boolean fair),true是公平鎖,false是不公平鎖
五、死鎖
概述
A和B兩人分別進入試衣間1和試衣間2,A想等B出來后去試衣間2,自己則在試衣間1中等待,B想等A出來后去試衣間1,自己則在試衣間2中等待,最終2個人都在等對方出來,但是對方都不出來,導致一直僵持。
public class DeadLockTest {
public static void main(String[] args) {
A a = new A();
B b = new B();
//子線程中需要和主線程中同樣的A和B對象
R r = new R(a,b);
Thread t = new Thread(r);
t.start();
b.say(a);
}
}
class B{
public synchronized void say(A a){
System.out.println("BBBBB");
a.reply();
}
public synchronized void reply(){
System.out.println("bbbbb");
}
}
class A{
public synchronized void say(B b){
System.out.println("AAAAA");
b.reply();
}
public synchronized void reply(){
System.out.println("aaaaa");
}
}
class R implements Runnable{
private A a;
private B b;
public R(A a, B b) {
this.a = a;
this.b = b;
}
public void run() {
a.say(b);
}
}
六、生產者與消費者
當廚師在做菜時,服務員休息狀態,當廚師做完菜后,廚師休息,服務員端菜出去,等服務員端空盤子回來后,服務員繼續休息,叫廚師繼續做菜。依次循環
public class Test {
public static void main(String[] args) {
Food f = new Food();
Cook c = new Cook(f);
Waiter w = new Waiter(f);
//廚師線程
new Thread(c).start();
//服務員線程
new Thread(w).start();
}
}
class Cook implements Runnable{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
f.cook(i);
try {
//此處休眠0.5秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Waiter implements Runnable{
private Food f;
private boolean flag = true;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
f.get();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Food{
private String name;
private String tasty;
private boolean flag = true;
public Food() {
}
public Food(String name, String tasty) {
this.name = name;
this.tasty = tasty;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTasty() {
return tasty;
}
public void setTasty(String tasty) {
this.tasty = tasty;
}
//廚師做菜
public synchronized void cook(int i){
if(flag){
if(i % 2 == 0){
this.setName("番茄炒蛋");
this.setTasty("咸");
}else{
this.setName("糖醋排骨");
this.setTasty("酸甜");
}
System.out.println("廚師做菜:"+this.getName()+",味道:"+this.getTasty());
}
flag = false;
//喚醒其他線程
this.notifyAll();
try {
//廚師線程休眠
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//服務員端菜
public synchronized void get(){
if(!flag){
System.out.println("服務員出菜:"+this.getName()+",味道:"+this.getTasty());
}
flag = true;
//喚醒其他線程
this.notifyAll();
try {
//服務員線程休眠
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
七、線程池(了解)
概述
當程序中需要執行許多的內容很少的線程時,線程創建所花費的時間就會多於每次線程運行的所耗費的時間,就是導致程序的效率大大降低。使用線程池的方法就可以為程序節省下創建線程的時間。
線程池的種類
ExecutorService service = Executors.創建方法
void execute(Runnable command) //指揮線程池執行任務
1.緩存線程池
任務線程傳入時自動分配線程,線程不夠時自動創建新線程
Executors.newCachedThreadPool() //創建緩存線程池
2.定長線程池
指定線程池線程的個數,任務線程傳入時自動分配線程,線程不夠時剩余任務線程排隊等待線程池中的線程執行完畢
Executors.newFixedThreadPool(int nThreads) //創建定長線程池,傳入線程池中線程的個數
3.單線程線程池
線程池中只有一個線程,任務線程傳入時自動分配線程,一個任務執行時剩余任務線程排隊等待線程池中的線程執行完畢
Executors.newSingleThreadExecutor() //創建單線程線程池
4.周期定長線程池
指定線程池線程的個數,任務線程傳入時自動分配線程,可以設定任務線程第一次執行的延時時間和之后每次執行的間隔時間
Executors.newScheduledThreadPool(int corePoolSize)
//創建周期定長線程池,傳入線程池中線程的個數
service.schedule(Runnable command, long delay, TimeUnit unit)
//線程定時執行,傳入任務、時長和時長單位
service.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
//周期性定時執行,傳入任務,初次執行延時時長,周期間隔時長和時長單位
最后
歡迎關注公眾號:前程有光,領取一線大廠Java面試題總結+各知識點學習思維導+一份300頁pdf文檔的Java核心知識點總結!