Java多線程和Executors框架

Java多線程

正確的編程方法:首先使代碼正確運(yùn)行,然后再提高代碼的速度。

常見創(chuàng)建線程的三種方式:

  • Thread

        //1. Thread方式
        MyThread thread=new MyThread();
        thread.start();
  • Runnable
        //2. Runnable
        Runnable runnable=new Runnable() {
            @Override
            public void run() {

                for(int i=0;i<100;++i){
                    System.out.println(Thread.currentThread().getName()+Thread.currentThread().getId()+"---"+i);

                }

            }
        };
        new Thread(runnable).start();
  • Callable
        //3. Callable
        Callable<Integer> callable=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {

                for(int i=0;i<100;++i){
                    System.out.println(Thread.currentThread().getName()+Thread.currentThread().getId()+"---"+i);
                }
                return 15;
            }
        };

        FutureTask<Integer> futureTask=new FutureTask(callable);
        new Thread(futureTask).start();
        try {
            Integer rt = futureTask.get();
            System.out.println(rt);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

區(qū)別:

  • 性質(zhì):
    • Runnable和Callable是接口
    • Thread 是類,Thread 實(shí)現(xiàn)了 Runnable
  • 調(diào)用:
    • Callable 調(diào)用call(), 有返回值
    • Runnable 調(diào)用run(),Runnable 無返回值,
  • 異常:
    • call 可以拋出異常到擁有者線程,也就是異常可知
    • Thread和Runnable的異常無法在其他線程catch
  • Callable 可以得到一個返回一次性任務(wù)FutureTask對象,表示一次性的異步計(jì)算

接口和類的區(qū)別:

  • 接口可以多implement,Java類只能單繼承
  • 接口的Class文件中superClass是空的,而類的superClass只要不是Object就一定非空
  • 接口是不涉及實(shí)現(xiàn)的,所以對象的attributes中沒有Code屬性,而Class有

線程安全

什么是線程安全?

當(dāng)多個線程訪問某個類時,這個類始終都能表現(xiàn)出正確的行為,那么就稱這個類是線程安全的。

----- Java 并發(fā)編程實(shí)戰(zhàn)

這里的正確性包括:

  • 不變性條件
    • 用于判斷狀態(tài)是有效的還是無效的,也就是狀態(tài)的狀態(tài)空間
如:long a ,a in [Long.MIN_VALUE,LONG.MAX_VALUE],
那么當(dāng)a>Long.MAX_VALUE時,就不滿足不變性條件
  • 后驗(yàn)條件
    • 判斷狀態(tài)遷移是否有效
    如:兩個屬性定義滿足:a+b=5;
    那么{a=2,b=3}就是有效狀態(tài),{a=3,b=3}就是無效狀態(tài)

這里其實(shí)Java并發(fā)編程關(guān)注的就是共享對象的狀態(tài)轉(zhuǎn)換。

線程安全類封裝了必要的同步機(jī)制,因此客戶端無須進(jìn)一步采取同步措施

編寫線程安全的代碼,其核心在于要對狀態(tài)操作進(jìn)行管理,特別是對共享的可變的狀態(tài)的訪問。

  • 無狀態(tài)的對象一定是線程安全的。
  • 線程間共享對象,實(shí)際上是對象所有權(quán)的傳遞,所有權(quán)唯一

封裝線程安全類的一些手段:

  • 盡量使得屬性是final的,因?yàn)闊o狀態(tài)的對象一定是線程安全的。
  • 盡量不對外發(fā)布狀態(tài),因?yàn)榘l(fā)布后所有權(quán)的轉(zhuǎn)移是不可控的。
//反例:
    private List<Integer> myList=new ArrayList<>();

    public List getList(){
        
        //對外發(fā)布對象,外部可以得到內(nèi)部對象的引用,之后對這個list的操作不受本類控制,myList的所有權(quán)不可控
        return this.myList;
    }
//正例
    private List<Integer> myList=new ArrayList<>();
    
    public List getList(){

        //拷貝一份
        ArrayList<Integer> list = new ArrayList<>();
        for(Integer i:myList){

            list.add(new Integer(i));

        }
        //拒絕修改
        return Collections.unmodifiableList(myList);
    }
  • 線程封閉。保證只有一個線程在訪問當(dāng)前變量。最具體的表現(xiàn)就是方法中不對外發(fā)布的局部變量
  • 加鎖。其實(shí)是為了保證對象所有權(quán)在一個時間點(diǎn)只有一個線程能得到。
    • 獨(dú)立的狀態(tài),單獨(dú)加鎖就好了
    • 組合的狀態(tài),狀態(tài)的轉(zhuǎn)換必須滿足后驗(yàn)條件,必須用同一把鎖來鎖住。
/**
 * 不變性條件: blood,lostBlood in [0,100]
 * 后驗(yàn)條件:blood+lostBlood=100
 */
