知識點
1.線程概述
1.1 什麼是線程
線程是程序執行的一條路徑, 一個進程中可以包含多條線程
一個應用程序可以理解成就是一個進程
多線程併發執行可以提高程序的效率, 可以同時完成多項工作
1.2 多線程應用場景
VNC同時共享屏幕給多個電腦
迅雷開啓多條線程一起下載
QQ同時和多個人一起視頻
服務器同時處理多個客戶端請求
1.3並行和併發的區別
並行就是兩個任務同時運行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU)
併發是指兩個任務都請求運行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於間時間隔較短,使人感覺兩個任務都在運行(畫圖-任務調度)。
1.4 Java程序運行原理
Java命令會啓動java虛擬機(JVM),等於啓動了一個應用程序,也就是啓動了一個進程。
該進程會自動啓動一個 “主線程” ,然後主線程去調用某個類的 main 方法
一個應用程序有且只有一個主線程,程序員不能New主線程,可以New子線程。
1.5 JVM啓動的是多線程嗎?
JVM啓動至少啓動了垃圾回收線程和主線程,所以是多線程的。
main方法的代碼執行的位置就是在主線程(路徑)
一個進程有多個線程
finalize()這個方法在子線程(垃圾回收線程)執行
public class Demo01 {
public static void main(String[] args) {
/*JVM的啓動是多線程的嗎?【面試題】*/
System.out.println("AAAAA");
System.out.println("BBBBB");
System.out.println("CCCCC");
System.out.println("DDDDD");
//打印線程名稱
System.out.println(Thread.currentThread());//主線程
for(int i = 0;i<2;i++){
new Student();
System.gc();//啓動垃圾回收
}
}
}
class Student{
//被垃圾回收器回收時,會調用
//對象從內存釋放時,會調用
@Override
protected void finalize() throws Throwable {
// TODO Auto-generated method stub
System.out.println("student 被回收了...");
//打印線程名稱
System.out.println(Thread.currentThread());//子線程
}
}
2.Java中線程的實現方式(重點)
2.1方式一、繼承Thread
使用步驟:
1.定義類繼承Thread
2.重寫run方法
3.把新線程要做的事寫在run方法中
4.創建線程對象
5.開啓新線程, 內部會自動執行run方法
public class Demo01 {
public static void main(String[] args) {
/*主線程,程序員不能創建,程序員只能創建子線程*/
//1.創建子線程對象
MyThread t1 = new MyThread();
/**不能通過下面的方式來執行任務
* 因爲這種試的任務是在主線程執行的*/
//t1.run();
//2.正確的執行任務的方式,調用start,內部會開啓新線程,調用run方法
t1.start();
//3.再創建子線程
MyThread t2 = new MyThread();
t2.start();
//4.循環創建子線程
for(int i=0;i<10;i++){
MyThread th = new MyThread();
th.start();
}
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("銀行信用卡還款短信任務..." + Thread.currentThread());
System.out.println("線程名稱" + this.getName());
}
}
2.2方式二、實現Runnable接口
實現步驟:
1.定義類實現Runnable接口
2.實現run方法
3.把新線程要做的事寫在run方法中
4.創建自定義的Runnable的子類對象,創建Thread對象傳入Runnable
5.調用start()開啓新線程, 內部會自動調用Runnable的run()方法
public class Demo01 {
public static void main(String[] args) {
/* 線程實現的方式 (2) - 定義類實現Runnable接口
//1.創建runable對象
BankTask task = new BankTask();
//2.創建Thread對象
Thread t1 = new Thread(task);
//3.啓動線程
t1.start();
//4.再開啓2個線程
Thread t2 = new Thread(task);
t2.start();
Thread t3 = new Thread(task);
t3.start();
}
}
class BankTask implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("銀行儲蓄卡自動結算利息任務..." + Thread.currentThread());
//System.out.println("線程名稱:" + this.getName());
System.out.println("線程名稱:" +Thread.currentThread().getName());
}
}
2.3兩種方式的區別
區別:
繼承Thread : 由於子類重寫了Thread類的run(), 當調用start()時直接找子類的run()方法
實現Runnable : 構造函數中傳入了Runnable的引用, 有個成員變量記住了它, 調用run()方法時內部判斷成員變量Runnable的引用是否爲空。
繼承Thread
好處是:可以直接使用Thread類中的方法,代碼簡單
弊端是:如果已經有了父類,就不能用這種方法
實現Runnable接口
好處是:即使自己定義的線程類有了父類也沒關係,因爲有了父類也可以實現接口,代碼更靈活
弊端是:不能直接使用Thread中的方法,需要先獲取到線程對象後,才能得到Thread的方法,代碼複雜
2.4 匿名內部類實現線程的兩種方式
public static void main(String[] args) {
//匿名內部類實現線程的兩種方式
/*Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("任務1...." + Thread.currentThread());
}
};
t1.start();*/
new Thread(){
public void run() {
System.out.println("任務1...." + Thread.currentThread());
};
}.start();
/*Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("任務2...." + Thread.currentThread());
}
});
t2.start();*/
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("任務2...." + Thread.currentThread());
}
}).start();
}
2.5 獲取線程名字和設置名字
通過Thread的getName()方法獲取線程對象的名字
通過setName(String)方法可以設置線程對象的名字
通過構造函數可以傳入String類型的名字
每個線程系統都會默認分配個名字,主線程:main,子線程thread-0 …
public class Demo01 {
public static void main(String[] args) {
/* 獲取線程名字和設置名字(掌握)
//1.獲取主線程對象
Thread mainThread = Thread.currentThread();
System.out.println(Thread.currentThread());
System.out.println(mainThread);
System.out.println("名稱:" + mainThread.getName());
//2.設置線程的名稱
mainThread.setName("主線程");
System.out.println(mainThread);
//3.設置子線程的名稱
MyThread myThread = new MyThread("子線程1");
myThread.start();
}
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("銀行代發工資任務..." + Thread.currentThread());
}
}
2.6 獲取當前線程的對象
Thread.currentThread()方法用於獲取當前線程對象
在不同的方法中,獲取的線程對象名稱是有可能不一樣的
在main中獲取的是主線程對象
在子線程的run方法中獲取的是子線程對象
public class Demo01 {
public static void main(String[] args) {
//獲取當前線程的對象(掌握)
Thread mainThread = Thread.currentThread();
mainThread.setName("主線程");
//打印主線程對象
System.out.println(mainThread);
//打印主線程對象類名
System.out.println(mainThread.getClass());
System.out.println("================");
//開啓子線程
MyThread mt = new MyThread();
mt.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("任務...");
Thread subThread = Thread.currentThread();
//打印子線程對象
System.out.println(subThread);
//打印子線程對象類名
System.out.println(subThread.getClass().getName());
}
}
3.線程的其它方法
3.1線程休眠(掌握)
Thread.sleep(毫秒), 控制當前線程休眠若干毫秒
1秒= 1000毫秒
1秒 = 1000毫秒* 1000微妙 * 1000納秒(1000000000 )
主線程休眠
/*** 主線程休眠 */
public static void test1() {
for(int i=0;i<10;i++){
System.out.println(i);
//休眠【暫停】
try {
Thread.sleep(1000);//主線程休眠
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("AAAAAAAAAAAAAAAAAA");
}
子線程休眠
/**
* 子線程休眠
*/
public static void test2() {
//子線程休眠
new Thread(){
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread() + " " + i);
//休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}.start();
System.out.println("AAAAAAAAAAAAAAAA");
}
3.2守護線程(瞭解)
setDaemon(), 設置一個線程爲守護線程, 該線程不會單獨執行, 當其他非守護線程都執行結束後, 自動退出
特點:男守護女,女的死,男的也不想活了
3.3加入線程(瞭解)
join(), 當前線程暫停, 等待指定的線程執行結束後, 當前線程再繼續
join(int), 可以等待指定的毫秒之後繼續
3.4線程讓出(瞭解)
yield() 讓出cpu
3.5線程優先級
setPriority()設置線程的優先級
默認優先級是5,最小優先級1,最高優先級10
可以設置2,3,4
Thread裏面有靜態常量
開發幾乎不用,瞭解
4.線程與同步(重點)
什麼是同步
同步就是加鎖,不讓其它人訪問
synchronized指的就是同步的意思
什麼情況下需要同步
當多線程併發, 有多段代碼同時執行時, 我們希望某一段代碼執行的過程中CPU不要切換到其他線程工作. 這時就需要同步.
如果兩段代碼是同步的, 那麼同一時間只能執行一段, 在一段代碼沒執行結束之前, 不會執行另外一段代碼.
同步代碼塊
使用synchronized關鍵字加上一個鎖對象來定義一段代碼, 這就叫同步代碼塊
多個同步代碼塊如果使用相同的鎖對象, 那麼他們就是同步的
同步方法
使用synchronized關鍵字修飾一個方法, 該方法中所有的代碼都是同步的
非靜態同步函數的鎖是:this
靜態同步函數的鎖是:字節碼對象(xx.class)
5.鎖的總結
1.鎖問題:
同步中,鎖最好同一個對象,如果不是同一對象,還是會有線程安全問題
鎖:this,代表當前對象
鎖:如果 new 對象,就不是同一把鎖
鎖:字節碼對象 String.class,內存中,只有一個字節碼對象
開發中:一般都是this
2.在方法內部聲明synchronized的就是 “同步代碼塊”
3.在聲明方法的時候,添加 synchronized,就是同步方法
》如果是非靜態方法,鎖就是this
》如果是靜態方法,鎖就當前類的字節碼對象
//TicketTask.class
public static synchronized void test1(){}
4.同步使用的建議:
同步加鎖的時候,儘量讓鎖住的代碼範圍小一點,這樣可以讓其它線程等待時間少一點,性能高
6.死鎖
死鎖就是大家都抱着鎖,不釋放
public class Demo01 {
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
// TODO Auto-generated method stub
//死鎖(瞭解)
//多線程同步的時候, 如果同步代碼嵌套, 使用相同鎖, 就有可能出現死鎖
new Thread(){
public void run() {
while(true){
synchronized (s1) {
System.out.println("線程A 拿到" + s1 + " 等待" + s2);
synchronized (s2) {
System.out.println("線程A 拿到" + s2 + " 開動喫飯...");
}
}
}
};
}.start();
new Thread(){
public void run() {
while(true){
synchronized (s2) {
System.out.println("線程B 拿到" + s2 + " 等待" + s1);
synchronized (s1) {
System.out.println("線程B 拿到" + s1 + " 開動喫飯。。");
}
}
}
};
}.start();
}
}
7.回顧線程安全的類
Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
Vector是線程安全的,ArrayList是線程不安全的
StringBuffer是線程安全的,StringBuilder是線程不安全的
Hashtable是線程安全的,HashMap是線程不安全的
8.單例設計模式(重點)
8.1什麼是單例
保證類在內存中只有一個對象。
對象是new出來的,因此也就是說在程序中只能new一次對象
8.2單例實現的基本步驟
1》聲明一個類,類中有一個靜態屬性,類型與類名相同
2》把空參構造方法聲明爲私有
3》在類中提供一個公共靜態訪問方法來返回該對象實例
8.3單例的多種寫法
寫法一 餓漢式
class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
寫法二 懶漢式
class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
寫法三 另一種簡單
class Singleton{
public static final Singleton instance = new Singleton();
private Singleton(){}
}
8.4餓漢式和懶漢式的區別
餓漢式是空間換時間,懶漢式是時間換空間
在多線程訪問時,餓漢式不會創建多個對象,而懶漢式有可能會創建多個對象
如果考慮線程安全問題,用餓漢式
如果不考慮線程安全問題,用懶漢式
8.5Runtime類的使用
Runtime類是一個單例類
每個 Java 應用程序都有一個 Runtime 類實例,使應用程序能夠與其運行的環境相連接。通過 getRuntime 方法獲取當前運行時。
案例:自動關機
Runtime r = Runtime.getRuntime();
r.exec(“shutdown -s -t 300”);//300秒後關機
r.exec(“shutdown -a”); //取消關機
9. Timer定時器
Timer一種工具,用於在後臺線程中執行的任務。可安排任務執行一次,或者定期重複執行。
方法
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task, long delay, long period)
public void schedule(TimerTask task, Date firstTime, long period)
public class Demo01 {
public static void main(String[] args) {
//Timer(計時器,定時器)
/* 一種工具,用於在後臺線程中執行的任務。可安排任務執行一次,或者定期重複執行。
方法
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task, long delay, long period)
指定時間執行任務
public void schedule(TimerTask task, Date firstTime, long period)*/
test3();
}
public static void test3() {
/**定時器的細節
* 1.定時器在子線程中執行
* 2.timer.cancel(); 取消定時器
*/
Timer timer = new Timer();
timer.schedule(new TimerTask() {
int count = 5;
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("任務A:" + count +"..." + Thread.currentThread());
count --;
if(count == 0){
//取消定時器
timer.cancel();
}
}
}, 1000,2000);
//timer.cancel();//主線程
}
public static void test2() {
//3秒後執行任務,每隔兩秒重複執行任務
Timer timmer = new Timer();
timmer.schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("任務A....." + new Date());
}
}, 3000, 2000);
}
//3秒後執行任務
public static void test1() {
//1.創建定時器
Timer timmer = new Timer();
//2.執行任務
/**
* 1.3秒後執行任務
* 2.任務執行完後,程序沒有退出
*/
timmer.schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("任務A....");
}
}, 3000);
}
}
10.線程間的通訊(重點)
10.1什麼時候需要通信
多個線程併發執行時, 在默認情況下CPU是隨機切換線程的,如果我們希望他們有規律的執行, 就可以使用通信, 例如每個線程執行一次打印
10.2線程怎麼通信
》如果希望線程等待, 就調用wait()
》如果希望喚醒等待的線程, 就調用notify();
notify是隨機喚醒一個線程
notifyAll是喚醒所有線程
》這兩個方法必須在同步代碼中執行, 並且使用同步鎖對象來調用
》如果方法中沒有同步鎖,會有異常IllegalMonitorStateException
10.3線程通訊的一些疑問
1.在同步代碼塊中,用哪個對象鎖,就用哪個對象調用wait方法
2.爲什麼wait方法和notify方法定義在Object這類中?
因爲鎖對象可以是任意對象,Object是所有的類的基類,所以wait方法和notify方法需要定義在Object這個類中
3.sleep方法和wait方法的區別?
》sleep方法必須傳入參數,參數就是時間,時間到了自動醒來
》wait方法可以傳入參數也可以不傳入參數,傳入參數就是在參數的時間結束後等待,不傳入參數就是直接等待
》sleep方法在同步函數或同步代碼塊中,不釋放鎖,睡着了也抱着鎖睡
》wait方法在同步函數或者同步代碼塊中,釋放鎖
11.JDK1.5新特性互斥鎖(重點)
11.1ReentrantLock介紹
使用ReentrantLock類也可以實現同步加鎖
ReentrantLock叫[互斥鎖],使用lock()和unlock()方法進行同步
11.2使用ReentrantLock類使用要點
使用ReentrantLock類的newCondition()方法可以獲取Condition對象
需要等待的時候使用Condition的await()方法, 喚醒的時候用signal()方法
不同的線程使用不同的Condition, 這樣就能區分喚醒的時候找哪個線程了
12.線程組
12.1概述
1.Java中使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。
2.默認情況下,所有的線程都屬於主線程組。
3.public final ThreadGroup getThreadGroup() 通過線程對象獲取他所屬於的組
4.public final String getName() 通過線程組對象獲取組的名字
5.我們也可以給線程設置分組ThreadGroup(String name) 創建線程組對象並給其賦值名字
12.2創建線程對象
Thread(ThreadGroup?group, Runnable?target, String?name)
12.3代碼演示
/**
* 掌握:
* 1.如何獲取一個線程所屬的線程組
* 2.如果在創建一個子線程時,設置它所屬的線程組
* @author gyf
*
*/
public class Demo01 {
public static void main(String[] args) {
//主線程
Thread mainThread = Thread.currentThread();
/**
* [main,5,main]
* main:線程名稱
* 5:代先級
* main:當前線程所屬的組名
*/
System.out.println("線程:" + mainThread);
//獲取線程的“線程組”對象
ThreadGroup tg = mainThread.getThreadGroup();
System.out.println("線程組:" + tg.getName());
//創建子線程
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("線程A...");
}
};
//t1.start();
System.out.println("t1子線程的線程組:" + t1.getThreadGroup());
//創建一個線程組
ThreadGroup abcGroup = new ThreadGroup("abc組");
//創建子線程對象
Thread t2 = new Thread(abcGroup, new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("線程B");
}
});
System.out.println("t2子線程的線程組:" + t2.getThreadGroup());
}
}
13.線程池
13.1線程池概述
程序啓動一個新線程成本是比較高的,因爲它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。線程池裏的每一個線程代碼結束後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用。在JDK5之前,我們必須手動實現自己的線程池,從JDK5開始,Java內置支持線程池
13.2Java的內置線程池
1.JDK5新增了一個Executors工廠類來產生線程池,有如下幾個方法
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
- 這些方法的返回值是ExecutorService對象,該對象表示一個線程池,
可以執行Runnable對象或者Callable對象代表的線程。
它提供瞭如下方法
Future<?> submit(Runnable task)
Future submit(Callable task)
3.使用步驟:
1.創建線程池對象
2.創建Runnable實例
3.提交Runnable實例
4.關閉線程池es.shutdown();
4.Runnable和Callable的區別
Runnable的run方法沒有返回值
Callable的call方法有返回值,一般返回值也沒用
13.3使用演示
public class Demo01 {
public static void main(String[] args) {
//案例:10個線程完成10個洗車任務
/*for(int i = 0;i<10;i++){
new Thread(){
public void run() {
System.out.println("洗車任務 " + Thread.currentThread());
};
}.start();
}*/
//案例:5個線程完成10個洗車的任務
//1.創建線程池
ExecutorService es = Executors.newFixedThreadPool(5);
//2.添加任務-方式一
/*for(int i=0;i<10;i++){
es.submit(new Runnable() {
@Override
public void run() {
System.out.println("洗車任務 " + Thread.currentThread());
}
});
}*/
//3.添加任務-方式二
for(int i=0;i<10;i++){
es.submit(new MyTask());
}
}
}
class MyTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("洗車任務 " + Thread.currentThread());
return 110;
}
}
14.線程的五種狀態
新建,就緒,運行,阻塞,死亡
練習題
1.賣火車票
需求,有A\B\C\D4個窗口同時買票,只有100張票可買
多線程會有安全問題熟記
public class Demo01 {
public static void main(String[] args) {
//同步代碼塊和同步方法
//火車站賣票【問題】
/**
* 湖南到廣州火車票:今天13:00 ,100張
* 火車站有4個窗口在同時賣票,要保證一張票只能被賣一次
*
* 搞4個線程表示4個窗口
*
* 通過加鎖可以解決被多次賣同一張票的問題
*
* 使用同步代碼塊
*/
//創建賣票的任務
TicketTask task = new TicketTask();
//A窗口
Thread t1 = new Thread(task);
t1.setName("窗口A");
//B窗口
Thread t2 = new Thread(task);
t2.setName("窗口B");
//C窗口
Thread t3 = new Thread(task);
t3.setName("窗口C");
//D窗口
Thread t4 = new Thread(task);
t4.setName("窗口D");
//開啓線程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketTask implements Runnable{
//只有100張票
int ticket = 100;
@Override
public void run() {
// TODO Auto-generated method stub
/**
* 同步代碼換括號裏參數可以傳任意對象
* this是一個鎖對象
* 不同的一把鎖,賣相同的票總是還是存在
*/
//賣票
while(true){
synchronized (this) {
if(ticket <= 0){
System.out.println("不好意思,票已經賣完了...");
break;
}else{
System.out.println(Thread.currentThread() + "恭喜你賣到票,票號" + ticket);
ticket --;
}
}
}
}
/*@Override
public void run() {
// TODO Auto-generated method stub
*//**
* 同步代碼換括號裏參數可以傳任意對象
*//*
synchronized (this) {
//賣票
while(true){
if(ticket <= 0){
System.out.println("不好意思,票已經賣完了...");
break;
}else{
System.out.println(Thread.currentThread() + "恭喜你賣到票,票號" + ticket);
ticket --;
}
}
}
}*/
}
2.兩個線程間的通訊
public class Demo01 {
public static void main(String[] args) {
//1.創建任務對象
MyTask task = new MyTask();
//2.開啓兩個線程執行2個任務
new Thread(){
public void run() {
while(true){
try {
task.task1();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}.start();
new Thread(){
public void run() {
while(true){
try {
task.task2();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}.start();
}
}
class MyTask{
//標識 1:可以執行任務1,2:可以執行任務2
int flag = 1;
public synchronized void task1() throws InterruptedException{
if(flag != 1){
this.wait();//當前線程等待
}
System.out.println("1.銀行信用卡自動還款任務...");
flag = 2;
this.notify();//喚醒其它線程
}
public synchronized void task2() throws InterruptedException{
if(flag != 2){
this.wait();//線程等待
}
System.out.println("2.銀行儲蓄卡自動結算利息任務...");
flag = 1;
this.notify();//喚醒其它線程
}
}
3.三個線程間的通訊(同步鎖實現有問題,少數沒按順序)
public class Demo01 {
public static void main(String[] args) {
//三個線程間的通訊
MyTask task = new MyTask();
new Thread(){
public void run() {
while(true){
try {
task.task1();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}.start();
new Thread(){
public void run() {
while(true){
try {
task.task2();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}.start();
new Thread(){
public void run() {
while(true){
try {
task.task3();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}.start();
}
}
class MyTask{
//標識 1:可以執行任務1,2:可以執行任務2, 3:可以執行任務3
int flag = 1;
public synchronized void task1() throws InterruptedException{
if(flag != 1){
this.wait();//當前線程等待
//this.wait(timeout);
}
System.out.println("1.銀行信用卡自動還款任務...");
flag = 2;
//this.notify();//喚醒隨機線程
this.notifyAll();//喚醒所有等待線程
}
public synchronized void task2() throws InterruptedException{
if(flag != 2){
this.wait();//線程等待
}
System.out.println("2.銀行儲蓄卡自動結算利息任務...");
flag = 3;
//this.notify();//喚醒其它線程
this.notifyAll();
//Thread.sleep(millis);
}
public synchronized void task3() throws InterruptedException{
if(flag != 3){
this.wait();//線程等待
}
System.out.println("3.銀行短信提醒任務...");
flag = 1;
//this.notify();//喚醒其它線程
this.notifyAll();
}
}
4.三個線程間的通訊(使用互斥鎖,解決同步鎖少數沒按順序的問題)
/**
* 互斥鎖的使用步驟
* 1.創建互斥鎖對象
* 2.創建3個Condition
* 3.加鎖、解鎖
* 4.線程等待-Condition的await方法
* 5.線程喚醒-Condition的signal方法
* @author gyf
*
*/
class MyTask{
//創建互斥鎖對象
ReentrantLock rl = new ReentrantLock();
//創建3個Condition
Condition c1 = rl.newCondition();
Condition c2 = rl.newCondition();
Condition c3 = rl.newCondition();
//標識 1:可以執行任務1,2:可以執行任務2, 3:可以執行任務3
int flag = 1;
public void task1() throws InterruptedException{
rl.lock();//加鎖
if(flag != 1){
c1.await();//當前線程等待
}
System.out.println("1.銀行信用卡自動還款任務...");
flag = 2;
//指定喚醒線程2
c2.signal();
rl.unlock();//解鎖
}
public void task2() throws InterruptedException{
rl.lock();
if(flag != 2){
c2.await();//線程等待
}
System.out.println("2.銀行儲蓄卡自動結算利息任務...");
flag = 3;
//喚醒線程3
c3.signal();
rl.unlock();
}
public void task3() throws InterruptedException{
rl.lock();
if(flag != 3){
c3.await();//線程等待
}
System.out.println("3.銀行短信提醒任務...");
flag = 1;
//喚醒線程1
c1.signal();
rl.unlock();
}
}
總結
通過線程一天的學習,會使用了兩種創建線程的方法,一種是繼承Thread類,另一種是實現Runnable接口(兩種方法都可以使用匿名內部類直接創建)。然後就是線程的其他方法,例如休眠、守護、加入等。掌握了同步代碼塊、同步方法關鍵字synchronized,在使用同步做案例:三個線程任務時,發現順序會出問題,於是學習了互斥鎖ReentrantLock,有效的實現了多線程調度的有序性。後面對線程池有了瞭解。記住,多線程會有安全問題。