一個程序在運行起來的時候會轉換成進程,通常含有多個線程。
通常情況下,一個進程中的比較耗時的操作(如長循環、文件上傳下載、網絡資源獲取等),往往會採用多線程來解決。
比如顯示生活中,銀行取錢問題、火車票多個售票窗口的問題,通常會涉及到併發的問題,從而需要多線程的技術。
當進程中有多個併發線程進入一個重要數據的代碼塊時,在修改數據的過程中,很有可能引發線程安全問題,從而造成數據異常。例如,正常邏輯下,同一個編號的火車票只能售出一次,卻由於線程安全問題而被多次售出,從而引起實際業務異常。
第一種實現線程安全的方式 同步代碼塊
package com.bpan.spring.beans.thread;
import com.sun.org.apache.regexp.internal.recompile;
public class ThreadSynchronizedSecurity {
static int tickets = 10;
class SellTickets implements Runnable{@Override
public void run() {
// 同步代碼塊
while(tickets > 0) {
synchronized (this) {
// System.out.println(this.getClass().getName().toString());
if (tickets <= 0) {
return;
}
System.out.println(Thread.currentThread().getName()+"--->售出第: "+tickets+" 票");
tickets--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (tickets <= 0) {
System.out.println(Thread.currentThread().getName()+"--->售票結束!");
}
}
}
}
public static void main(String[] args) {
SellTickets sell = new ThreadSynchronizedSecurity().new SellTickets();
Thread thread1 = new Thread(sell, "1號窗口");
Thread thread2 = new Thread(sell, "2號窗口");
Thread thread3 = new Thread(sell, "3號窗口");
Thread thread4 = new Thread(sell, "4號窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
第二種 方式 同步方法
package com.bpan.spring.beans.thread;
public class ThreadSynchroniazedMethodSecurity {
static int tickets = 10;
class SellTickets implements Runnable{@Override
public void run() {
//同步方法
while (tickets > 0) {
synMethod();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (tickets<=0) {
System.out.println(Thread.currentThread().getName()+"--->售票結束");
}
}
}
synchronized void synMethod() {
synchronized (this) {
if (tickets <=0) {
return;
}
System.out.println(Thread.currentThread().getName()+"---->售出第 "+tickets+" 票 ");
tickets-- ;
}
}
}
public static void main(String[] args) {
SellTickets sell = new ThreadSynchroniazedMethodSecurity().new SellTickets();
Thread thread1 = new Thread(sell, "1號窗口");
Thread thread2 = new Thread(sell, "2號窗口");
Thread thread3 = new Thread(sell, "3號窗口");
Thread thread4 = new Thread(sell, "4號窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}}
第三種 方式 Lock鎖機制, 通過創建Lock對象,採用lock()加鎖,unlock()解鎖,來保護指定的代碼塊
package com.bpan.spring.beans.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ThreadLockSecurity {
static int tickets = 10;
class SellTickets implements Runnable{
Lock lock = new ReentrantLock();@Override
public void run() {
// Lock鎖機制
while(tickets > 0) {
try {
lock.lock();
if (tickets <= 0) {
return;
}
System.out.println(Thread.currentThread().getName()+"--->售出第: "+tickets+" 票");
tickets--;
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}finally {
lock.unlock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (tickets <= 0) {
System.out.println(Thread.currentThread().getName()+"--->售票結束!");
}
}
}
public static void main(String[] args) {
SellTickets sell = new ThreadLockSecurity().new SellTickets();
Thread thread1 = new Thread(sell, "1號窗口");
Thread thread2 = new Thread(sell, "2號窗口");
Thread thread3 = new Thread(sell, "3號窗口");
Thread thread4 = new Thread(sell, "4號窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
最後總結:
由於synchronized是在JVM層面實現的,因此係統可以監控鎖的釋放與否;而ReentrantLock是使用代碼實現的,系統無法自動釋放鎖,需要在代碼中的finally子句中顯式釋放鎖lock.unlock()。
另外,在併發量比較小的情況下,使用synchronized是個不錯的選擇;但是在併發量比較高的情況下,其性能下降會很嚴重,此時ReentrantLock是個不錯的方案。
補充:
在使用synchronized 代碼塊時,可以與wait()、notify()、nitifyAll()一起使用,從而進一步實現線程的通信。
其中,wait()方法會釋放佔有的對象鎖,當前線程進入等待池,釋放cpu,而其他正在等待的線程即可搶佔此鎖,獲得鎖的線程即可運行程序;線程的sleep()方法則表示,當前線程會休眠一段時間,休眠期間,會暫時釋放cpu,但並不釋放對象鎖,也就是說,在休眠期間,其他線程依然無法進入被同步保護的代碼內部,當前線程休眠結束時,會重新獲得cpu執行權,從而執行被同步保護的代碼。
wait()和sleep()最大的不同在於wait()會釋放對象鎖,而sleep()不會釋放對象鎖。
notify()方法會喚醒因爲調用對象的wait()而處於等待狀態的線程,從而使得該線程有機會獲取對象鎖。調用notify()後,當前線程並不會立即釋放鎖,而是繼續執行當前代碼,直到synchronized中的代碼全部執行完畢,纔會釋放對象鎖。JVM會在等待的線程中調度一個線程去獲得對象鎖,執行代碼。
需要注意的是,wait()和notify()必須在synchronized代碼塊中調用。
notifyAll()是喚醒所有等待的線程。
package com.bpan.spring.beans.thread;
public class ThreadDemo {
static final Object obj = new Object();
//第一個子線程
static class ThreadA implements Runnable{@Override
public void run() {
int count = 10;
while(count > 0) {
synchronized (ThreadDemo.obj) {
System.out.println("A-----"+count);
count--;
synchronized (ThreadDemo.obj) {
//notify()方法會喚醒因爲調用對象的wait()而處於等待狀態的線程,從而使得該線程有機會獲取對象鎖。
//調用notify()後,當前線程並不會立即釋放鎖,而是繼續執行當前代碼,直到synchronized中的代碼全部執行完畢,
ThreadDemo.obj.notify();
try {
ThreadDemo.obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
static class ThreadB implements Runnable{
@Override
public void run() {
int count = 10;
while(count > 0) {
synchronized (ThreadDemo.obj) {
System.out.println("B-----"+count);
count--;
synchronized (ThreadDemo.obj) {
//notify()方法會喚醒因爲調用對象的wait()而處於等待狀態的線程,從而使得該線程有機會獲取對象鎖。
//調用notify()後,當前線程並不會立即釋放鎖,而是繼續執行當前代碼,直到synchronized中的代碼全部執行完畢,
ThreadDemo.obj.notify();
try {
ThreadDemo.obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) {
new Thread(new ThreadA()).start();
new Thread(new ThreadB()).start();
}}