public class Person {
    
    private int blood=100;
    private int lostBlood=0;

    public void injured(int k){
        //必須是同一把鎖
        synchronized(this){
            blood-=k;
            lostBlood+=k;

        }
    }
}
  • 記得把類和其方法的線程安全性寫入文檔

Servlet

Servlet在非分布式環(huán)境下默認(rèn)是單例的,可以配置為多實(shí)例。所以要注意它的線程安全問題。

  • 創(chuàng)建的時機(jī):

    1. 第一次訪問Servlet的時候創(chuàng)建
    2. web服務(wù)器啟動時創(chuàng)建

配置:

 <load-on-startup>1</load-on-startup>
  • 創(chuàng)建:tomcat等服務(wù)器

Executors 框架

  • Executor接口
  • ExecutorService 接口擴(kuò)展Executor接口,增加了生命周期方法

實(shí)現(xiàn)一個支持并發(fā)的小型服務(wù)器:

public class TaskServer {
    private static final int NTHREADS=100;
    private static final int PORT=80;

    //固定大小的線程池
    private static final Executor exec= Executors.newFixedThreadPool(NTHREADS);


    public static void main(String[] args) throws IOException {

        ServerSocket serverSocket = new ServerSocket(PORT);
        
        while(true){

            //阻塞
            final Socket s=serverSocket.accept();

            Runnable task=new Runnable() {
                @Override
                public void run() {
                    handleRequest(s);
                }
            };

            exec.execute(task);
        }
    }
    private static void handleRequest(Socket s) {
        //do something
    }
}
  • ExecutorService 和Callable:支持返回值和捕捉異常
       
    Callable task= new Callable() {
            
    @Override
    public Object call() throws Exception {
        
        //do something and return

        int rt=1;
        return rt;
        }
    };
    
    //提交上去的任務(wù)得到一個future,也即一次性任務(wù)
    Future future = exec.submit(task);


    //阻塞方法,只有任務(wù)執(zhí)行完畢才會返回值,且一次完成,之后返回都是一致的
    int rt=funture.get();

  • ComCompletionService

ComCompletionService包裝Executor對象,并且內(nèi)置一個已完成隊(duì)列。還有調(diào)度邏輯。

會將已完成的任務(wù)放到BlockQueue中,要用的時候直接take


image.png
package cc.siriuscloud;

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

public class Renderer {
    private static final int NTHREADS=100;

    private final ExecutorService executor;


    public Renderer() {
        this.executor = Executors.newFixedThreadPool(NTHREADS);
    }


    @Test
    public void renderPage() {

        List<String> infos = getList();

        //包裝成completionService
        CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(executor);

        for (final String item : infos) {
            //提交任務(wù)
            completionService.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {

                    int rt = Integer.parseInt(item);

                    synchronized (this){

                        //模仿耗時操作
                        Random random = new Random();
                        wait(Math.abs(random.nextInt(1000)));
                    }
                    return rt;
                }
            });

        }


        // 執(zhí)行完的任務(wù)結(jié)果放在已完成任務(wù)隊(duì)列中,
        try {

            for (int t = 0, n = infos.size(); t < n; ++t) {
                //取出一個已完成的任務(wù),沒有任何結(jié)果時阻塞
                Future<Integer> f = completionService.take();
                Integer rt = f.get();
                System.out.println(" 結(jié)果是:rt=" + rt);

            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }

    private List<String> getList() {
        ArrayList<String> list = new ArrayList<>();
        list.add("1000");
        list.add("110");
        list.add("11");
        list.add("1");
        return list;

    }
}


參考:

ConcurrentHashMap學(xué)習(xí)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對應(yīng)一個進(jìn)程,當(dāng)一個程序進(jìn)入內(nèi)存運(yùn)行時,即變成一個進(jìn)程.進(jìn)程是處于運(yùn)行過程中...
    小徐andorid閱讀 2,993評論 3 53
  • 下面是我自己收集整理的Java線程相關(guān)的面試題,可以用它來好好準(zhǔn)備面試。 參考文檔:-《Java核心技術(shù) 卷一》-...
    阿呆變Geek閱讀 15,142評論 14 507
  • 昨天去看了《芳華》這部影片,給了我很大的觸動。 走出影院的時候,我聽到了兩個穿校服女生的對話。 一個說:“這部電影...
    寶貝帕克kr閱讀 425評論 2 0
  • 面對生活中的各色事物,情緒總是時好時壞。被鼓勵時積極,被打擊時消極。情緒時起時落,不由己控制。積極的能力也只會間歇...
    oneandone閱讀 367評論 0 3
  • R 《非暴力溝通》 I 非暴力溝通適用于任何想要達(dá)到更好理解的溝通,四要素法則是為了讓別人更好的理解你的想法,理解...
    水晶雪梨閱讀 235評論 4 0

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