前情提要
通過本文你可以收穫
- 通過多線程計數求和了解synchronized的應用場景,以及其他加鎖方式爲什麼不生效
- 框架 對象鎖和類鎖
1、同一時刻只有一個線程執行這段代碼
2、最基本的互斥同步手段
3、分類 一共有倆種鎖:
1、對象鎖
1、同步代碼塊鎖
2、方法鎖
3 類鎖
案例演示與分析:
1、不使用鎖 求和會出問題
public class MutilThreadCount implements Runnable{
static int sum = 0;
@Override
public void run() {
//synchronized (this){
for (int i = 0; i < 100000; i++) {
sum++;
}
//}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
預期結果200000 結果小於200000
2、使用volatile也會出問題
public class MutilThreadCount implements Runnable{
static volatile int sum = 0;
@Override
public void run() {
//synchronized (this){
for (int i = 0; i < 100000; i++) {
sum++;
}
//}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
預期結果200000 結果小於200000
volititle有倆個特徵:
1、變量對線程是可見的
雖然變量對各個線程是可見的, 但是sum++不是原子的,包含了獲取值,自增,賦值 每一個操作都可能被其他線程打斷 修改了變量的值
2、防止指令重排
例子就是雙重懶加載的單例模式
3、不鎖同一個對象 求和會出問題
public class MutilThreadCount implements Runnable{
Object lock1 = new Object();
Object lock2 = new Object();
static int sum = 0;
@Override
public void run() {
synchronized (lock1){
for (int i = 0; i < 100000; i++) {
sum=sum+1;
}
}
synchronized (lock2){
for (int i = 0; i < 100000; i++) {
sum=sum+1;
}
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
預期:400000 結果小於400000 第二個求和循環使用lock1就正確了
4、在方法上使用synchronized 求和正確
public class MutilThreadCount implements Runnable{
static int sum = 0;
@Override
public void run() {
synchronized (this){
for (int i = 0; i < 100000; i++) {
sum++;
}
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
預期200000結果200000
5、使用synchronized鎖類 求和正確(鎖類意味着鎖住了Class對象 這個類所有的實例都被鎖了 所以即使使用lock1和lock2也沒問題 )
public class MutilThreadCount implements Runnable{
static int sum = 0;
@Override
public void run() {
synchronized (MutilThreadCount.class){
for (int i = 0; i < 100000; i++) {
sum=sum+1;
}
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
下面是新建倆個線程對象 但因爲是鎖的類 ,所以結果也正確,但是如果鎖this就不行了
public class MutilThreadCount implements Runnable{
private Object lock1 = new Object();
private Object lock2 = new Object();
static int sum = 0;
@Override
public void run() {
synchronized (MutilThreadCount.class){
for (int i = 0; i < 100000; i++) {
sum=sum+1;
}
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count1 = new MutilThreadCount();
MutilThreadCount count2 = new MutilThreadCount();
Thread t1 = new Thread(count1);
Thread t2 = new Thread(count2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
預期20000 結果20000
6、使用AtomicInteger(更好的voltitle變量) 求和正確
public class MutilThreadCount implements Runnable{
static AtomicInteger sum = new AtomicInteger();
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
sum.addAndGet(1);
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
7、使用CAS技術(p263) 學懂aqs需要對照着併發包一起學習
8、使用retreentlock
9、使用futuretask來做
voltitle講解的不錯
https://crossoverjie.top/%2F2018%2F03%2F09%2Fvolatile%2F
https://zhuanlan.zhihu.com/p/56191979
分析
爲什麼前面倆種結果不對?
sum++不是一個原子操作
分爲三步:
讀取sum
把sum+1
把sum寫回內存
倆個線程會在這三步中任何一步交替執行,擾亂了sum的值
爲什麼使用volitile也不行?不是傳說中volitile是對線程可見的麼?
爲什麼最後一種執行結果沒問題:
在run方法中加了鎖關鍵字就正常輸出20000了
鎖this也就是意味着給當前對象加了鎖
synchronized可以簡單的理解爲同一時刻只有一個線程執行這段代碼
肘後備急
synchronized
-
方法拋出異常 jvm會釋放鎖
-
synchronized 作用於非static的方法 就是鎖了對象 和作用於this一樣
作用於static方法就是鎖了類,鎖了類也就是鎖了這個類所有的對象實例,和synchronized(xx.class)作用一樣
- 可重入:從外層函數獲得鎖後,內層函數可以直接獲取該鎖
同一個方法,不是同一個方法,不是一個類的方法都具有可重入性質,重入意味着獲取鎖的粒度是線程 不是調用。
可重入原理:
-
不可中斷:
-
加鎖和釋放鎖的原理
-
可見性原理:
需要了解Java內存模型 共享變量和線程變量
synchronize釋放後 會把變量重新寫入主內存,另一個線程也會從主內存中把數據拷貝到線程內存,
但是爲什麼volitile不行?
-
synchronize缺陷 :
1、釋放鎖的情況少,試圖獲得鎖不能設定超時,不能中斷一個正在試圖獲取鎖的線程
lock更好 可以手動釋放鎖和設置超時
2、不夠靈活 讀寫鎖更靈活
3、無法知道是否成功獲取到鎖
-
synchronize只會在倆種情況下釋放鎖:
第一就是執行完成任務,第二是拋出出異常
-
使用注意
鎖對象不能爲空:因爲鎖的信息是保存在對象頭中的
作用域不要太大,會嚴重降低效率
避免死鎖
-
如何選擇synchronize和Lock
儘量不要使用這倆個,可以使用併發包下的
優先使用synchronize 因爲代碼量少?
如果需要Lock特性 那麼就使用Lock
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yFY7nzK7-1590563267129)(/Users/wwh/Library/Application Support/typora-user-images/image-20200303001609889.png)]