出現場景: 多個線程同時操作一個對象,如果該對象沒有線程安全的控制,便會出現線程安全問題。例如:我們有一個類A
public class A{
int count=0;
public void add1000(){
for(int i=0;i<1000;i++){
count++;
System.out.println(count);
}
}
}
如上代碼所示,A中有一個成員變量count,有一個讓count加1000的方法,並打印。如果聲明A一個對象,一個線程訪問沒問題,會從打印0~999。但當多個線程操作A的同一個對象,就可能發生一個現象:
public static void main(String[] args) {
Run run = new Run();
new Thread(new Runnable() {
@Override
public void run() {
run.add1000("A");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
run.add1000("B");
}
}).start();
}
從上面的代碼可以看到,啓動了兩個線程,每個線程都只是想讓A的count打印從0到999,但由於線程之間的運行方式是輪着運行的(對線程運行方法不瞭解,可自行搜索學習),會導致這兩個線程都操作count,count的值也是在一直變的,就不能保證add1000這個方法的原子性。
解決辦法: 對於解決辦法,java特別提供了保證線程安全的類和關鍵字,同時java的類中也有好多是線程安全和不安全兩個版本。
首先最簡單的解決辦法就是使用synchronized關鍵字
public synchronized void add1000(String s) {
for (int i = 0; i < 1000; i++) {
count++;
System.out.println(s + count);
}
}
在add1000方法前添加synchronized關鍵字便可保證該方法變成線程安全的,原理大概是,當某個線程調用此方法時,會獲取該實例的對象鎖,方法未結束之前,其他線程只能去等待。當這個方法執行完時,纔會釋放對象鎖。其他線程纔有機會去搶佔這把鎖,去執行該方法。同時該關鍵字也能同步代碼塊。
使用Lock類;lock是java concurrent包下的一個類,該類可以很自由的給任意代碼段添加鎖及釋放鎖。
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Run run = new Run(lock);
new Thread(new Runnable() {
@Override
public void run() {
run.add1000("A");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
run.add1000("B");
}
}).start();
}
public static class Run {
int count = 0;
Lock lock;
public Run(Lock lock){
this.lock=lock;
}
// count加1000
public synchronized void add1000(String s) {
for (int i = 0; i < 1000; i++) {
try{
lock.lock();
count++;
System.out.println(s + count);
}finally{
lock.unlock();
}
}
}
}
如上代碼便是使用Lock進行加鎖,如果你把finally的釋放鎖代碼註釋掉,第二個線程就無法執行該,程序一直卡在這,因爲線程一一直持有該段代碼的鎖,線程二一直等待獲取鎖,便發生了死鎖堵塞。