一、線程簡單操作
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