線程基礎(chǔ)和多線程相關(guān)


基本線程類(lèi)

1. 繼承Thread類(lèi)

public class MyThread extends Thread {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}  
MyThread myThread1 = new MyThread();  
MyThread myThread2 = new MyThread();  
myThread1.start();  
myThread2.start();

Thread類(lèi)本質(zhì)上是實(shí)現(xiàn)Runnable接口的一個(gè)實(shí)例,代表一個(gè)線程的實(shí)例。啟動(dòng)線程的唯一方法是通過(guò)Thread類(lèi)的start()實(shí)例方法。start()是一個(gè)native方法,它將啟動(dòng)一個(gè)新線程,并執(zhí)行run()方法。這種方式實(shí)現(xiàn)多線程很簡(jiǎn)單,通過(guò)自己的類(lèi)直接extend Thread,并復(fù)寫(xiě)run()方法,就可以啟動(dòng)新線程并執(zhí)行自己定義的run()方法。 **多個(gè)線程之間無(wú)法共享線程類(lèi)的實(shí)例變量 **

2.實(shí)現(xiàn)Runnable接口

public class MyThread extends OtherClass implements Runnable {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
} 
MyThread myThread = new MyThread();  
Thread thread = new Thread(myThread);  
thread.start(); 

3.實(shí)現(xiàn)Callable接口通過(guò)FutureTask包裝器來(lái)創(chuàng)建線程

Callable接口提供了一個(gè)call()方法可以作為線程執(zhí)行體,這個(gè)方法具有返回值,還可以聲明拋出異常

實(shí)現(xiàn)Callable接口通過(guò)FutureTask包裝器來(lái)創(chuàng)建Thread線程future模式 并發(fā)模式的一種, 可以有二種形式, 無(wú)阻賽和阻塞,分別是isDoneget其中future 對(duì)象用來(lái)存放該線程的返回值以及狀態(tài)

//Callable接口
public interface Callable<V>   { 
  V call() throws Exception;  
} 

public class SomeCallable<V> extends OtherClass implements Callable<V> {

    @Override
    public V call() throws Exception {
        // TODO Auto-generated method stub
        return null;
    }
}
Callable<V> oneCallable = new SomeCallable<V>();   
//由Callable<Integer>創(chuàng)建一個(gè)FutureTask<Integer>對(duì)象:   
FutureTask<V> oneTask = new FutureTask<V>(oneCallable);   
//注釋?zhuān)篎utureTask<Integer>是一個(gè)包裝器,它通過(guò)接受Callable<Integer>來(lái)創(chuàng)建,
//它同時(shí)實(shí)現(xiàn)了Future和Runnable接口。 
  //由FutureTask<Integer>創(chuàng)建一個(gè)Thread對(duì)象:   
Thread oneThread = new Thread(oneTask);   
oneThread.start();   //至此,一個(gè)線程就創(chuàng)建完成了

4.使用ExccutorService,Callable,Future實(shí)現(xiàn)有返回結(jié)果的線程

ExecutorService pool = Executors.newFixedThreadPool(taskSize); 
   for (int i = 0; i < taskSize; i++) {  
    Callable c = new MyCallable();  
    // 執(zhí)行任務(wù)并獲取Future對(duì)象  
    Future f = pool.submit(c); 
    list.add(f);  
   }  
    // 關(guān)閉線程池  
    pool.shutdown();  
    //從Future對(duì)象上獲取任務(wù)的返回值
    f.get()     
   // 獲取所有并發(fā)任務(wù)的運(yùn)行結(jié)果  
//實(shí)現(xiàn)Callable接口
class MyCallable implements Callable<Object> {  
public Object call() throws Exception {  
   return null;  
}  
  • ExecutoreService提供了submit()方法,傳遞一個(gè)Callable,或Runnable,返回Future。如果Executor后臺(tái)線程池還沒(méi)有完成Callable的計(jì)算,這調(diào)用返回Future對(duì)象的get()方法,會(huì)阻塞直到計(jì)算完成
ExecutorService e = Executors.newFixedThreadPool(taskSize); //新建線程池
 //submit方法有多重參數(shù)版本,及支持callable也能夠支持runnable接口類(lèi)型.
Future future = e.submit(new myCallable());
future.isDone() //return true,false 無(wú)阻塞
future.get() // return 返回值,阻塞直到該線程運(yùn)行結(jié)束

總結(jié) :Runnable,Callable接口與Thread類(lèi)的對(duì)比

