對于下面這段代碼袱耽,輸出是什么?
package com.conrrentcy.thread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThreadSafeProblem {
private final static Object locker = new Object();
private static int count = 0;
private static final Logger log = LoggerFactory
.getLogger(ThreadSafeProblem.class);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count++;
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count --;
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.info(" count value is {}", count);
}
}
以上的結(jié)果可能是整數(shù)也可能是負數(shù)也可能是0据途。因為java對靜態(tài)變量的自增绞愚、自減并不是原子性的啄巧。要徹底理解巫击,必須從字節(jié)碼角度來看钠怯。
對于靜態(tài)count++ 而言 敦锌,會產(chǎn)生如下JVM的指令
getstatic count //獲取靜態(tài)變量的值
iconst_1 //準備常量1
iadd //自增
putstatic count // 將修改后的在值放入count
對于靜態(tài)count-- 而言 痢站,會產(chǎn)生如下JVM的指令
getstatic count //獲取靜態(tài)變量的值
iconst_1 //準備常量1
isub //自減
putstatic count // 將修改后的在值放入count
java內(nèi)存模型中婆排,主存的每個線程的內(nèi)存需要同步霉猛,主存是真正進行運算的地方钻蹬, 主存運算完的值需要同步回線程內(nèi)存佛致。 在多個線程的情況下贮缕,如果主存的值可以被多個線程修改,那么當 同步回去時晌杰,就有極大可能和單獨一個線程修改的值不同跷睦。這就產(chǎn)生了線程安全問題。
如果指令按照線程順序運行肋演,就不會有任何問題
如果出現(xiàn)交錯抑诸,就可能有兩種情況
-
負數(shù)
image.png -
正數(shù)
image.png
臨界區(qū)
多線程的的問題主要是對共享資源發(fā)生讀寫指令交錯,就會出現(xiàn)問題爹殊。
一個代碼塊如果存在對共享資源的多線程讀寫操作蜕乡,稱之為臨界區(qū)。
競態(tài)條件
多個線程在臨界區(qū)運行梗夸,由于代碼執(zhí)行序列導(dǎo)致結(jié)果無法預(yù)測层玲,稱之為競態(tài)條件。
互斥
避免競態(tài)條件的發(fā)生反症,可以用以下方式:
- 阻塞式 synchronize lock
- 非阻塞式 原子變量
這章主要介紹synchronize 關(guān)鍵字的阻塞方法辛块,就是對象鎖。
package com.conrrentcy.thread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThreadSafeProblem {
private final static Object locker = new Object();
private static int count = 0;
private static final Logger log = LoggerFactory
.getLogger(ThreadSafeProblem.class);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (locker) {
count++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (locker) {
count--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.info(" count value is {}", count);
}
}
下面代碼铅碍,運行結(jié)果每次都為0润绵。
思考
- 如果synchronized 加在 for 循環(huán)外面
- 如果t1 synchronized object1 , t2 synchronized object2
- 如果t1 synchronized, t2不加
這三種情況各是什么行為?
synchronized 加在對象 和 方法上有什么區(qū)別胞谈?
變量的線程安全分析
成員變量和靜態(tài)變量
- 如果他們沒有共享尘盼,則線程安全
- 如果共享并且只有讀操作憨愉,線程安全。
- 如果共享有讀寫操作卿捎,則需要考慮線程安全配紫。(有可能只有寫操作么?)
package com.conrrentcy.thread;
import java.util.ArrayList;
import java.util.List;
public class ThreadSafeReference {
public static void main(String[] args) throws InterruptedException {
ThreadUnSafeList tl = new ThreadUnSafeList();
for(int i=0; i<2;i++){
new Thread(()->{
tl.method1(200);
},"t"+i).start();
}
ThreadSafeList tl2 = new ThreadSafeList();
for(int i=0;i <2;i++){
new Thread(()->{
tl2.method1(200);
},"t"+i).start();
}
}
}
class ThreadUnSafeList {
private List list = new ArrayList();
public void method1(int loopNumber){
for( int i=0; i<loopNumber;i++){
method2();
method3();
}
}
private void method2(){
list.add("1");
}
private void method3(){
list.remove(0);
}
}
class ThreadSafeList {
public void method1(int loopNumber){
List list = new ArrayList();
for( int i=0; i<loopNumber;i++){
method2(list);
method3(list);
}
}
private void method2(List list){
list.add("1");
}
private void method3(List list){
list.remove(0);
}
}
比較一下ThreadUnSafeList 和 ThreadSafeList 有啥不同午阵?下面是ThreadUnSafeList 的內(nèi)存模型
局部變量
- 局部變量是線程安全的
- 局部變量引用的對象如果沒有逃離方法的作用域躺孝,線程安全
- 局部變量引用的對象超出方法的作用域,需要考慮線程安全底桂。
- 局部變量在棧幀中重建多份括细,不存在共享
public static void test(){
int i=0;
i++;
}
下面是剛才代碼ThreadSafeList 的內(nèi)存模型
但是,光是局部變量也不能完全保證線程安全戚啥,需要保證在同一個線程內(nèi)部沒有起另外一個線程, 下面的代碼就不是線程安全的锉试,主要是在method3猫十, 又另外起了一個線程。導(dǎo)致list 雖然對是一個local 變量呆盖,但是在method3拖云, 內(nèi)部就不再是一個線程變量了。
package com.conrrentcy.thread;
import java.util.ArrayList;
import java.util.List;
public class ThreadExpose {
public static void main(String[] args) {
ThreadSafeList2 tlc= new ThreadSafeListChild();
for(int i=0;i <2;i++){
new Thread(()->{
tlc.method1(200);
},"t"+i).start();
}
}
}
class ThreadSafeList2 {
public void method1(int loopNumber){
List list = new ArrayList();
for( int i=0; i<loopNumber;i++){
method2(list);
method3(list);
}
}
public void method2(List list){
list.add("1");
}
public void method3(List list){
list.remove(0);
}
}
class ThreadSafeListChild extends ThreadSafeList2{
public void method3(List list){
new Thread(()->{
list.remove(0);
},"tchild").start();
}
}
class modifier - private, final 能夠加強線程安全