第二章 線程基本概念(二)

一、線程簡單操作

1. 創建線程的方式

1)繼承Thread,重寫run()。

public class MyThread extends Thread {
    public void run() {
        // 線程邏輯
    }
}
Thread t = new MyThread();
t.start();

2)實現Runnable接口,重寫run(),將接口實現類作爲Thread的入參。

public class MyRunnable implements Runnable {
    public void run() {
        // 線程邏輯
    }
}
Thread t = new Thread(new MyRunnable());
t.start();

2. 不被推薦使用的stop()方法

 如果此時有兩個線程分別是讀線程和寫線程,寫線程正寫到一半,被stop()停止執行,此時加在寫線程上的鎖被釋放,讀線程獲取鎖後將會讀到髒數據。

public class StopThreadUnsafe {

    public static User u = new User();
    public static class User {
        private int id;
        private String name;

        public User() {
            id = 0;
            name = "0";
        }
        /**getter、setter*/
    }

    public static class ChangeObjectThread extends Thread {
        @Override
        public void run() {
            while(true) {
                synchronized (u) {
                    int v = (int)(System.currentTimeMillis() / 1000);
                    u.setId(v);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    u.setName(String.valueOf(v));
                    // System.out.println(u);
                }
                Thread.yield();
            }
        }
    }

    public static class ReadObjectThread extends Thread {
        @Override
        public void run() {
            while(true) {
                synchronized (u) {
                    if(u.getId() != Integer.parseInt(u.getName())) {
                        System.out.println(u.toString());
                    }
                }
                Thread.yield();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new ReadObjectThread().start();
        while(true) {
            Thread t = new ChangeObjectThread();
            t.start();
            Thread.sleep(150);
            t.stop();
        }
    }
}

可以通過改進上面的代碼,在寫線程中,可以在進入同步代碼塊之前停止線程,這樣寫線程就沒有機會寫壞對象。

public static class ChangeObjectThread extends Thread {
	volatile boolean stopme = false;
	public void stopMe() {
		stopme = true;
	}
	@Override
	public void run() {
		while(true) {
			if(stopme) {
				System.out.println("exit by stop me");
				break;
			}
			synchronized (u) {
				int v = (int)(System.currentTimeMillis() / 1000);
				u.setId(v);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				u.setName(String.valueOf(v));
				// System.out.println(u);
			}
			Thread.yield();
		}
	}
}

3. 線程中斷

線程中斷不會讓線程立即退出執行,而是給線程發送了一個通知,告知目標線程有人希望你退出。至於目標線程收到通知後如何處理,則完全由目標線程決定。

三個重要的線程中斷方法
public void Thread.interrupt(); //中斷線程,將會給目標線程發送中斷通知
public boolean Thread.isInterrupted(); // 判斷線程是否被中斷
public static boolean Thread.interrupted(); // 判斷線程是否被中斷,並清除當前中斷標識

下面的代碼雖然調用了線程的中斷方法,但是隻是給目標線程發送了中斷通知,具體的線程該如何處理,取決去目標線程。

public class InterruputThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
          @Override
          public void run() {
              while(true) {
                  if(Thread.currentThread().isInterrupted()) {
                      System.out.println("想讓我退出,得聽我的");
                      break; // 終止該線程靠的是這個break
                  }
                  Thread.yield();
              }
          }
        };
        t.start();
        Thread.sleep(2000);
        t.interrupt();
    }
}

中斷可以幫助我們在出現類似wait()或者sleep()方法時通過拋出InterruptedException異常,我們可以在catch語句中處理該線程。

public class InterruputSleepThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
          @Override
          public void run() {
              while(true) {
                  try {
                      Thread.sleep(2000);
                  } catch (InterruptedException e) {
                      System.out.println("我正在休眠呢,居然被吵醒了");
                      // 此時本可以直接退出,但是假如後面有需要保持數據一致性和完整性的邏輯
                      // 但是進了這個catch語句,則會清除中斷標識位,這裏我們重新設置下
                      this.interrupt();
                  }
                  // 保持數據的一致性和完整性代碼
                  System.out.println("==保持數據的一致性和完整性代碼==");
                  // 此時可以圓滿退出了,通過判斷標識位
                  if(Thread.currentThread().isInterrupted()) {
                      System.out.println("被中斷了");
                      break;
                  }
                  Thread.yield(); // 讓出線程執行權
              }
          }
        };
        t.start();
        Thread.sleep(500);
        t.interrupt();
    }
}

