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