2.線程同步
2.1賣票【應用】
-
案例需求
某電影院目前正在上映國産大片,共有100張票,而它有3個視窗賣票,請設計一個程式模拟該電影院賣票
- 實作步驟
- 定義一個類SellTicket實作Runnable接口,裡面定義一個成員變量:private int tickets = 100;
- 在SellTicket類中重寫run()方法實作賣票,代碼步驟如下
- 判斷票數大于0,就賣票,并告知是哪個視窗賣的
- 賣了票之後,總票數要減1
- 票賣沒了,線程停止
- 定義一個測試類SellTicketDemo,裡面有main方法,代碼步驟如下
- 建立SellTicket類的對象
- 建立三個Thread類的對象,把SellTicket對象作為構造方法的參數,并給出對應的視窗名稱
- 啟動線程
- 代碼實作
public class SellTicket implements Runnable {
private int tickets = 100;
//在SellTicket類中重寫run()方法實作賣票,代碼步驟如下
@Override
public void run() {
while (true) {
if(ticket <= 0){
//賣完了
break;
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticket + "張票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//建立SellTicket類的對象
SellTicket st = new SellTicket();
//建立三個Thread類的對象,把SellTicket對象作為構造方法的參數,并給出對應的視窗名稱
Thread t1 = new Thread(st,"視窗1");
Thread t2 = new Thread(st,"視窗2");
Thread t3 = new Thread(st,"視窗3");
//啟動線程
t1.start();
t2.start();
t3.start();
}
}
2.2賣票案例的問題【了解】
- 賣票出現了問題
- 相同的票出現了多次
- 出現了負數的票
-
問題産生原因
線程執行的随機性導緻的,可能在賣票過程中丢失cpu的執行權,導緻出現問題
2.3同步代碼塊解決資料安全問題【應用】
- 安全問題出現的條件
- 是多線程環境
- 有共享資料
- 有多條語句操作共享資料
- 如何解決多線程安全問題呢?
- 基本思想:讓程式沒有安全問題的環境
- 怎麼實作呢?
- 把多條語句操作共享資料的代碼給鎖起來,讓任意時刻隻能有一個線程執行即可
- Java提供了同步代碼塊的方式來解決
- 同步代碼塊格式:
synchronized(任意對象) {
多條語句操作共享資料的代碼
}
synchronized(任意對象):就相當于給代碼加鎖了,任意對象就可以看成是一把鎖
- 同步的好處和弊端
- 好處:解決了多線程的資料安全問題
- 弊端:當線程很多時,因為每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程式的運作效率
- 代碼示範
public class SellTicket implements Runnable {
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) { // 對可能有安全問題的代碼加鎖,多個線程必須使用同一把鎖
//t1進來後,就會把這段代碼給鎖起來
if (tickets > 0) {
try {
Thread.sleep(100);
//t1休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//視窗1正在出售第100張票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");
tickets--; //tickets = 99;
}
}
//t1出來了,這段代碼的鎖就被釋放了
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "視窗1");
Thread t2 = new Thread(st, "視窗2");
Thread t3 = new Thread(st, "視窗3");
t1.start();
t2.start();
t3.start();
}
}
2.4同步方法解決資料安全問題【應用】
-
同步方法的格式
同步方法:就是把synchronized關鍵字加到方法上
修飾符 synchronized 傳回值類型 方法名(方法參數) {
方法體;
}
同步方法的鎖對象是什麼呢?
this
-
靜态同步方法
同步靜态方法:就是把synchronized關鍵字加到靜态方法上
修飾符 static synchronized 傳回值類型 方法名(方法參數) {
方法體;
}
同步靜态方法的鎖對象是什麼呢?
類名.class
- 代碼示範
public class MyRunnable implements Runnable {
private static int ticketCount = 100;
@Override
public void run() {
while(true){
if("視窗一".equals(Thread.currentThread().getName())){
//同步方法
boolean result = synchronizedMthod();
if(result){
break;
}
}
if("視窗二".equals(Thread.currentThread().getName())){
//同步代碼塊
synchronized (MyRunnable.class){
if(ticketCount == 0){
break;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票");
}
}
}
}
}
private static synchronized boolean synchronizedMthod() {
if(ticketCount == 0){
return true;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票");
return false;
}
}
}
public class Demo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("視窗一");
t2.setName("視窗二");
t1.start();
t2.start();
}
}
2.5Lock鎖【應用】
雖然我們可以了解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪裡加上了鎖,在哪裡釋放了鎖,為了更清晰的表達如何加鎖和釋放鎖,JDK5以後提供了一個新的鎖對象Lock
Lock是接口不能直接執行個體化,這裡采用它的實作類ReentrantLock來執行個體化
- ReentrantLock構造方法
方法名 | 說明 |
ReentrantLock() | 建立一個ReentrantLock的執行個體 |
- 加鎖解鎖方法
方法名 | 說明 |
void lock() | 獲得鎖 |
- 代碼示範
public class Ticket implements Runnable {
//票的數量
private int ticket = 100;
private Object obj = new Object();
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//synchronized (obj){//多個線程必須使用同一把鎖.
try {
lock.lock();
if (ticket <= 0) {
//賣完了
break;
} else {
Thread.sleep(100);
ticket--;
System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticket + "張票");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
// }
}
}
}
public class Demo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("視窗一");
t2.setName("視窗二");
t3.setName("視窗三");
t1.start();
t2.start();
t3.start();
}
}
2.6死鎖【了解】
-
概述
線程死鎖是指由于兩個或者多個線程互相持有對方所需要的資源,導緻這些線程處于等待狀态,無法前往執行
- 什麼情況下會産生死鎖
- 資源有限
- 同步嵌套
- 代碼示範
public class Demo {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(()->{
while(true){
synchronized (objA){
//線程一
synchronized (objB){
System.out.println("小康同學正在走路");
}
}
}
}).start();
new Thread(()->{
while(true){
synchronized (objB){
//線程二
synchronized (objA){
System.out.println("小薇同學正在走路");
}
}
}
}).start();
}
}