在java中對于多線程的實作一定要有一個多線程的主類,多線程主類的實作是要有一定要求的
繼承Thread父類
在java.lang包中,子類繼承Thread類之後需要覆寫Thread類中的run方法,
run方法就是線程的主方法,特别需要注意的是所有的多線程的執行一定是并發完成的,即每個時間段上會有多個線程交替執行,是以為了達到這樣的目的絕對不能直接調用run方法,而是應該調用Thread隻能的start方法啟動多線程。
思考為什麼啟動多線程不使用run而非要使用start方法??
由于線程的啟動需要牽扯到作業系統的資源配置設定問題,是以具體的線程啟動應該要根據不同的作業系統有不同的實作,而JVM相當于根據系統中定義的start0()方法來根據不同的作業系統進行該方法的實作。
由于Thread類的start方法會調用start0()方法,是以隻有start()方法才能進行作業系統資源的配置設定
實作Runnable接口或者是 Callable接口
繼承Thread類會産生單繼承的局限操作,是以最好的方法是用接口,觀察Runnable的操作接口的定義結構
在java.lang中,是個函數式接口,隻有一個run方法,然而如果要啟動多線程隻能使用start方法,實作接口沒有start()方法,可以利用Thread的構造方法
public Thread(Runnable target)能夠接受Runnable對象
MyThread myThread1=new MyThread("線程1");
MyThread myThread2=new MyThread("線程2");
MyThread myThread3=new MyThread("線程3");
new Thread(myThread1).start();
new Thread(myThread2).start();
new Thread(myThread3).start();
很多時候為了友善可能直接使用匿名内部類或者lamada表達式
匿名内部類
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(i);
}
}
}).start();
Lamda
new Thread(()->{
for (int i = 0; i <10 ; i++) {
System.out.println(i);
}
}).start();
隻要給出的是函數式接口都可以使用Lamda表達式
多線程兩種實作方式的差別
首先觀察Thread類的定義結構:
public class Thread extends Onbject implements Runnable
可以發現Thread實作了Runnable接口。
Runnable接口實作的多線程要比Thread類實作的多線程要更友善的表示出資料共享的操作(賣票)
MyThread myThread=new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
操作的同一個票資料,實作了資料共享
Callable借口
從jdk1.5之後多了一個Callable接口,比Runnable接口強大之處在于可以傳回執行結果
這個接口在java.util.concurrent包中,結構如下
public interface Callable{
public V call() throws Exection
}
這個泛型表示的是傳回值類型,call()方法就是run()方法
問題是Thread類中沒有接受Callable對象的構造方法,為了啟動線程需要觀察繼承結構
image.png
Callable cal=new MyThread();
FutureTask task =new FutureTask(cal);//取得執行結果
Thread thread=new Thread(task);
thread.start();
System.out.println(task.get());
多線程的常用操作方法
線程的命名和取得
多線程的運作狀态不是固定的,要想确定線程的執行唯一的差別在于線程的名稱上。在起名的時候避免重名,或者避免修改名稱。
實作線程名稱的操作
構造方法:public Thread(Runnable target,String name
設定名字:public final void setName(String name)
取得名字:public String getName()
既然線程的執行本身是不确定的狀态,是以如果要取得線程名字的話。那麼唯一能做的就是取得目前的線程上的名字,是以在Thread類提供了這個方法:public static Thread currentThread()
如果在設定線程對象時沒有設定名字,會采取預設的名字
直接調用run()方法,會自動取名為main
線程休眠
如果要想讓某些線程延緩執行,可以使用休眠的方式來處理,休眠操作如下:
休眠方法:public static void sleep(long millis) throw InterruptedExection
這個異常表示,如果休眠的時間沒到就停止休眠了,那麼就會産生中斷異常,線程被中斷一定是被其他的線程中斷
線程優先級
從理論上來講,優先級越高的線程越有可能先執行,在Thread類中有一下的優先級方法
設定優先級:public final void setPriority(int newPriority);
取得優先級:public final int getPriority
對于優先級一共定義有三種
最高優先級:public static final int MAX_PRIORITY 10
中等優先級:public static final int NORM_PRIORITY 5
最低優先級:public static final int MIN_PRIORITY 1
主線程是中等優先級
線程的同步與死鎖
同步問題的引出
如果要想進行同步操作,多個線程通路同一個資源
正常代碼:
public class MyThread implements Runnable {
private int tickets=5;
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
if(this.tickets>0){
System.out.println(Thread.currentThread().getName()+",ticket="+this.tickets--);
}
}
}
}
MyThread mt=new MyThread();
Thread t1=new Thread(mt,"票販子A");
Thread t2=new Thread(mt,"票販子B");
Thread t3=new Thread(mt,"票販子C");
t1.start();
t2.start();
t3.start();
執行和結果沒有問題
票販子A,ticket=5
票販子B,ticket=3
票販子C,ticket=4
票販子B,ticket=1
票販子A,ticket=2
如果線程休眠,會出現問題
執行結果變為
票販子A,ticket=5
票販子C,ticket=3
票販子B,ticket=4
票販子A,ticket=2
票販子B,ticket=1
票販子C,ticket=0
票販子A,ticket=-1
解決不同步的問題,實作同步操作
實作鎖的概念使用同步代碼塊或同步方法
同步代碼塊
使用synchronized關鍵字定義的就是同步代碼塊,但是在進行同步的時候需要設定有一個同步對象,往往使用this
for (int i = 0; i <10 ; i++) {
synchronized (this){
if(this.tickets>0){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+",ticket="+this.tickets--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
加入同步代碼塊之後程式執行的速度變慢了。而且不像沒有同步的時候那樣多個線程一起進入到方法體中,異步的執行速度快于同步,是非線程不安全
同步方法
private int tickets = 5;
@Override
public void run() {
this.sale();
}
public synchronized void sale() {
for (int i = 0; i < 10; i++) {
if (this.tickets > 0) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ",ticket=" + this.tickets--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
死鎖(等待太多出現的問題)
死鎖是一種不确定的狀态,對于死鎖的操作,出現的越少越好
死鎖代碼示範
import java.util.concurrent.ExecutionException;
class QiangDao {
public synchronized void say(YouQianRen yqr) {
System.out.println("強盜說:給我3000萬美金");
yqr.get();
}
public synchronized void get() {
System.out.println("強盜得到了錢");
}
}
class YouQianRen {
public synchronized void say(QiangDao qd) {
System.out.println("有錢人說:先放了我兒子在給你錢");
qd.get();
}
public synchronized void get() {
System.out.println("孩紙被救回");
}
}
public class Main implements Runnable {
private QiangDao qd = new QiangDao();
private YouQianRen yqr = new YouQianRen();
public Main() {
new Thread(this).start();
yqr.say(qd);
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Main();
}
@Override
public void run() {
qd.say(yqr);
}
}
面試題:請問多個線程通路統一資源時會産生什麼問題,以及會産生什麼費附加問題。
多個線程通路統一資源必須考慮同步,可以使用synchronized 定義同步代碼塊或者同步方法。
程式中如果出現過多的同步那麼将會産生死鎖