Java多執行緒實現以及執行緒安全筆記
Java虛擬機器允許應用程式併發地執行多個執行緒。
以下為多執行緒的實現常用的2種方法
(1)繼承Thread類,重寫run()方法
Thread本質上也是實現了Runnable介面的一個例項,代表一個執行緒的例項。啟用執行緒的唯一方法就是通過Thread類的start()方法。呼叫start()方法後並不是立即執行多執行緒程式碼,而是使得該執行緒變為可執行態(Running),什麼時候執行是由作業系統決定的。
class MyThread extends Thread{//建立函式體 public void run(){ System.out.println("Thread body");//執行緒函式體 } } public class Test{ public static void main(String[] args){ MyThread thread = new MyThread(); thread.start();//執行緒開啟 } }
(2)實現Runnable介面,並實現該介面的run()方法
主要步驟
- 自定義類並實現Runable介面,實現run()方法。
- 建立Thread物件,用實現Runnable介面的物件作為引數例項化該Thread物件
- 呼叫Thread的start()方法
class MyThread implements Runnable{ public void run(){ System.out.println("Thread body"); } } public class Test{ public static void main(String []args){ MyThread thread = new MyThread(); Thread t = new Thread(thread); t.start();//開啟執行緒 } }
這兩種方法最終都是通過Thread 的物件的API來控制執行緒的。
執行緒狀態:
-
NEW 新建狀態
-
RUNNABLE 執行狀態
-
BLOCK 受阻塞 執行緒具有CPU的執行資格
-
WAIT (無限期)等待 object的方法 notify(喚醒) 放棄CPU
-
TIMED_WAITING 休眠 sleep 放棄CPU
-
TERMINATED 死亡
執行緒池
-
節約開銷
-
由執行緒池工廠建立的,再呼叫執行緒池中的方法獲取執行緒,再通過執行緒去執行方法
-
在java.util.concurrent 中的Executors 類
-
實現執行緒池程式,使用供現場類Executors中的靜態方法建立執行緒物件,指定執行緒個數
static ExectorService newFixedThreadPool(int 個數) 返回執行緒池物件
返回的是ExecutorService介面的實現類(執行緒池物件)
介面實現類物件,呼叫方法submit (Runnable r) 提交執行緒執行任務
-
-
run方法沒有返回值,不能拋異常
執行緒開啟虛擬棧空間,棧記憶體是執行緒私有的
建立執行緒的目的:為了建立程式單獨的執行路徑
Thread.currentThread();返回正在執行的執行緒物件
執行緒安全
-
單執行緒沒有安全問題
-
多執行緒同時操作同一個共享資料,往往出現安全問題
-
解決:當一個執行緒進入資料操作的時候,無論是否休眠,其他執行緒只能等待。保證只有一個執行緒在操作
-
同步程式碼塊:
-
synchronized(任意物件){ 執行緒要操作的共享資料 }
-
同步物件,任意物件。物件:同步鎖,物件監視器 obj
-
同步保證安全性:沒有鎖的執行緒不能執行,只能等。加了同步後,執行緒進同步判斷鎖,獲取鎖,出同步釋放鎖,導致程式執行速度的下降。
-
執行緒遇到同步程式碼塊後,執行緒判斷同步鎖還有沒有。
-
如果有,獲取鎖,進入同步中,去執行,執行完畢後將鎖物件還回去。在同步中執行緒,進行了休眠,此時另一個執行緒,會執行。
-
沒有鎖的執行緒,不能進入同步中執行,被阻擋在同步程式碼塊的外面。在同步中的執行緒,不出去同步,不會釋放鎖。
-
-
-
同步方法:程式碼簡潔,將執行緒共享資料,和同步,抽取到一個方法中。物件鎖是本類物件引用this.如果方法為靜態,鎖是本類自己.class 屬性
-
-
電影院賣票,排隊上廁所原理
package ticket;
/**
* 多執行緒併發訪問同一個資料資源
* 3 個執行緒,對一個票資源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//建立Runnable介面實現類物件
Tickets t = new Tickets();
//建立3個Thread物件,傳遞Runnable介面實現類
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
package ticket;
public class Tickets implements Runnable{
private int ticket = 100;
//同步程式碼塊
// private Object obj = new Object();
// @Override
// public void run() {
// while (true){
// //對票數判斷,大於0,可以出售
// synchronized (obj){//不寫匿名物件是因為每次迴圈時,物件就變了
// if(ticket > 0){
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// }
// System.out.println(Thread.currentThread().getName()+"出售第"+ticket--);
// }
// }
// }
// }
//同步方法
@Override
public void run() {
while (true){
payTicket();
}
}
//對票數判斷,大於0,可以出售
public synchronized void payTicket(){
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"出售第"+ticket--);
}
}
}
Lock
-
可以替代synchronized,比他更靈活
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Tickets implements Runnable{
private int ticket = 100;
//在類的成員位置,建立Lock介面實現類的物件
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
//對票數判斷,大於0,可以出售
if(ticket > 0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"出售第"+ticket--);
} catch (InterruptedException e) {
}finally {
lock.unlock();
}}}}}
死鎖
-
同步鎖使用的弊端:當執行緒任務中出現了多個同步(多個鎖)時,如果同步中嵌套了其他的同步。這是容易引發一種現象:程式出現無限等待,這種現象稱為死鎖。
synchronized(A 鎖){
synchronized(B 鎖){
}
}
-
前提:必須是多執行緒的,出現同步巢狀
-
執行緒進入同步獲取鎖,不出去同步,不會釋放鎖
public class Main {
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
Thread t0 = new Thread(deadLock);
Thread t1 = new Thread(deadLock);
t0.start();
t1.start();
}
}
public class DeadLock implements Runnable{
private int i = 0;
public void run(){
while(true){
if(i%2 == 0){
//先進入A同步,再進入B同步
synchronized (LockA.lockA){
System.out.println("if 。。。locka");
synchronized (LockB.lockB){
System.out.println("if。。。lockb");
}
}
}else{
//先進入B同步,再進入A同步
synchronized (LockB.lockB){
System.out.println("else。。lockb");
synchronized (LockA.lockA) {
System.out.println("else。。locka");
}
}
}
i++;
}
}
}
public class LockA {
private LockA(){}
public static final LockA lockA = new LockA();
}
public class LockB {
private LockB(){}
public static final LockB lockB = new LockB();
}
等待喚醒機制
-
執行緒之間通訊:多個執行緒在處理同一個資源,單隻處理的動作(執行緒任務)卻不相同。通過一定的手段使各個執行緒能有效的利用資源。這種手段即——等待喚醒機制。
-
wait() 無限等
-
notify() 喚醒,一次只喚醒一個,任意的
-
notifyAll() 喚醒全部
//保證兩個執行緒交替輸入輸出值
public class ThreadDemo {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread tin = new Thread(in);
Thread tout = new Thread(out);
tin.start();
tout.start();
}
}
public class Resource {
public String name;
public String sex;
public boolean flag = false;
}
public class Input implements Runnable {
private Resource resource ;
public Input(Resource r){
resource = r;
}
//flag 為ture表示賦值完成,false為取值完成
@Override
public void run() {
int i = 0;
while(true){
synchronized (resource){
//標記是true,等待
if(resource.flag){
try{resource.wait();}catch (Exception e){}
}
if(i%2 ==0){
resource.name = "張";
resource.sex = "男";
}else{
resource.name = "li";
resource.sex = "nv";
}
//將對方執行緒喚醒,標記該為true
resource.flag = true;
resource.notify();
}
i++;
}
}
}
public class Output implements Runnable {
private Resource r ;//new物件之後輸出物件預設值,因為main方法中的物件和這物件不是一個。
public Output (Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
synchronized (r){
// 判斷標記,是false,等待
if(!r.flag){
try {
r.wait();
}catch (Exception e){}
}
System.out.println(r.name+".."+r.sex);
//標記改成false,喚醒對方執行緒
r.flag = false;
r.notify();
}
}
}
}