  • 采用實(shí)現(xiàn)Runnable,Callable接口優(yōu)缺點(diǎn):
    1,接口可以多繼承,繼承了Runnable接口還能繼承其他接口
    2,適合多個(gè)相同線程來(lái)處理同一份資源的情況,
    3,缺點(diǎn)是,編程稍微復(fù)雜,訪問(wèn)當(dāng)前線程必須使用Thread.currentThread()
  • 采用繼承Thread類(lèi)優(yōu)缺點(diǎn):
    1,編寫(xiě)簡(jiǎn)單,訪問(wèn)當(dāng)前線程可直接用this
    2,缺點(diǎn)是,不能再繼承其他類(lèi)
    綜上,建議采用實(shí)現(xiàn)Runnable接口的方法來(lái)創(chuàng)建和啟動(dòng)線程

synchronized關(guān)鍵字

synchronized關(guān)鍵字用于修飾方法和代碼塊,以實(shí)現(xiàn)同步,當(dāng)多個(gè)線程在執(zhí)行被synchronized修飾的代碼,以排隊(duì)的方式進(jìn)行處理。當(dāng)一個(gè)線程調(diào)用被修飾的代碼時(shí),先判斷有沒(méi)有被上鎖,如果上鎖就說(shuō)明有其他線程在調(diào)用,必須等待其他線程結(jié)束調(diào)用后才能執(zhí)行這段代碼,synchronized可以在任意對(duì)象以及方法上加鎖,加鎖的這段代碼被稱(chēng)為“互斥區(qū)”或者“臨界區(qū)”

synchronized關(guān)鍵字加到static靜態(tài)方法是給Class類(lèi)上鎖

synchronized關(guān)鍵字加到非static靜態(tài)方法上是給對(duì)象上鎖

關(guān)于并行,并發(fā)和同步的概念

  • 并行:
    • 多個(gè)CPU實(shí)例或者多臺(tái)機(jī)器同時(shí)執(zhí)行一段處理邏輯,是真正的同時(shí).
  • 并發(fā)
    • 通過(guò)CPU調(diào)度算法,讓用戶看上去同時(shí)執(zhí)行,實(shí)際上CPU操作層面不是真正的同時(shí),并發(fā)往往場(chǎng)景中有公共資源,那么針對(duì)這個(gè)公用的資源往往產(chǎn)生瓶頸,我們會(huì)用TPS或者QPS來(lái)反應(yīng)這個(gè)系統(tǒng)的處理能力
  • 同步
    • Java中的同步指的是通過(guò)人為的控制和調(diào)度,保證共享資源的多線程訪問(wèn)成為線程安全,.來(lái)確保結(jié)果的準(zhǔn)確,如上面的

線程安全

  • 指在并發(fā)的情況下,改代碼經(jīng)過(guò)多線程的使用,線程的調(diào)度順序不影響任何結(jié)果,這個(gè)時(shí)候使用多線程,我們只需要關(guān)注系統(tǒng)功能的內(nèi)存,Cpu是不是夠用即可,反過(guò)來(lái),線程不安全就意味著線程的調(diào)度順序會(huì)響應(yīng)最終結(jié)果
    1. 多個(gè)線程同時(shí)操作一個(gè)全局變量是不安全的,使用自旋鎖并不是絕對(duì)的安全(因?yàn)閱螌?xiě)多讀)。
    2. 在多個(gè)線程進(jìn)行讀寫(xiě)操作時(shí),仍然能夠保證數(shù)據(jù)的正確 。使用互斥鎖可以實(shí)現(xiàn),但是消耗性能
    3. 所有更新UI的操作都在主線程上執(zhí)行

線程的狀態(tài)

  • 新建狀態(tài):使用new 關(guān)鍵字和Thread類(lèi)或其子類(lèi)建立一個(gè)線程對(duì)象后,該線程對(duì)象就處于新建狀態(tài)。它保持這個(gè)狀態(tài)直到程序 start()這個(gè)線程。

  • 就緒狀態(tài):當(dāng)線程對(duì)象調(diào)用了start()方法之后,該線程就進(jìn)入就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊(duì)列中,要等待JVM里線程調(diào)度器的調(diào)度。

  • 阻塞(Blocked) : 對(duì)Running狀態(tài)的線程加同步鎖synchronized使其今如(Lock Blocked Pool) 同步鎖被釋放今如可運(yùn)行狀態(tài),

  • 等待(Waiting) :線程可以主動(dòng)調(diào)用obj.wait或者Thread.sleep或者 join 今入,Waiting是等待另一個(gè)線程完成某一個(gè)操作,如join等待另一個(gè)完成執(zhí)行.

  • waiting 和 Blocked狀態(tài), Blocked也是一種等待,等待的是monitor 但是waiting等待是notify()方法

