多線程運行原理:
-
JVM執行Main方法,操作系統會開闢一道路徑(即主線程)到CPU,但是在多線程的情況下,沒啓動一個線程,相當於開闢一條到CPU的路,CPU可以自己選擇,因此,CPU的執行一般是無序的。
-
而在內存當中,當主方法開始執行時,主方法在棧中壓棧執行,當調用start()方法後,會重新開闢一個空間,供新啓動的線程使用
線程安全問題解決
以售票問題爲例
問題:出現了重複票和不存在的票
- 解決方法1:使用同步代碼塊
格式:
synchronized(鎖對象){
//可能出現線程安全問題的代碼塊
}
注意:
1.通過代碼塊中的鎖對象,可以使用任意的對象
2.但是必須保證多個線程使用的鎖對象是同一個
3.鎖對象作用:
- 把同步代碼塊鎖住,只讓一個線程在同步代碼塊中執行
代碼實現:
public class RunnableImp implements Runnable {
private int ticket=100;
Object o=new Object();
@Override
public void run() {
while(true){
synchronized (o){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"售出了第"+ticket+"張票。。。");
ticket--;
}
}
}
}
}
總結:同步中的線程,沒有執行完畢不會釋放鎖,同步外的線程沒有鎖進不去同步。同步保證了只能由一個線程在同步中執行共享數據,保證了安全。缺點是程序頻繁的判斷鎖,獲取鎖,釋放鎖,程序的效率會降低
- 使用同步方法
在方法上添加Synchronized關鍵字。
定義一個同步方法,同步方法也會把方法內部的代碼鎖住,只讓一個線程執行,同步方法的鎖對象是new RunnableImpl(),也就是this
代碼實現:
public class RunnableImpl implements Runnable {
private int ticket=100;
@Override
public void run() {
while(true){
selticket();
}
}
public synchronized void selticket(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"售出了第"+ticket+"張票。。。");
ticket--;
}
}
}
3 靜態同步方法.
此時鎖對象不是this,this是創建對象之後產生的,靜態方法優先於對象。靜態方法的鎖對象是本類的class屬性 --》class文件對象
代碼演示:
public class RunnableImpl implements Runnable {
private static int ticket=100;
@Override
public void run() {
while(true){
selticketStatic();
}
}
public static synchronized void selticketStatic(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"售出了第"+ticket+"張票。。。");
ticket--;
}
}
}
4 Lock類
使用步驟:
- 在成員位置創建一個ReentrantLock對象
- 在可能會出現安全問題的代碼前調用Lock接口中的方法Lock獲取鎖
- 在可能會出現安全問題的代碼後調用Lock接口中的方法unlock釋放鎖
代碼示例:
public class RunnableImp implements Runnable {
private int ticket=100;
Lock l=new ReentrantLock();
@Override
public void run() {
while(true) {
l.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "售出了第" + ticket + "張票。。。");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
}
}