單機高併發應該掌握的線程基礎:線程狀態,異常與鎖等
1.進程、線程、協程的概念:
public class T01_WhatIsThread {
private static class T1 extends Thread {
@Override
public void run() {
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1");
}
}
}
public static void main(String[] args) {
new T1().run();
//new T1().start();
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main");
}
}
}
執行new T1().run()方法時先輸出10個T1,再輸出10個main,這是因爲此時run就是一個普通的方法,在主線程main裏面,先執行run()方法,再執行循環。
執行new T1().start()時,結果如下:
T1和main交替出現是因爲,調用start方法的時候,新創建了一個線程,主線程和新建的線程交替執行,執行的順序由CPU決定的,所以導致打印的結果不是唯一的,多執行幾次代碼,得到的結果是不一樣的。
2.創建線程的幾種方法以及代碼實現:
面試題:啓動線程的三種方式?
2.1繼承Thread方法,重寫run方法:
2.2實現runnable接口,重寫run方法:
2.3lambda表達式也可以實現,一般還有線程池但是底層原來還是繼承Thread方法:
具體實現代碼:
public class T02_HowToCreateThread {
static class MyThread extends Thread{
@Override
public void run(){
System.out.println("MyThread!!");
}
}
static class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println("MyRunnable");
}
}
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRunnable()).start();
new Thread(()->{
System.out.println("Lambda");
}).start();
}
}
輸出結果:
MyThread!!
MyRunnable
Lambda
阿里編碼開發手冊中說:
線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。 說明:使用線程池的好處是減少在創建和銷燬線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName()));
singleThreadPool.shutdown();
3.sleep、yield、join方法的使用:
sleep:
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
* 意思是說:當前正在執行的線程休眠(暫時停止執行)指定的毫秒數,具體取決於系統計時器和調度程序的精度和準確性。 該線程不會失去任何監視器的所有權。
* @param millis
* the length of time to sleep in milliseconds
* 毫秒爲單位
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
其實主要的就是他是讓其他線程走,自己進行休眠,但是自己卻不會釋放對象鎖,也就是說,如果有同步鎖的時候,其他線程不能訪問共享數據。
注意該方法要捕獲異常 比如有兩個線程同時執行(沒有Synchronized),一個線程優先級爲MAX_PRIORITY,另一 個爲MIN_PRIORITY,如果沒有Sleep()方法,只有高優先級的線程執行完成後,低優先級 的線程才能執行;但當高優先級的線程sleep(5000)後,低優先級就有機會執行了。 總之,sleep()可以使低優先級的線程得到執行的機會,當然也可以讓同優先級、高優先級的 線程有執行的機會。
yield:
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
* 意思是說 提示當前線程可以讓處理器忽略當前線程,去處理其他線程
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
* 它是一種啓發式嘗試,用於改善線程之間的相對進展,否則會過度利用CPU。 它的使用應與詳細的分析和基準測試相結合,以確保它實際上具有所需的效果。
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package. * 使用這種方法很少是合適的。 它可能對調試或測試目的很有用,它可能有助於重現因競爭條件而產生的錯誤。 在設計併發控制結構(如中的那些)時,它也可能很有用
*/
public static native void yield();
yield() 這個方法從以上註釋可以看出,也是一個休眠自身線程的方法,同樣不會釋放自身鎖的標識,區別在於它是沒有參數的,即yield()方 法只是使當前線程重新回到可執行狀態,
所以執行yield()的線程有可能在進入到可執行狀態 後馬上又被執行,另外yield()方法只能使同優先級或者高優先級的線程得到執行機會,這也 和sleep()方法不同。
join:
/**
* Waits for this thread to die.
* 等待線程死亡
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0); // 調用了有參方法
}
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
* 這個線程最多等多少毫秒,如果超時了,就會進行線程死鎖
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException
保證當前線程停止執行,直到該線程所加入的線程完成爲止。然而,如果它加入的線程沒有存活,則當前線程不需要停止。
public class T03_Sleep_Yield_Join {
public static void main(String[] args) {
//testSleep();
// testYield();
testJoin();
}
static void testSleep() {
new Thread(()->{
for(int i=0; i<10; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
static void testYield() {
new Thread(()->{
for(int i=0; i<10; i++) {
System.out.println("A" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
new Thread(()->{
for(int i=0; i<10; i++) {
System.out.println("------------B" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
}
static void testJoin() {
Thread t1 = new Thread(()->{
for(int i=0; i<10; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(()->{
try {
t1.join(); //當執行t2時,T1必須現在執行完纔可以執行T2
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0; i<10; i++) {
System.out.println("B" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
t1.start();
}
}
4.線程狀態:
public class T04_ThreadState {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(this.getState());
for(int i=0; i<10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
public static void main(String[] args) {
Thread t = new MyThread();
System.out.println(t.getState());
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getState());
}
}
5.線程同步:synchronized關鍵字:
synchronized鎖的是對象不是代碼,說的是this對象,如果是靜態方法說的是xx.class,
注意:鎖定對象的時候不能用String常量,Integer,Long
鎖定方法和非鎖定方法可以同時執行
底層實現:
jdk早期重量級實現:概念:找操作系統申請鎖,必須進入內核態,所以是重量級的。
鎖升級:jdk1.5以後
改進點:hotSpot實現:markword記錄這個線程的id(偏向鎖),線程再次進入的時候,看markword是否是自己的線程id,如果是直接拿鎖,獲取資;如果有線程爭用:線程爭用的前提是該鎖已經被其他線程獲取,所以本身升級爲自旋鎖,默認旋10次,如果10次以後還得不到這把鎖的話就升級爲重量級鎖,向os申請鎖。注意鎖只能升級不能降級。
在什麼情況使用自旋鎖比較好:自旋鎖雖然佔用cpu,但是不進入內核態,只在用戶態。加鎖代碼執行時間短,並且線程的數量不會太多的時候用自旋鎖。執行時間長,並且線程資源多的時候用系統鎖。
多個線程同時訪問一個資源的時候:必須對資源上鎖
5.1對某個對象加鎖:
/**
* synchronized關鍵字
* 對某個對象加鎖
*/
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
synchronized(o) { //任何線程要執行下面的代碼,必須先拿到o的鎖
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
5.2對某個static方法加鎖:
/**
* synchronized關鍵字
* 對某個對象加鎖
*/
public class T {
private static int count = 10;
public synchronized static void m() { //這裏等同於synchronized(FineCoarseLock.class)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void mm() {
synchronized(T.class) { //考慮一下這裏寫synchronized(this)是否可以? 不可以
count --;
}
}
}