每個(gè)對(duì)象都有的機(jī)制

  • monitor :Java中 每個(gè)對(duì)象都有一個(gè)監(jiān)視器,來(lái)監(jiān)視并發(fā)代碼的重入,在非多線程編碼時(shí)改監(jiān)視器不發(fā)揮作用,反之如果在``synchronized`范圍內(nèi),監(jiān)視器發(fā)揮作用.
  • wait/notify 必須存在于synchronized塊中, 并且這三個(gè)關(guān)鍵字針對(duì)的是同一個(gè)監(jiān)視器,意味著wait之后,其他線程可以進(jìn)入同步代碼塊
  • synchronized 單獨(dú)使用 在多線程環(huán)境下,synchronized塊中的方法獲取了lock實(shí)例的monitor,如果實(shí)例相同,那么只有一個(gè)線程能執(zhí)行該塊內(nèi)容

優(yōu)雅地停止線程

  • interrupt阻塞中斷和非阻塞中斷

  • interrupt()方法并不會(huì)立即執(zhí)行中斷操作,這個(gè)方法只會(huì)給線程設(shè)置一個(gè)為true的中斷標(biāo)志。設(shè)置之后,則根據(jù)線程當(dāng)前的狀態(tài)進(jìn)行不同的后續(xù)操作。

  • 線程的當(dāng)前狀態(tài)處于非阻塞狀態(tài),那么僅僅是線程的中斷標(biāo)志被修改為true而已(2)如果線程的當(dāng)前狀態(tài)處于阻塞狀態(tài),那么在將中斷標(biāo)志設(shè)置為true后,如果是 wait、sleep以及join 三個(gè)方法引起的阻塞,那么會(huì)將線程的中斷標(biāo)志重新設(shè)置為false,并拋出一個(gè)InterruptedException,這樣受阻線程就得以退出阻塞的狀態(tài)。

    public class TestThread1 {
    
        public static void main(String[] args) {
            MyRunnable1 myRunnable=new MyRunnable1();
            Thread thread=new Thread(myRunnable,"子線程");
            thread.start();
            try{
                //主線程休眠
                Thread.sleep(3000);
                //調(diào)用中斷,true
               thread.interrupt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class MyRunnable1 implements Runnable{
    
        @Override
        public void run() {
          int i=0;
          while(true){
              System.out.println(Thread.currentThread().getName()+"循環(huán)第"+ ++i+"次");
              try{
                //判斷線程的中斷情況
                    boolean interruptStatus=Thread.currentThread().isInterrupted();
                    System.out.println(Thread.currentThread().getName()+"循環(huán)第"+ ++i+"次"+interruptStatus);
                    Thread.sleep(1000);
                    //非阻塞中斷 只是設(shè)置標(biāo)記位true
                  //非阻塞中斷 只是設(shè)置標(biāo)記位true
                  if(interruptStatus){
                  //如果中斷為true則退出
                      break;
                  }
                 
              } catch (InterruptedException e) {
                  
                  // 一個(gè)線程在運(yùn)行狀態(tài)中,其中斷標(biāo)志被設(shè)置為true之后,一旦線程調(diào)用了
                  // wait、join、sleep方法中的一種,立馬拋出一個(gè)InterruptedException,且中斷標(biāo)志被程序會(huì)自動(dòng)清除,重新設(shè)置為false
                  System.out.println("阻塞中斷"+Thread.currentThread().isInterrupted());//顯示false并拋異常
                  return;//不想返回還可繼續(xù)寫(xiě)代碼
              }
          }
        }
     
    }
    
    子線程循環(huán)第1次false
    子線程循環(huán)第2次false
    子線程循環(huán)第3次false
    阻塞中斷false
    
  • Stop()停止

    • 由于不安全,已經(jīng)不使用了,因?yàn)?code>stop會(huì)解除由線程獲取的所有鎖定,當(dāng)在一個(gè)線程對(duì)象上調(diào)用stop()方法時(shí),這個(gè)線程對(duì)象所運(yùn)行的線程就會(huì)立即停止,假如一個(gè)線程正在執(zhí)行:synchronized void { x = 3; y = 4;}由于方法是同步的, 多個(gè)線程訪問(wèn)時(shí)總能保證x,y被同時(shí)賦值,而如果一個(gè)線程正在執(zhí)行到x = 3;時(shí),被調(diào)用了 stop()方法,即使在同步塊中,它也會(huì)馬上stop了,這樣就產(chǎn)生了不完整的殘廢數(shù)據(jù)。
  • 設(shè)置標(biāo)記位停止

public class TestThread2_1 {
    public static void main(String[] args) throws InterruptedException {
        MyThreads my = new MyThreads();
        new Thread(my, "線程A").start();
        Thread.sleep(10000);
        //設(shè)置標(biāo)記位
        //my.setFlag(false);

        //stop方法
        new Thread(my, "線程A").stop();
        System.out.println("代碼結(jié)束");

    }
}

class MyThreads implements Runnable {
    private boolean flag = true;

    @Override
    public void run() {
        int i = 1;
        while (flag) {
            try {
                Thread.sleep(1000);
                System.out.println("第" + i + "次執(zhí)行,線程名稱(chēng)為:" + Thread.currentThread().getName());
                i++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

多線程控制類(lèi)

ThreadLocal類(lèi)(關(guān)于這個(gè)類(lèi)后續(xù)會(huì)推出關(guān)于這個(gè)類(lèi)的專(zhuān)題)

  • 用處: 保存線程的獨(dú)立變量, 對(duì)一個(gè)下線程類(lèi)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程單獨(dú)提供了獨(dú)立的變量,所以每個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其他線程所對(duì)應(yīng)的副本,長(zhǎng)用于用戶登錄控制,如記錄session信息

  • 常用方法

    方法 作用
    initValue() 副本創(chuàng)建的方法
    get() 得到副本
    set() 設(shè)置副本
  • 實(shí)例

    • ThreadLocal能實(shí)現(xiàn)為不同的線程保存變量的原理是,它內(nèi)部有個(gè)Entry,保存<線程名,變量值>,不同的線程對(duì)應(yīng)著不同的value,就能加以區(qū)分了
    public class ThreadLocalDemo {
      //銀行對(duì)象,有錢(qián),有存款和取款兩個(gè)操作
      static class Bank{
          ThreadLocal<Float> threadLocal = new ThreadLocal<Float>() {
              protected Float initialValue() {
                  return 0.0f;
              }
              
          };
          //存款
          public Float get() {
              return threadLocal.get();
          }
          //取款
          public void set(Float money) {
              threadLocal.set(threadLocal.get()+money);
          }
      }
      
      
      //轉(zhuǎn)賬對(duì)象,從銀行中取錢(qián)然后轉(zhuǎn)賬,然后保存到賬戶
      static class Transfer implements Runnable{
          private Bank bank;
          public Transfer(Bank bank){
              this.bank = bank;
          }
          
          @Override
          public void run() {
              // TODO Auto-generated method stub
              for(int i = 0;i<10;i++) {
                  bank.set(10.0f);
                  System.out.println(Thread.currentThread().getName()+"賬戶余額"+bank.get());
              }
          }
      }
    }
    //main 中 模擬操作
          Bank bank = new Bank();
          Transfer transfer = new Transfer(bank);
          Thread t1 =new Thread(transfer,"客戶1");
          Thread t2 =new Thread(transfer,"客戶2");
          t1.start();
          t2.start();
    

Lock類(lèi)

  • Lock類(lèi)實(shí)際上是一個(gè)接口,我們?cè)趯?shí)例化的時(shí)候?qū)嶋H上是實(shí)例化實(shí)現(xiàn)了該接口的類(lèi)
Lock lock = new ReentrantLock();
  • 作用 :通過(guò)Lock對(duì)象lock,用lock.lock來(lái)加鎖 用lock.unlock來(lái)釋放鎖。在兩者中間放置需要同步處理的代碼。

    • 可重入鎖(ReentrantLock): 線程請(qǐng)求它已經(jīng)擁有的鎖時(shí)不會(huì)阻塞,可以進(jìn)入它已經(jīng)擁有的同步代碼塊,但是它擁有了多少次,就要解鎖多少次才能釋放鎖
    • 讀寫(xiě)鎖(ReadWriteLock) :可以同時(shí)讀,但是讀的時(shí)候不能寫(xiě),也不能同時(shí)寫(xiě),寫(xiě)的時(shí)候不能讀
    public class MyConditionService {
        private Lock lock = new ReentrantLock();//ReentrantLock是個(gè)重入鎖
        public void testMethod(){
            lock.lock();//加鎖
            for (int i = 0 ;i < 5;i++){
                System.out.println("ThreadName = " + Thread.currentThread().getName() + (" " + (i + 1)));
            }
            lock.unlock();//解鎖
        }
    }
    
  • 其他作用

    • 實(shí)現(xiàn)鎖的公平
    • 獲取當(dāng)前線程調(diào)用lock的次數(shù),也就是獲取當(dāng)前線程鎖定的個(gè)數(shù)
    • 獲取等待鎖的線程數(shù)
    • 查詢(xún)指定的線程是否等待獲取此鎖定
    • 查詢(xún)是否有線程等待獲取此鎖
    • 查詢(xún)當(dāng)前線程是否持有鎖定
    • 判斷一個(gè)鎖是不是被線程持有
    • 加鎖時(shí)如果中斷則不加鎖,進(jìn)入異常處理
    • 嘗試加鎖,如果該鎖未被其他線程持有的情況下成功
  • Condition類(lèi)

  • Condition是Java提供了來(lái)實(shí)現(xiàn)等待/通知的類(lèi),Condition類(lèi)還提供比wait/notify更豐富的功能,Condition對(duì)象是由lock對(duì)象所創(chuàng)建的。但是同一個(gè)鎖可以創(chuàng)建多個(gè)Condition的對(duì)象,即創(chuàng)建多個(gè)對(duì)象監(jiān)視器。這樣的好處就是可以指定喚醒線程。notify喚醒的線程是隨機(jī)喚醒一個(gè)。

  • condition對(duì)象通過(guò)lock.newCondition()來(lái)創(chuàng)建,用condition.await()來(lái)實(shí)現(xiàn)讓線程等待,使得線程進(jìn)入阻塞。用condition.signal()來(lái)實(shí)現(xiàn)喚醒線程。喚醒的線程是用同一個(gè)conditon對(duì)象調(diào)用await()方法而進(jìn)入阻塞。并且和wait/notify一樣,await()signal()也是在同步代碼區(qū)內(nèi)執(zhí)行。
    對(duì)于等待/通知機(jī)制,簡(jiǎn)化而言,就是等待一個(gè)條件,當(dāng)條件不滿足時(shí),就進(jìn)入等待,等條件滿足時(shí),就通知等待的線程開(kāi)始執(zhí)行。為了實(shí)現(xiàn)這種功能,需要進(jìn)行wait的代碼部分與需要進(jìn)行通知的代碼部分必須放在同一個(gè)對(duì)象監(jiān)視器里面。執(zhí)行才能實(shí)現(xiàn)多個(gè)阻塞的線程同步執(zhí)行代碼,等待與通知的線程也是同步進(jìn)行。對(duì)于wait/notify而言,對(duì)象監(jiān)視器與等待條件結(jié)合在一起 即synchronized(對(duì)象)利用該對(duì)象去調(diào)用wait以及notify。但是對(duì)于``Condition類(lèi),是對(duì)象監(jiān)視器與條件分開(kāi),Lock類(lèi)來(lái)實(shí)現(xiàn)對(duì)象監(jiān)視器,condition`對(duì)象來(lái)負(fù)責(zé)條件,去調(diào)用await以及signal。

  • 示例代碼:

    public class ConditionWaitNotifyService {
    
        private Lock lock = new ReentrantLock();
    
        public Condition condition = lock.newCondition();
    
    
        public void await(){
            try{
                lock.lock();
                System.out.println("await的時(shí)間為 " + System.currentTimeMillis());
                condition.await();
                System.out.println("await結(jié)束的時(shí)間" + System.currentTimeMillis());
    
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    
    
        public void signal(){
            try{
                lock.lock();
                System.out.println("sign的時(shí)間為" + System.currentTimeMillis());
                condition.signal();
            }finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
    
            ConditionWaitNotifyService service = new ConditionWaitNotifyService();
            new Thread(service::await).start();
            Thread.sleep(1000 * 3);
            service.signal();
            Thread.sleep(1000);
    
        }
    }
    
    • 其他作用:和wait類(lèi)提供了一個(gè)最長(zhǎng)等待時(shí)間,awaitUntil(Date deadline)在到達(dá)指定時(shí)間之后,線程會(huì)自動(dòng)喚醒。但是無(wú)論是await或者awaitUntil,當(dāng)線程中斷時(shí),進(jìn)行阻塞的線程會(huì)產(chǎn)生中斷異常。Java提供了一個(gè)awaitUninterruptibly的方法,使即使線程中斷時(shí),進(jìn)行阻塞的線程也不會(huì)產(chǎn)生中斷異常。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容