繼承Thread類實現多線程示例代碼:
class Thread1 extends Thread{
//使用構造函數給線程起名稱
Thread1(String name){
super(name);
}
public void run(){
for (int i = 0; i<60; i++)
System.out.println(this.getName()+" run"+i);
}
}
public class ThreadDemo1{
public static void main(String[] args) {
Thread1 thread1 = new Thread1("test1");
Thread1 thread2 = new Thread1("test2");
thread1.start();
thread2.start();
for (int i = 0; i<200; i++)
System.out.println("main run"+i);
}
}
多線程特性:隨機性
爲什麼要覆蓋run方法:
Thread類用於描述線程,該類定義了一個功能(run() run方法),用於存儲線程要運行的代碼
線程的五種狀態(如圖):
線程都有自己的默認名稱:Thread-編號 編號從0開始
static Thread currentThread():獲取當前線程對象
getName():獲取線程名稱
設置線程名稱方式:setName()或者構造函數
創建線程的第二種方式:實現Runnable接口
步驟:
1定義類實現Runnable接口
2覆蓋Runnable接口中的run方法(將線程所需要運行的代碼存放在該run方法中)
3通過Thread類建立對象
4將Runnable接口的子類對象做爲參數傳遞給Thread的構造函數(原因:自定義的run方法所屬對象是Runnable接口的子類對象,所以要讓線程去執行指定對象的run方法,就必須指明改run方法所屬對象)
5調用Thread類的start()開啓線程並調用Runnable接口子類的run方法
兩種方法的區別:
接口實現方式的好處:避免了單繼承的侷限性,建議使用(java只支持單繼承)
繼承Thread,線程代碼存放在Thread子類run方法中。實現Runnable代碼存放在接口實現類的run方法中
同步函數(格式 public synchronized void test(){})使用的鎖是this
靜態同步函數(格式 public static synchronized void test(){})使用的鎖是該方法所在類的字節碼對象類名.class(靜態進入內存時,內存中沒有對象,但是一定有該類對應的字節碼文件對象,類名.class)
知識點:
死鎖demo
1、 t1先運行,這個時候flag == true,先鎖定obj1,然後睡眠1秒鐘
2、 而t1在睡眠的時候,另一個線程t2啓動,flag == false,先鎖定obj2,然後也睡眠1秒鐘
3、 t1睡眠結束後需要鎖定obj2才能繼續執行,而此時obj2已被t2鎖定
4、 t2睡眠結束後需要鎖定obj1才能繼續執行,而此時obj1已被t1鎖定
5、 t1、t2相互等待,都需要得到對方鎖定的資源才能繼續執行,從而死鎖。
public class DeadLock implements Runnable{
private static Object obj1 = new Object();
private static Object obj2 = new Object();
private boolean flag;
public DeadLock(boolean flag){
this.flag = flag;
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "運行");
if(flag){
synchronized(obj1){
System.out.println(Thread.currentThread().getName() + "已經鎖住obj1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj2){
// 執行不到這裏
System.out.println("1秒鐘後,"+Thread.currentThread().getName()
+ "鎖住obj2");
}
}
}else{
synchronized(obj2){
System.out.println(Thread.currentThread().getName() + "已經鎖住obj2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj1){
// 執行不到這裏
System.out.println("1秒鐘後,"+Thread.currentThread().getName()
+ "鎖住obj1");
}
}
}
}
}
兩個線程操作統一資源情景,同步解決方案
概念:
notify() – 喚醒在此對象監視器上等待的單個線程。
notifyAll() – 喚醒在此對象監視器上等待的所有線程。
wait() – 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout) – 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout, int nanos) – 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量”,當前線程被喚醒(進入“就緒狀態”)
/**
* wait()、notify()、notifyAll()
* 這些方法都是用在同步中,因爲要對持有監視器(鎖)的線程操作,同步纔有鎖
* 這些方法在操作同步線程時,都必須要標識他們所操作的線程持有的鎖,在同一個鎖上的被等待線程,
* 可以被同一鎖上notify喚醒,不可以對不同鎖操作,等待、喚醒必須爲同一鎖
* 鎖可以是任意對象,所以定義在Object類中
*/
class Res{
String name;
String age;
boolean flag = false;
}
class Input implements Runnable{
//保證資源唯一 單例模式也可以
private Res res;
Input(Res res){
this.res = res;
}
@Override
public void run() {
int x = 0;
while(true) {
synchronized (res) {
try {
if(res.flag)//如果標記爲true 則不再賦值 等待outPut線程取值打印
res.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(x == 0) {
res.name = "tom";
res.age = "18";
} else {
res.name = "傑克";
res.age = "二十";
}
//得到 0、1
x = (x+1)%2;
res.flag = true;
res.notify();
}
}
}
}
class Output implements Runnable{
//保證資源唯一 單例模式也可以
private Res res;
Output(Res res){
this.res = res;
}
@Override
public void run() {
while(true) {
synchronized (res) {
try {
if(!res.flag)
res.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("name:"+res.name+",age:"+res.age);
res.flag = false;
res.notify();
}
}
}
}
public class InOutRunnable {
public static void main(String[] args) {
Res res = new Res();
Input in = new Input(res);
Output out = new Output(res);
Thread thread1 = new Thread(in);
Thread thread2 = new Thread(out);
thread1.start();
thread2.start();
}
}