4. 等待(wait)和通知(notify)

這兩個方法是Object類中的方法,用於線程間協作用的。線程A調用了obj.wait()後,線程A將停止運行,直到其他線程調用了obj.notify()或obj.notifyAll()。要說明的是這個obj上等待隊列上可能不僅僅有A線程,而notify()方法只能喚醒其中的一個,而notifyAll()才能喚醒全部的等待線程。要注意的是在調用wait()和notify()方法時需要被包含在synchronized語句中。

public class SimpleWN {
    // 對象鎖
    public static Object obj = new Object();
    public static class T1 extends Thread {

        @Override
        public void run() {
            synchronized (obj) {
                // T1獲得了對象鎖
                System.out.println(System.currentTimeMillis() + ": T1 start!");
                try {
                    System.out.println(System.currentTimeMillis() + ": T1 for wait");
                    obj.wait();// T1在執行了wait方法後,會釋放對象鎖
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ": T1 stop!");
            }
        }
    }

    public static class T2 extends Thread {
        @Override
        public void run() {
            synchronized (obj) {
                // T2獲得T1釋放的對象鎖
                System.out.println(System.currentTimeMillis() + ": T2 start!");
                obj.notify(); // 通知obj上的T1線程

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ": T2 end!"); // 執行這行結束,會釋放T1的對象鎖
            }
        }
    }
    public static void main(String[] args) {
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();


    }
}

5. 不被推薦使用的掛起(suspend)和繼續執行(resume)線程

不推薦使用suspend()方法是因爲調用後,不會釋放鎖。這將會導致一大片需要這個鎖的線程阻塞。雖然resume()可以將將掛起的線程重新激活,但是萬一執行的順序不對,比如在suspend()方法之前執行呢。如下面代碼:

public class BadSuspend {
    public static Object u = new Object();
    private static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super.setName(name);
        }
        @Override
        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                try {
                    Thread.sleep(1000); // 處理必要邏輯所花費時間
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 線程掛起
                Thread.currentThread().suspend();
                System.out.println(getName() + " end!");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        t1.start();
        t1.resume();
        t1.join();
    }
}

可以重寫一個可靠的suspend()和resume()方法,達到相同的效果。

public class GoodSuspend {
   
    public static Object u = new Object();

    public static class ChangeObjectThread extends Thread {
        volatile boolean suspendme = false;

        public void suspendMe() {
            suspendme = true;
        }

        public void resumeMe() {
            suspendme = false;
            synchronized (u) {
                u.notify();
            }
        }

        @Override
        public void run() {
            while(true) {
                synchronized (u) {
                    System.out.println("我正在happy的執行呢");
                    if (suspendme) {
                        System.out.println("我被掛起來了");
                        try {
                            u.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ChangeObjectThread t = new ChangeObjectThread();
        t.start();
        Thread.sleep(1000);
        t.suspendMe();
        Thread.sleep(1000);
        t.resumeMe();
    }
}

6. 等到調用join()的線程執行結束和線程謙讓方法yeild()

join()方法有個重載的有參方法,不像join()方法會一直等待,在等待指定的時間後線程還沒執行完,則它的父線程會繼續往下執行。

public class JoinMain {
    private volatile static int i = 0;

    public static class AddThread extends Thread {
        @Override
        public void run() {
            for(i=0; i<1000000; i++);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

值得注意的是,join()方法底層是調用的wait()方法, 而鎖是調用wait()方法的線程。因此不要在線程實例上使用類似wait()方法或者notify()方法,否則可能影響join()工作。

yield()方法比較好理解,就是正在執行的線程讓出執行權,重新回到就緒狀態,等待下次分配CPU執行權。

下一章 線程基本概念(三):https://blog.csdn.net/qq_35165775/article/details/107117440

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章