把java基礎擼一邊,從簡單的開始。
執行緒部分:
存在都是有理由的。如果沒有執行緒我們的程式只能是下圖這個樣子,想象一個下假如有1千萬個請求,每個請求1秒,這得請求多長時間
如果不止一個視窗處理事情,這個時候,執行緒的優點就體現出來了,這樣執行完這麼多請求就除4了
有點是會體現出來,但同時也暴露出了多執行緒的不足。一個CPU只能執行一個執行緒。四核也就是可以執行4個執行緒,平時看這開100個執行緒也沒事,一下就執行完了,但是這些CPU執行的時間片段太短,執行快所以看上去也沒事,但是如果超過了一定範疇也會出問題,(這不是本章的重點),還有就是資料容易成為髒資料,如果多個執行緒去修改同一個int,那麼這個字串最終是什麼樣呢?還有各種搶佔資源,死鎖...等等。
本章重點說的是髒資料問題。其他的後續更新
例項,多執行緒下髒資料的出現:
public class Demo21 extends Thread{
private int va = 1;
@Override
public void run() {
for (int i = 0 ; i < 100 ; i++){
va ++ ;
System.out.println("Thread Name :"+Thread.currentThread().getName() + "va :" +va);
}
}
public static void main(String[] age){
Demo21 demo21 = new Demo21();
demo21.start();
Demo21 demo22 = new Demo21();
demo22.start();
Demo21 demo33 = new Demo21();
demo33.start();
}
}複製程式碼
三個執行緒開啟,對va進行自加1。按照理想狀態,是1 2 3 ...300
但是結果是什麼樣呢?
Thread Name :Thread-0va :2
Thread Name :Thread-2va :2
Thread Name :Thread-1va :2
Thread Name :Thread-2va :3
Thread Name :Thread-0va :3
複製程式碼
三個執行緒 同時列印2。明明va++很短,執行很快為什麼還是會出現這個情況。
簡單看一下jvm的執行空間是什麼回事,這裡看兩個區域,執行緒共享區,執行緒獨佔區。常量是會放到執行緒共享區的,也就是說沒個執行緒都可以拿到這個值,而執行緒獨佔區,裡面的資料只可以被當前執行緒享用(執行緒之間的資料通訊另說)。這樣就可以瞭解到為什麼會出現這個情況。
1:資料不是可以馬上寫的,從地中中讀到資料進入CPU快取,CPU再做修改,修改完之後在給到主存中
2:A執行緒修改資料B執行緒並不知道。
執行緒一個危險就是這個髒資料,破壞了資料的一致性 。解決這些方法java中提供了很多操作
synchronize,AtomicIntege,LongAdder
這次主要介紹synchronize
public class Demo21 implements Runnable{
private int va = 1;
public void get() {
synchronized (Demo21.class){
va++;
System.out.println("Thread Name :"+Thread.currentThread().getName() + "va :" +va);
}
}
@Override
public void run() {
for (int i = 0 ; i < 100 ; i++){
get();
}
}
public static void main(String[] age){
Demo21 demo21 = new Demo21();
Thread thread = new Thread(demo21);
Thread thread1 = new Thread(demo21);
Thread thread2 = new Thread(demo21);
thread.start();
thread1.start();
thread2.start();
}
}複製程式碼
列印結果
Thread Name :Thread-0va :2
Thread Name :Thread-0va :3
Thread Name :Thread-0va :4
Thread Name :Thread-0va :5
Thread Name :Thread-1va :6
Thread Name :Thread-0va :7
Thread Name :Thread-0va :8
Thread Name :Thread-0va :9
Thread Name :Thread-0va :10
複製程式碼
可以見到加了這個關鍵字就可以順序執行。
參考一下java synchronize原理總結,對synchronize做一個初步瞭解
synchronize的底層是使用作業系統的mutex lock實現
記憶體可見性:一個執行緒對共享變數值的修改,能夠及時被其他執行緒看到。
操作原子性:持有同一個鎖的兩個同步快只能序列進入
synchronize保證 1,2,3,4執行緒執行synchronize修飾包括的程式程式碼塊中,是保證進入的只有一個執行緒。synchronize可以理解為一個只允許一個執行緒進入的大門,進來一個就用它持有的鎖給鎖住,防止其他執行緒進入,當程式碼塊執行完畢之後,再開啟。
java是物件導向的語言,synchronize用的鎖也是存在java物件頭裡。
synchronized有三種使用方式:
1:修飾例項方法
2:修飾靜態方法
3:修飾程式碼塊
修飾例項方法:
public class A {
public void A(){
System.out.println("A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void B(){
System.out.println("B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}複製程式碼
public class Demo21 {
public static void main(String[] age){
A a = new A();
new Thread(new Runnable() {
@Override
public void run() {
long l = System.currentTimeMillis();
a.A();
long l2 = System.currentTimeMillis() - l;
System.out.println(l2);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
long l = System.currentTimeMillis();
a.B();
long l2 = System.currentTimeMillis() - l;
System.out.println(l2);
}
}).start();
}
}複製程式碼
如果沒有synchronize修飾
A
B
1001
1001
複製程式碼
是沒有序列執行的
public synchronized void A()
public synchronized void B()複製程式碼
修飾例項方法之後
A
1001
B
2000
複製程式碼
可以看到是序列執行,有鎖的效果
public class Demo21 {
public static void main(String[] age){
A a = new A();
A a1 = new A();
new Thread(new Runnable() {
@Override
public void run() {
long l = System.currentTimeMillis();
a.A();
long l2 = System.currentTimeMillis() - l;
System.out.println(l2);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
long l = System.currentTimeMillis();
a1.B();
long l2 = System.currentTimeMillis() - l;
System.out.println(l2);
}
}).start();
}
}複製程式碼
如果是不同的例項執行A B方法
A
B
1001
1001
複製程式碼
這樣就可以知道,修飾非靜態例項方法的鎖,就是它的例項物件
修飾靜態方法:
public static synchronized void B()
public static synchronized void A()
複製程式碼
修飾靜態方法之後
A
B
1000
2000
複製程式碼
建立兩個例項,也是序列執行,這樣可以證明是,當前類加鎖,進入同步程式碼塊錢要獲取當前類物件的鎖
修飾程式碼塊
public void A(){
synchronized (A.class){
System.out.println("A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void B(){
synchronized (A.class) {
System.out.println("B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}複製程式碼
程式碼執行後
A
B
1001
2000
複製程式碼
在synchronize(物件.class)這個物件就是這個同步方法的鎖了
這裡就是物件synchronize做了一下簡單的認識,其實還有很多複雜的東西,要了解synchronize還需要知道jvm,作業系統等等。但能力不足,就介紹到這裡。
推薦三篇文章: