線程:cpu調度的最小單位
進程:操作系統分配資源的最小單位(線程共享進程內部的資源)
java 中提供了Thread 類,而這個類有幾個方法
yield,start,run(來自接口runnable),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.
測試代碼:
(測試效果基本看不出)
Thread[] threads = new Thread[10];
threads[0] = new Thread(() -> {
// ReadThreadLocal readThreadLocal2 = new ReadThreadLocal();
//readThreadLocal2.begin();
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
threads[0].setName("-1");
threads[0].start();
for (int i = 1; i < 10; i++) {
final Thread pre = threads[i - 1];
final int index = i;
threads[i] = new Thread(() -> {
try {
// 這裏嘗試1,2,號線程讓出cpu
if (index < 3) {
Thread.yield();
}
// 以下邏輯 可以保證 6,7,8,9 線程是有序執行
else if (index > 6) {
pre.join();
}
// ReadThreadLocal readThreadLocal2 = new ReadThreadLocal();
// readThreadLocal2.begin();
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threads[i].setName(i + "");
threads[i].start();
}
效果:(這三次輸出只是看到了join 接力棒的效果,始終保證6,7,8,9之間是嚴格有序的)
第一次:
-1 1 3 2 5 4 6 7 8 9
第二次
-1 1 6 5 2 4 3 7 8 9
第三次
-1 3 1 4 2 6 5 7 8 9
start:線程啓動入口
啓動線程(線程進入就緒態:(也就是等待被cpu調度,並不是說先調用start,就先執行run方法)
run: 線程真正的執行體,線程需要處理的業務邏輯全在這裏
join:接力棒
join(othrerThread)
Waits for this thread to die// 等待到參數中的thread 死亡,自己纔開始
(可以類比爲線程進行接力賽,下一棒需要等上一棒到了才能開始跑),也就是可以達到有序執行的目的
代碼測試
看例子程序:
Thread[] threads = new Thread[10];
threads[0] = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() );
});
threads[0].setName("-1");
threads[0].start();
for (int i = 1; i < 10; i++) {
final Thread pre = threads[i - 1];
threads[i] = new Thread(() -> {
try {
pre.join();
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() );
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threads[i].setName(i + "");
threads[i].start();
}
sleep: 抱鎖入睡
注意不會丟失monitors,監視器,也就是不會釋放鎖
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.
代碼測試:
線程1,線程2 都去獲取同一個對象的monitor,各自都在獲取到之後輸出 “sleep start”+id;sleep(500);輸出"sleep end "+id
如果不釋放鎖的話,要麼是線程1 連續輸出兩條,然後線程2連續輸出兩條
否則是反過來
代碼使用10個線程
public class TestSleep {
public static void main(String[] args) {
final Object lock=new Object();
for (int i=0;i<10;i++) {
Thread t=new Thread(()->{
synchronized (lock){
try {
System.out.println("sleep start"+Thread.currentThread().getName());
Thread.sleep(500);
System.out.println("sleep end"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setName(i+"");
t.start();
}
}
}
效果:
Object 的方法:wait VS notify
wait:等待,會釋放鎖,將當前位置保存,暫停執行
norify:喚醒,會釋放鎖,但是會將synchronized修飾代碼塊執行完之後纔會釋放
僞代碼描述
以下列僞代碼爲例:
第一塊代碼塊
synchonized(helloObject){// 這裏相當於是嘗試獲取對象helloObject 的鎖
輸出:我是hello1
helloObject.wait();
輸出:我是hello2
}
第二塊代碼塊
synchronized (helloObject){
輸出:我是word1:
helloObject.notifyAll();
輸出:我是word2
}
- 假設上述代碼:線程1進入到第一塊代碼塊,此時線程2阻塞到第二行代碼塊的synchronized 處,線程2沒有獲取到鎖
- 線程1調用helloObject.wait();掛起線程,進入waiting 狀態;// 已經輸出 hello1
- 線程2獲取到helloObject 的鎖, //輸出我是word1
- 線程2調用helloObject.notifyAll() //喚醒所有處於該對象的waiting 狀態的線程
- 線程2退出synchronized 同步塊;//已輸出 我是word2
- 線程2釋放helloObject 的monitor(看成鎖)
- 線程1繼續// 輸出我是hello2
以下附上java併發編程藝術中的一個例子:等待-喚醒機制
/**
* @author wangwei
* @date 2019/3/10 9:15
* @classDescription 場景:一個線程修改了一個對象的值,而另一個線程感覺到了變化,然後進行相應的操作
* 整個過程開始於一個線程,作用於另一個線程`,前者是生產者,後者是消費者
* <p>
* 這種生產者消費者模式隔離了"做什麼" 和"怎麼做"
* java 中的實現方式
* while(value!=desire){
* Thread.sleep(1000);
* }
* doSomething();
* <p>
* 上述代碼:在條件不滿足時,sleep一段時間,避免過快的"無效嘗試"
* <p>
* 存在的問題:
* 1.難以確保及時性:睡眠時基本不佔用處理器資源,但是如果睡眠過久,難以及時發現條件已經變化
* 2.難以降低開銷:如果降低睡眠時間,消費者能及時發現條件變化,但是,卻可能需要浪費更多的資源
* <p>
* 兩點矛盾,但是java內置了等待/通知機制,可以解決這個問題
*/
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
@Override
public void run() {
synchronized (lock) {
// 當條件不滿足時,繼續wait,但是會釋放鎖lock
while (flag) {
try {
System.out.println(Thread.currentThread() + " flag is true,wait @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//條件滿足時,完成工作
System.out.println(Thread.currentThread() + " flag is false,running@ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run() {
//加鎖,擁有lock 的monitor
synchronized (lock) {
// 獲取lock鎖,然後進行通知(不會釋放鎖)
// 直到當前線程釋放lock鎖之後,WaitThread 才能在wait方法上返回
System.out.println(Thread.currentThread() + " hold lock ,notify @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtil.second(5);
}
// 再次加鎖
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock again ,sleep @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtil.second(5);
}
}
}
}
輸出:
等待喚醒機制應用–線程池
奉上併發編程藝術一書中簡易web服務器
可前往github 獲取
https://github.com/TopForethought/java/tree/master/src/top/forethought/concurrency/threads/httpserver
這裏就不再粘貼代碼了