柚子快報激活碼778899分享:Java之線程篇四
柚子快報激活碼778899分享:Java之線程篇四
目錄
volatile關(guān)鍵字
volatile保證內(nèi)存可見性
代碼示例
代碼示例2-(+volatile)
volatile不保證原子性
synchronized保證內(nèi)存可見性
wait()和notify()
wait()方法
notify()
理解notify()和notifyAll()
wait和sleep的對比
volatile關(guān)鍵字
volatile保證內(nèi)存可見性
volatile 修飾的變量, 能夠保證 "內(nèi)存可見性".
代碼在寫入 volatile 修飾的變量的時候:
改變線程工作內(nèi)存中volatile變量副本的值 將改變后的副本的值從工作內(nèi)存刷新到主內(nèi)存
代碼在讀取 volatile 修飾的變量的時候:?
從主內(nèi)存中讀取volatile變量的最新值到線程的工作內(nèi)存中 從工作內(nèi)存中讀取volatile變量的副本?
加上 volatile , 強(qiáng)制讀寫內(nèi)存. 速度是慢了, 但是數(shù)據(jù)變的更準(zhǔn)確了。?
代碼示例
public class Demo13 {
private static int isQuit=0;
public static void main(String[] args) {
Thread t1=new Thread(()->{
while(isQuit==0){
}
System.out.println("t1 退出");
});
t1.start();
Thread t2=new Thread(()->{
System.out.println("請輸入 isQuit:");
Scanner scanner=new Scanner(System.in);
isQuit=scanner.nextInt();
});
t2.start();
}
}
運(yùn)行結(jié)果
通過jconsole觀察,會看到線程t1處于RUNNABLE狀態(tài)。
t1
讀的是自己工作內(nèi)存中的內(nèi)容
.
當(dāng)
t2
對
flag
變量進(jìn)行修改
,
此時
t1
感知不到
flag
的變化
.
原因解釋:
1) load 讀取內(nèi)存中isQuit的值到寄存器中. 2)通過cmp 指令比較寄存器的值是否是0.決定是否要繼續(xù)循環(huán). 由于這個循環(huán),循環(huán)速度飛快.短時間內(nèi),就會進(jìn)行大量的循環(huán).也就是進(jìn)行大量的load和cmp 操作.此時,編譯器/JVM就發(fā)現(xiàn)了,雖然進(jìn)行了這么多次load,但是 load 出來的結(jié)果都一樣的.并且, load 操作又非常費(fèi)時間,一次load花的時間相當(dāng)于上萬次cmp 了. 所以編譯器就做了一個大膽的決定~~只是第一次循環(huán)的時候才讀了內(nèi)存.后續(xù)都不再讀內(nèi)存了,而是直接從寄存器中,取出isQuit的值了.?
代碼示例2-(+volatile)
public class Demo13 {
private static volatile int isQuit=0;
public static void main(String[] args) {
Thread t1=new Thread(()->{
while(isQuit==0){
}
System.out.println("t1 退出");
});
t1.start();
Thread t2=new Thread(()->{
System.out.println("請輸入 isQuit:");
Scanner scanner=new Scanner(System.in);
isQuit=scanner.nextInt();
});
t2.start();
}
}
運(yùn)行結(jié)果
代碼示例3-(+sleep)
public class Demo13 {
private static int isQuit=0;
public static void main(String[] args) {
Thread t1=new Thread(()->{
while(isQuit==0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t1 退出");
});
t1.start();
Thread t2=new Thread(()->{
System.out.println("請輸入 isQuit:");
Scanner scanner=new Scanner(System.in);
isQuit=scanner.nextInt();
});
t2.start();
}
}
運(yùn)行結(jié)果
volatile不保證原子性
代碼示例
class Counter {
volatile public int count = 0;
void increase() {
count++;
}
}
public class Demo13 {
public static void main(String[] args) throws InterruptedException {
final Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.count);
}
}
運(yùn)行結(jié)果
我們會發(fā)現(xiàn),加上volatile以后,依舊不是線程安全的。
synchronized保證內(nèi)存可見性
代碼示例
class Counter {
public int flag = 0;
}
public class Demo13 {
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (true) {
synchronized (counter) {
if (counter.flag != 0) {
break;
}
}
}
System.out.println("循環(huán)結(jié)束!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("輸入一個整數(shù):");
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
}
運(yùn)行結(jié)果
wait()和notify()
wait()方法
wait 做的事情:
使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待. (把線程放到等待隊列中) 釋放當(dāng)前的鎖 滿足一定條件時被喚醒, 重新嘗試獲取這個鎖.?
wait 要搭配 synchronized 來使用. 脫離 synchronized 使用 wait 會直接拋出異常.
代碼示例
public class Demo14 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println("wait 之前");
// 把 wait 要放到 synchronized 里面來調(diào)用. 保證確實是拿到鎖了的.
object.wait();
System.out.println("wait 之后");
}
}
}
?運(yùn)行結(jié)果
此時object就會一直進(jìn)行wait,當(dāng)然我們肯定不想讓程序一直等待下去,下面將介紹notify()來喚醒它。
notify()
notify 方法是喚醒等待的線程.?
方法notify()也要在同步方法或同步塊中調(diào)用,該方法是用來通知那些可能等待該對象的對象鎖的其它線程,對其發(fā)出通知notify,并使它們重新獲取該對象的對象鎖。 如果有多個線程等待,則有線程調(diào)度器隨機(jī)挑選出一個呈 wait 狀態(tài)的線程。(并沒有 "先來后到"),在notify()方法后,當(dāng)前線程不會馬上釋放該對象鎖,要等到執(zhí)行notify()方法的線程將程序執(zhí)行完,也就是退出同步代碼塊之后才會釋放對象鎖。
代碼示例
public class Demo15 {
public static void main(String[] args) {
Object object = new Object();
Thread t1 = new Thread(() -> {
synchronized (object) {
System.out.println("wait 之前");
try {
object.wait();
// object.wait(5000);//也可以指定等待時間后自動喚醒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait 之后");
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object) {
System.out.println("進(jìn)行通知");
object.notify();
}
});
t1.start();
t2.start();
}
}
運(yùn)行結(jié)果
notifyAll()
notify方法只是喚醒某一個等待線程. 使用notifyAll方法可以一次喚醒所有的等待線程.
代碼示例
class WaitTask implements Runnable {
private Object locker;
public WaitTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
while (true) {
try {
System.out.println("wait 開始");
locker.wait();
System.out.println("wait 結(jié)束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class NotifyTask implements Runnable {
private Object locker;
public NotifyTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
System.out.println("notify 開始");
locker.notifyAll();
System.out.println("notify 結(jié)束");
}
}
}
public class Demo16 {
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitTask(locker));
Thread t3 = new Thread(new WaitTask(locker));
Thread t4 = new Thread(new WaitTask(locker));
Thread t2 = new Thread(new NotifyTask(locker));
t1.start();
t2.start();
t3.start();
Thread.sleep(5000);
t4.start();
}
}
運(yùn)行結(jié)果
注意: 雖然是同時喚醒 3 個線程, 但是這 3 個線程需要競爭鎖. 所以并不是同時執(zhí)行, 而仍然是有先有后的執(zhí)行.
理解notify()和notifyAll()
notify
只喚醒等待隊列中的一個線程
.
其他線程還是乖乖等著.
notifyAll 一下全都喚醒, 需要這些線程重新競爭鎖.
wait和sleep的對比
唯一的相同點就是都可以讓線程放棄執(zhí)行一段時間.
1. wait
需要搭配
synchronized
使用
. sleep
不需要
.
2. wait
是
Object
的方法
sleep
是
Thread
的靜態(tài)方法
.
柚子快報激活碼778899分享:Java之線程篇四
精彩文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。