1. 程式人生 > 實用技巧 >java執行緒基礎知識整理

java執行緒基礎知識整理

目錄

執行緒基本概念

1、java實現執行緒

2、執行緒的生命週期

3、執行緒常用的方法

3.1、sleep()

3.2、interrupt方法

3.3、stop方法

4、執行緒排程

4.1、常見的執行緒排程模型

4.2、java中提供的執行緒排程方法

4.3、執行緒讓步

4.4、執行緒合併

4.5、執行緒安全

4.5.1、執行緒同步的實現

4.5.2、java中的執行緒安全性

4.5.3、synchronized總結

4.5.4、死鎖

4.6、守護執行緒

4.6.1、守護執行緒的特點

4.6.2、一個簡單的守護執行緒的例子

5、定時任務

5.1、實現一個定時器

6、通過Callable介面實現一個執行緒

7、Object類中的wait和notify方法

7.1、wait和notify方法介紹

7.2、生產者和消費者模式

7.3、實現奇偶數的交替輸出


執行緒基本概念

1、什麼是程序?什麼是執行緒?

程序是一個應用程式,執行緒是一個程序中的執行場景/執行單元。一個程序可以啟動多個執行緒。在java語言中對於兩個執行緒A和B,堆記憶體和方法區記憶體共享。但是棧記憶體獨立,一個執行緒一個棧。在使用了多執行緒機制之後,main()方法結束了,只是主執行緒結束了,主棧空了,但其他執行緒不一定結束,其他棧(執行緒)可能還在壓棧彈棧。

1、java實現執行緒

java語言支援多執行緒機制。並且java已經實現了多執行緒(java.lang.Thread類和java.lang.Runnable介面)

第一種實現方式(繼承java.lang.Thread類並重寫run方法)

  1. public class Thread_01 extends Thread {
  2. @Override
  3. public void run() {
  4. super.run();
  5. System.out.println("第一個執行緒");
  6. }
  7. public static void main(String[] args) {
  8. Thread thread=new Thread(new Thread_01(),"first_thread"); //建立執行緒物件
  9. thread.start(); //啟動執行緒 start方法使執行緒處於就緒佇列,等待CPU呼叫
  10. }
  11. }

第二種實現方式(實現java.lang.Runnable介面並實現run方法)

  1. public class Thread_02 implements Runnable {
  2. @Override
  3. public void run() {
  4. System.out.println("第一個執行緒");
  5. }
  6. public static void main(String[] args) {
  7. Thread thread=new Thread(new Thread_01(),"first_thread"); //建立執行緒物件
  8. thread.start(); //啟動執行緒 start方法使執行緒處於就緒佇列,等待CPU呼叫
  9. }
  10. }

通常使用第二種方法,因為一個類實現了介面還可以繼承其他類

注意:start方法和run方法的區別!!!

run方法不會啟動執行緒。

start方法的作用是:啟動一個執行緒,在JVM中為執行緒開闢一個新的棧空間。之後start方法就結束了。執行緒啟動成功並進入排隊等待序列。等到被CPU呼叫到,就會自動呼叫run方法。

2、執行緒的生命週期

3、執行緒常用的方法

3.1、sleep()

public static native void sleep(long millis) throws InterruptedException;

作用:讓執行緒進入休眠,進入“阻塞狀態”。放棄佔有CPU時間片,讓其他執行緒使用。

  1. public class Thread_03 {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread thread=new Thread(new Thread_03_1(),"Thread_03_1");
  4. thread.start();
  5. int count=1; //計數器
  6. while(true){
  7. System.out.println("Thread_03_1執行緒沉睡了"+count+++"秒");
  8. Thread.sleep(1000);
  9. if(count>5){
  10. break;
  11. }
  12. }
  13. }
  14. }
  15. class Thread_03_1 implements Runnable{
  16. @Override
  17. public void run() {
  18. try {
  19. Thread.sleep(1000*5); //讓執行緒沉睡(等待) 5秒
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. System.out.println(Thread.currentThread().getName());
  24. }
  25. }

3.2、interrupt方法

interrupt方法可以中斷執行緒的睡眠,依靠了java異常處理機制

  1. public class Thread_03 {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread thread=new Thread(new Thread_03_1(),"Thread_03_1");
  4. thread.start();
  5. int count=1; //計數器
  6. while(true){
  7. System.out.println("Thread_03_1執行緒沉睡了"+count+++"秒");
  8. Thread.sleep(1000);
  9. if(count==3){
  10. System.out.println("打斷Thread_03_1睡眠");
  11. thread.interrupt();
  12. break;
  13. }
  14. }
  15. }
  16. }
  17. class Thread_03_1 implements Runnable{
  18. @Override
  19. public void run() {
  20. try {
  21. Thread.sleep(1000*5); //讓執行緒沉睡(等待) 5秒
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println(Thread.currentThread().getName()+"醒了");
  26. }
  27. }

執行緒Thread_03_1原計劃沉睡5秒,在它睡到3秒時,使用interrupt方法打斷其睡眠。

3.3、stop方法

stop方法可以強制終止一個執行緒的執行。不過這種方式容易丟失資料。因為這種方式會直接殺死執行緒,執行緒沒有儲存的資料會丟失。所以不建議使用

  1. public class Thread_04 {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread thread = new Thread(new Thread_04_1(), "Thread_03_1");
  4. thread.start();
  5. //執行緒Thread_03_1執行5秒後,強制結束執行緒Thread_03_1
  6. Thread.sleep(1000*6);
  7. System.out.println("強制終止執行緒Thread_03_1");
  8. thread.stop(); //強制終止執行緒
  9. }
  10. }
  11. class Thread_04_1 implements Runnable{
  12. @Override
  13. public void run() {
  14. for(int i=1;i<1000;i++) {
  15. try {
  16. Thread.sleep(1000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println(Thread.currentThread()+"-->"+i+"秒");
  21. }
  22. }
  23. }

建議使用如下方法結束一個執行緒:線上程類中增加一個布林型別的變數run,通過改變run的值,來控制執行緒執行/停止狀態。

  1. public class Thread_05 {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread_05_1 t=new Thread_05_1();
  4. Thread thread = new Thread(t, "Thread_05_1");
  5. thread.start();
  6. //等候5秒之後,終止該執行緒
  7. Thread.sleep(1000*5);
  8. t.run=false;
  9. System.out.println(thread.getName()+"執行緒已暫停");
  10. }
  11. }
  12. class Thread_05_1 implements Runnable{
  13. boolean run=true; //通過引入一個布林型別的變數,來標記該執行緒的狀態(執行/停止)
  14. @Override
  15. public void run() {
  16. for (int i = 1; i < 1000; i++) {
  17. if (run) {
  18. System.out.println(Thread.currentThread().getName() + "-->" + i + "秒");
  19. try {
  20. Thread.sleep(1000);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }else{
  25. /**
  26. * return就表示該執行緒結束了
  27. * 如果有什麼需要儲存的,可以寫在return之前
  28. */
  29. return;
  30. }
  31. }
  32. }
  33. }

4、執行緒排程

4.1、常見的執行緒排程模型

搶佔式排程模型、均分式排程模型

4.2、java中提供的執行緒排程方法

  1. void setPriority(int newPriority) //設定執行緒優先順序
  2. int getPriority() //獲取執行緒優先順序

最低優先順序:1 預設優先順序:5 最高優先順序:10

優先順序高的執行緒搶佔的CPU時間片就多一些,處於執行狀態的時間片就多一些

  1. public class Thread_06 {
  2. public static void main(String[] args) {
  3. Thread_06_1 t61=new Thread_06_1();
  4. Thread_06_2 t62=new Thread_06_2();
  5. Thread_06_3 t63=new Thread_06_3();
  6. Thread thread1=new Thread(t61,"Thread_06_1");
  7. Thread thread2=new Thread(t62,"Thread_06_2");
  8. Thread thread3=new Thread(t63,"Thread_06_3");
  9. //設定執行緒優先順序
  10. thread1.setPriority(1); thread2.setPriority(2); thread3.setPriority(10);
  11. thread1.start(); thread2.start(); thread3.start();
  12. System.out.println("主執行緒優先順序為:"+Thread.currentThread().getPriority());
  13. for(int i=0;i<1000;i++){
  14. System.out.println(Thread.currentThread().getName()+"-->"+i);
  15. }
  16. }
  17. }
  18. class Thread_06_1 implements Runnable{
  19. @Override
  20. public void run() {
  21. System.out.println(Thread.currentThread().getName()+"優先順序為:"+Thread.currentThread().getPriority());
  22. for(int i=0;i<1000;i++){
  23. System.out.println(Thread.currentThread().getName()+"-->"+i);
  24. }
  25. }
  26. }
  27. class Thread_06_2 implements Runnable{
  28. @Override
  29. public void run() {
  30. System.out.println(Thread.currentThread().getName()+"優先順序為:"+Thread.currentThread().getPriority());
  31. for(int i=0;i<1000;i++){
  32. System.out.println(Thread.currentThread().getName()+"-->"+i);
  33. }
  34. }
  35. }
  36. class Thread_06_3 implements Runnable{
  37. @Override
  38. public void run() {
  39. System.out.println(Thread.currentThread().getName()+"優先順序為:"+Thread.currentThread().getPriority());
  40. for(int i=0;i<1000;i++){
  41. System.out.println(Thread.currentThread().getName()+"-->"+i);
  42. }
  43. }
  44. }

4.3、執行緒讓步

yield方法:使當前執行緒暫停,回到就緒狀態,讓給其他執行緒

public static native void yield();
  1. public class Thread_07 {
  2. public static void main(String[] args) {
  3. Thread_07_1 tt=new Thread_07_1();
  4. Thread thread=new Thread(tt,"Thread_07_1");
  5. thread.start();
  6. for(int i=0;i<100;i++){
  7. System.out.println(Thread.currentThread().getName()+"-->"+i);
  8. }
  9. }
  10. }
  11. class Thread_07_1 implements Runnable{
  12. @Override
  13. public void run() {
  14. for(int i=0;i<100;i++){
  15. if(i%10==0){
  16. System.out.println(Thread.currentThread().getName()+"暫停了一下");
  17. Thread.yield(); //讓當前執行緒暫停一下
  18. }
  19. System.out.println(Thread.currentThread().getName()+"-->"+i);
  20. }
  21. }
  22. }

4.4、執行緒合併

join方法可以使得在t.join()中讓CPU優先執行完t。將t合併到當前執行緒中,使當前執行緒受阻,t執行緒執行直到結束。

  1. public class Thread_08 {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread_08_1 thread_08_1=new Thread_08_1();
  4. Thread_08_2 thread_08_2=new Thread_08_2();
  5. Thread thread=new Thread(thread_08_1,"Thread_08_1");
  6. Thread thread1=new Thread(thread_08_2,"Thread_08_2");
  7. thread.start();
  8. //合併執行緒
  9. thread.join(); //t合併到當前執行緒中,當前執行緒受阻塞,t執行緒執行直到結束
  10. thread1.start();
  11. thread1.join();
  12. //Thread.currentThread().join();
  13. System.out.println("main over");
  14. }
  15. }
  16. class Thread_08_1 implements Runnable{
  17. @Override
  18. public void run() {
  19. for(int i=0;i<3;i++){
  20. System.out.println(Thread.currentThread().getName()+"-->"+i);
  21. }
  22. }
  23. }
  24. class Thread_08_2 implements Runnable{
  25. @Override
  26. public void run() {
  27. for(int i=0;i<3;i++){
  28. System.out.println(Thread.currentThread().getName()+"-->"+i);
  29. }
  30. }
  31. }

join()的底層實現程式碼。

  1. public final void join() throws InterruptedException {
  2. join(0);
  3. }
  4. public final synchronized void join(long millis)
  5. throws InterruptedException {
  6. long base = System.currentTimeMillis();
  7. long now = 0;
  8. if (millis < 0) {
  9. throw new IllegalArgumentException("timeout value is negative");
  10. }
  11. if (millis == 0) {
  12. while (isAlive()) {
  13. wait(0);
  14. }
  15. } else {
  16. while (isAlive()) {
  17. long delay = millis - now;
  18. if (delay <= 0) {
  19. break;
  20. }
  21. wait(delay);
  22. now = System.currentTimeMillis() - base;
  23. }
  24. }
  25. }

join()是在底層呼叫了wait方法,當主執行緒呼叫了thread.join()之後,主執行緒進入此方法,呼叫join()方法中的wait(0)方法,wait(0)表示無限等待直到被notify。即主執行緒會無限等待thread執行緒執行完成。

  1. public final void wait(long timeout, int nanos) throws InterruptedException {
  2. if (timeout < 0) {
  3. throw new IllegalArgumentException("timeout value is negative");
  4. }
  5. if (nanos < 0 || nanos > 999999) {
  6. throw new IllegalArgumentException(
  7. "nanosecond timeout value out of range");
  8. }
  9. if (nanos > 0) {
  10. timeout++;
  11. }
  12. wait(timeout);
  13. }

4.5、執行緒安全

資料在多執行緒併發的環境下會存在安全問題。例如如果多個使用者想要修改某個共享的資料,就會引發執行緒安全問題。因此需要引入執行緒同步機制(即執行緒排隊執行,不能併發)

4.5.1、執行緒同步的實現

java裡面通過關鍵字synchronized給執行緒加鎖。執行緒會獲取鎖,並獨佔cpu,只有當執行緒釋放了鎖之後,其餘執行緒拿到鎖之後才能執行。

當一個執行緒在執行狀態時遇到synchronized關鍵字,該執行緒就會放棄佔有的cpu時間片,在鎖池裡面找共享物件的物件鎖。

一個執行緒同步synchronized的例子——模擬ATM機取款

Account類
  1. package ATM;
  2. /*
  3. 銀行賬戶,使用執行緒同步機制,解決執行緒安全問題
  4. */
  5. public class Account {
  6. private String accountName; //賬戶名
  7. private double remain; //餘額
  8. Object obj=new Object();
  9. public Account(String accountName, int remain) {
  10. this.accountName = accountName;
  11. this.remain = remain;
  12. }
  13. public String getAccountName() {
  14. return accountName;
  15. }
  16. public void setAccountName(String accountName) {
  17. this.accountName = accountName;
  18. }
  19. public double getRemain() {
  20. return remain;
  21. }
  22. public void setRemain(double remain) {
  23. this.remain = remain;
  24. }
  25. //取款方法
  26. public void withdraw(double money) {
  27. //synchronized (obj){ //obj是一個全域性變數,例項一次Account建立一個obj物件,是可以被共享的
  28. /*Object obj1=new Object();
  29. synchronized (obj1){*/ //obj1是一個區域性變數,每呼叫一次run方法則會建立一個obj1物件,所以obj1不是共享物件
  30. //synchronized (null){ //報錯:空指標
  31. //synchronized ("abc"){ //"abc"在字串常量池當中,會讓所有的執行緒都同步
  32. synchronized (this){
  33. double before = this.getRemain(); //取款之前的餘額
  34. double after = before - money; //取款之後的餘額
  35. try{
  36. Thread.sleep(1000);
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. this.setRemain(after); //更新餘額
  41. }
  42. }
  43. }
AccountThread類
  1. package ATM;
  2. public class AccountThread extends Thread {
  3. //兩個執行緒必須共享同一個賬戶物件
  4. private Account act;
  5. private double money; //取款金額
  6. //通過構造方法傳遞過來構造物件
  7. public AccountThread(Account act,double money) {
  8. this.act = act;
  9. this.money=money;
  10. }
  11. @Override
  12. public void run() { //run方法執行表示取款操作
  13. act.withdraw(money);
  14. System.out.println(Thread.currentThread().getName()+"在賬戶"+
  15. act.getAccountName()+"取款"+money+"之後餘額為:"+act.getRemain());
  16. }
  17. }

啟動類:Test類

  1. package ATM;
  2. public class Test {
  3. public static void main(String[] args) {
  4. Account act=new Account("act001",10000); //建立賬戶物件
  5. //建立兩個執行緒,對同一賬戶取款
  6. Thread threadA=new AccountThread(act,5000);
  7. Thread threadB=new AccountThread(act,3000);
  8. threadA.setName("小明");
  9. threadB.setName("小華");
  10. threadA.start();
  11. threadB.start();
  12. }
  13. }

結果:

  1. //synchronized可以用在例項方法上,此時鎖是this
  2. public synchronized void withdraw(double money) {
  3. double before = this.getRemain(); //取款之前的餘額
  4. double after = before - money; //取款之後的餘額
  5. try {
  6. Thread.sleep(1000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. this.setRemain(after); //更新餘額
  11. }
  12. }

可以在例項方法上使用synchronized,synchronized出現在例項方法上,鎖一定是this,不能是其他的物件了。所以這種方式不靈活。

另外還有一個缺點:synchronized出現在例項方法上,表示整個方法體都需要同步,可能會無故擴大同步的範圍,導致程式的執行效率降低。所以這種方式不常用。

4.5.2、java中的執行緒安全性

java中有三大變數 :1、例項變數(在堆中) 2、靜態變數(在方法中) 3、區域性變數(在棧中)

區域性變數不會有執行緒安全問題,因為區域性變數不共享,區域性變數在棧中,一個執行緒一個棧。

常量不會有執行緒安全問題,因為常量不會被改變。

例項變數在堆中,堆只有1個。靜態變數在方法區中,方法區只有1個。由於堆和方法區都是多執行緒可共享的,所以例項變數和靜態變數可能存線上程安全問題。

ArrayList、HashMap 、HashSet 是非執行緒安全的

Vector、Hashtable 是執行緒安全的

4.5.3、synchronized總結

synchronized有三種寫法:

第一種:同步程式碼塊(靈活)

synchronized(執行緒共享物件){

同步程式碼塊;

}

第二種:在例項方法上使用 synchronized

表示共享物件一定是this,並且同步程式碼塊是整個方法體。

第三種:在靜態方法上使用 synchronized

表示找類鎖。

類鎖永遠只有1把(就算建立了100個物件,那類鎖也只有1把)

物件鎖:1個物件1把鎖,100個物件100把鎖。 類鎖:100個物件,也可能只是1把類鎖。

  1. package Synchronized;
  2. //Q:doOther方法執行的時候需要等待doSome方法的結束嗎?
  3. //A:需要,因為靜態方法是類鎖,不管建立了幾個物件,類鎖只有1把
  4. public class Exam01 {
  5. public static void main(String[] args) throws InterruptedException {
  6. MyClass mc1 = new MyClass();
  7. MyClass mc2 = new MyClass();
  8. Thread t1 = new Mythread(mc1);
  9. Thread t2 = new Mythread(mc2);
  10. t1.setName("t1");
  11. t2.setName("t2");
  12. t1.start();
  13. Thread.sleep(1000);
  14. t2.start();
  15. }
  16. }
  17. class Mythread extends Thread {
  18. private MyClass mc;
  19. public Mythread(MyClass mc) {
  20. this.mc = mc;
  21. }
  22. @Override
  23. public void run() {
  24. super.run();
  25. if (Thread.currentThread().getName().equals("t1")) {
  26. try {
  27. mc.doSome();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. if (Thread.currentThread().getName().equals("t2")) {
  33. mc.doOther();
  34. }
  35. }
  36. }
  37. class MyClass {
  38. //synchronized出現在靜態方法上是走 類鎖
  39. public synchronized static void doSome() throws InterruptedException {
  40. System.out.println("doSome begin");
  41. Thread.sleep(1000 * 5);
  42. System.out.println("doSome over");
  43. }
  44. public synchronized static void doOther() {
  45. System.out.println("doOther begin");
  46. System.out.println("doOther over");
  47. }
  48. }

4.5.4、死鎖

一個簡單的死鎖實現的例子

  1. package Thread;
  2. /*
  3. 實現一個死鎖,
  4. t1中obj1鎖上後就睡了,t2中obj2鎖上後就睡了。
  5. t1想要釋放obj1鎖,就必須請求到obj2鎖
  6. t2想要釋放obj2鎖,就必須請求到obj1鎖
  7. t1與t2互相請求鎖,但彼此都無法釋放鎖,所以形成了死鎖
  8. */
  9. public class Dead_lock {
  10. public static void main(String[] args) {
  11. Object obj1 = new Object();
  12. Object obj2 = new Object();
  13. //t1,t2兩個執行緒共享o1,o2
  14. Thread t1 = new MyThread_1(obj1, obj2);
  15. Thread t2 = new MyThread_2(obj1, obj2);
  16. t1.start();
  17. t2.start();
  18. }
  19. }
  20. class MyThread_1 extends Thread {
  21. Object obj1 = new Object();
  22. Object obj2 = new Object();
  23. public MyThread_1(Object obj1, Object obj2) {
  24. this.obj1 = obj1;
  25. this.obj2 = obj2;
  26. }
  27. @Override
  28. public void run() {
  29. synchronized (obj1) {
  30. try {
  31. Thread.sleep(1000 * 3);
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. synchronized (obj2) {
  36. }
  37. }
  38. }
  39. }
  40. class MyThread_2 extends Thread {
  41. Object obj1 = new Object();
  42. Object obj2 = new Object();
  43. public MyThread_2(Object obj1, Object obj2) {
  44. this.obj1 = obj1;
  45. this.obj2 = obj2;
  46. }
  47. @Override
  48. public void run() {
  49. synchronized (obj2) {
  50. try {
  51. Thread.sleep(1000 * 3);
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. }
  55. synchronized (obj1) {
  56. }
  57. }
  58. }
  59. }

注:synchronized在開發中最好不要巢狀使用,一不小心就可能導致死鎖現象發生

4.6、守護執行緒

java語言中執行緒可分為兩大類:

一類是:使用者執行緒 例如:main方法主執行緒

一類是:守護執行緒(後臺執行緒) 例如java垃圾回收執行緒

4.6.1、守護執行緒的特點

一般守護執行緒是一個死迴圈,所有使用者執行緒只要結束,守護執行緒自動結束。

守護執行緒一般會用在一些定時任務,例如每天0點系統自動備份需要用到定時器,我們可以將定時器設定為守護執行緒一直在那裡看著。每到0點就備份一次。所有的使用者執行緒如果結束了,守護執行緒就自動退出。

4.6.2、一個簡單的守護執行緒的例子

  1. package Thread;
  2. /**
  3. * 使用者執行緒備份資料,守護執行緒守護。
  4. */
  5. public class GuardThread {
  6. public static void main(String[] args) {
  7. Thread thread = new BakDataThread();
  8. thread.setName("備份資料的執行緒");
  9. //啟動執行緒之前,將執行緒設定為守護執行緒
  10. thread.setDaemon(true);
  11. thread.start();
  12. for (int i = 0; i < 5; i++) {
  13. System.out.println(Thread.currentThread().getName() + "--->" + i);
  14. try {
  15. Thread.sleep(1000);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21. }
  22. class BakDataThread extends Thread {
  23. @Override
  24. public void run() {
  25. int i = 0;
  26. //即使是死迴圈,但由於該執行緒是守護者。當用戶執行緒結束,守護執行緒自動終止
  27. while (true) {
  28. System.out.println(Thread.currentThread().getName() + "--->" + i++);
  29. try {
  30. Thread.sleep(1000);
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }
  36. }

5、定時任務

5.1、實現一個定時器

  1. package Thread;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.Date;
  5. import java.util.Timer;
  6. import java.util.TimerTask;
  7. public class TimerTest {
  8. public static void main(String[] args) throws ParseException {
  9. //建立定時器物件
  10. Timer timer = new Timer();
  11. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  12. Date firstTime = sdf.parse("2020-07-25 19:10:30");
  13. //指定定時任務
  14. //timer.schedule(定時任務,第一次執行時間時間,間隔多久執行一次)
  15. timer.schedule(new LogTimerTask(), firstTime, 1000 * 5);
  16. }
  17. }
  18. //編寫一個定時任務
  19. class LogTimerTask extends TimerTask {
  20. @Override
  21. public void run() {
  22. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  23. String strTime = sdf.format(new Date());
  24. System.out.println(strTime + ":成功完成了一次資料備份!");
  25. }
  26. }

6、通過Callable介面實現一個執行緒

使用Callable介面實現執行緒,可以獲得該執行緒的返回值(JDK8新特性)

一個簡單的例項:

  1. package Thread;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.FutureTask;
  5. public class ThirdWay {
  6. public static void main(String[] args) throws ExecutionException, InterruptedException {
  7. FutureTask futureTask = new FutureTask(new Task());
  8. Thread thread = new Thread(futureTask);
  9. thread.start();
  10. Object object = futureTask.get(); //通過get方法可以獲取當前執行緒的返回值
  11. ////主執行緒中這裡的程式必須等待get()方法結束才執行
  12. // get()方法為了拿另一個執行緒的執行結果需要等待其執行完成,因此要等待較長時間
  13. System.out.print("執行緒執行結果:");
  14. System.out.println(object);
  15. }
  16. }
  17. class Task implements Callable {
  18. @Override
  19. public Object call() throws Exception {
  20. System.out.println("call method begin");
  21. Thread.sleep(1000 * 10);
  22. System.out.println("call method end");
  23. int a = 100;
  24. int b = 200;
  25. return a + b;
  26. }
  27. }

FutureTask類相關原始碼
  1. public FutureTask(Callable<V> callable) {
  2. if (callable == null)
  3. throw new NullPointerException();
  4. this.callable = callable;
  5. this.state = NEW; // ensure visibility of callable
  6. }
  1. public V get() throws InterruptedException, ExecutionException {
  2. int s = state;
  3. if (s <= COMPLETING)
  4. s = awaitDone(false, 0L);
  5. return report(s);
  6. }

Future類中實現了get方法獲取傳入執行緒的返回結果

7、Object類中的wait和notify方法

7.1、wait和notify方法介紹

wait和notify方法不是執行緒物件的方法,不能通過執行緒物件呼叫。

  1. Object object=new Object();
  2. object.wait();//object.wait()讓正在object物件上活動的執行緒進入等待狀態,無限等待,直到被喚醒為止
  3. object.notify();//object.notify()喚醒正在object物件上等待的執行緒
  4. object.notifyAll();//object.notifyAll喚醒正在object物件上等待的所有執行緒

7.2、生產者和消費者模式

一個簡單的生產者和消費者例項

模擬生產者生產一個,消費者就消費一個。讓倉庫始終零庫存。

  1. package Thread;
  2. import java.awt.*;
  3. import java.util.*;
  4. import java.util.List;
  5. public class Producer_Consumer {
  6. public static void main(String[] args) {
  7. List<String> list = new ArrayList<String>();
  8. Thread thread1 = new Thread(new Consumer(list), "消費者執行緒");
  9. Thread thread2 = new Thread(new Producer(list), "生產者執行緒");
  10. thread1.start();
  11. thread2.start();
  12. }
  13. }
  14. //消費者執行緒
  15. class Consumer implements Runnable {
  16. //倉庫
  17. private List<String> list;
  18. public Consumer(List<String> list) {
  19. this.list = list;
  20. }
  21. @Override
  22. public void run() {
  23. //消費
  24. while (true) {
  25. synchronized (list) {
  26. //如果倉庫已經空了,消費者執行緒等待並釋放list集合的鎖
  27. if (list.size() == 0) {
  28. try {
  29. list.wait();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. //倉庫中有商品,消費者進行消費
  35. String str = list.remove(0);
  36. System.out.println(Thread.currentThread().getName() + " 消費 " + str);
  37. list.notify(); //喚醒生產者
  38. }
  39. }
  40. }
  41. }
  42. //生產者執行緒
  43. class Producer implements Runnable {
  44. //倉庫
  45. private List<String> list;
  46. public Producer(List<String> list) {
  47. this.list = list;
  48. }
  49. @Override
  50. public void run() {
  51. //生產
  52. while (true) {
  53. synchronized (list) {
  54. //如果倉庫裡有東西,則停止生產。生產者執行緒等待並釋放list集合的鎖
  55. if (list.size() > 0) {
  56. try {
  57. list.wait();
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. }
  61. }
  62. list.add("商品");
  63. System.out.println(Thread.currentThread().getName() + " 生產 " + list.get(0));
  64. list.notify(); //喚醒消費者
  65. }
  66. }
  67. }
  68. }

7.3、實現奇偶數的交替輸出

  1. package Thread;
  2. /**
  3. * 使用生產者和消費者模式實現兩個執行緒交替輸出:一個執行緒負責輸出奇數,另一個執行緒負責輸出偶數
  4. */
  5. public class Number {
  6. public static void main(String[] args) throws InterruptedException {
  7. Num num = new Num(0);
  8. Thread thread1 = new Thread(new Odd(num), "Odd");
  9. Thread thread2 = new Thread(new Event(num), "Event");
  10. thread1.start();
  11. thread2.start();
  12. }
  13. }
  14. class Odd implements Runnable {
  15. Num num;
  16. public Odd(Num num) {
  17. this.num = num;
  18. }
  19. @Override
  20. public void run() {
  21. while (true) {
  22. synchronized (num) {
  23. try {
  24. Thread.sleep(1000);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. if (num.getI() % 2 == 0) {
  29. try {
  30. num.wait();
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. System.out.println(Thread.currentThread().getName() + "--->" + num.printNum());
  36. num.notifyAll();
  37. }
  38. }
  39. }
  40. }
  41. class Event implements Runnable {
  42. Num num;
  43. public Event(Num num) {
  44. this.num = num;
  45. }
  46. @Override
  47. public void run() {
  48. while (true) {
  49. synchronized (num) {
  50. try {
  51. Thread.sleep(1000);
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. }
  55. if (num.getI() % 2 != 0) {
  56. try {
  57. num.wait();
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. }
  61. }
  62. System.out.println(Thread.currentThread().getName() + "--->" + num.printNum());
  63. num.notifyAll();
  64. }
  65. }
  66. }
  67. }
  68. class Num {
  69. private int i = 0;
  70. public Num(int i) {
  71. this.i = i;
  72. }
  73. public int getI() {
  74. return i;
  75. }
  76. public void setI(int i) {
  77. this.i = i;
  78. }
  79. int printNum() {
  80. return i++;
  81. }