1. 程式人生 > 實用技巧 >java ---執行緒池和多執行緒

java ---執行緒池和多執行緒

執行緒池

執行緒池,其實就是一個容納多個執行緒的容器,其中的執行緒可以反覆使用,省去了頻繁建立執行緒物件的操作,無需反覆建立執行緒而消耗過多資源。

使用執行緒池方式--Runnable介面

使用執行緒池中執行緒物件的步驟:

1. 建立執行緒池物件

2. 建立Runnable介面子類物件

3. 提交Runnable介面子類物件

4. 關閉執行緒池

//    程式碼演示:
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //建立執行緒池物件
        ExecutorService service = Executors.newFixedThreadPool(2);//
包含2個執行緒物件 //建立Runnable例項物件 MyRunnable r = new MyRunnable(); //自己建立執行緒物件的方式 //Thread t = new Thread(r); //t.start(); ---> 呼叫MyRunnable中的run() //從執行緒池中獲取執行緒物件,然後呼叫MyRunnable中的run() service.submit(r); //再獲取個執行緒物件,呼叫MyRunnable中的run()
service.submit(r); service.submit(r); //注意:submit方法呼叫結束後,程式並不終止,是因為執行緒池控制了執行緒的關閉。將使用完的執行緒又歸還到了執行緒池中 //關閉執行緒池 //service.shutdown(); } } // Runnable介面實現類 public class MyRunnable implements Runnable { @Override public void run() { System.out.println("我要一個教練");
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("教練來了: " +Thread.currentThread().getName()); System.out.println("教我游泳,交完後,教練回到了游泳池"); } }

使用執行緒池方式—Callable介面

使用執行緒池中執行緒物件的步驟:

1. 建立執行緒池物件

2. 建立Callable介面子類物件

3. 提交Callable介面子類物件

4. 關閉執行緒池

//程式碼演示:
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //建立執行緒池物件
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2個執行緒物件
        //建立Callable物件
        MyCallable c = new MyCallable();
        
        //從執行緒池中獲取執行緒物件,然後呼叫MyRunnable中的run()
        service.submit(c);
        
        //再獲取個教練
        service.submit(c);
        service.submit(c);
//注意:submit方法呼叫結束後,程式並不終止,是因為執行緒池控制了執行緒的關閉。將使用完的執行緒又歸還到了執行緒池中

//關閉執行緒池
        //service.shutdown();
    }
}

//    Callable介面實現類,call方法可丟擲異常、返回執行緒任務執行完畢後的結果
public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println("我要一個教練:call");
        Thread.sleep(2000);
        System.out.println("教練來了: " +Thread.currentThread().getName());
        System.out.println("教我游泳,交完後,教練回到了游泳池");
        return null;
    }
}

執行緒池練習:返回兩個數相加的結果

要求:通過執行緒池中的執行緒物件,使用Callable介面完成兩個數求和操作

Future介面:用來記錄執行緒任務執行完畢後產生的結果。執行緒池建立與使用

get() 獲取Future物件中封裝的資料結果

//程式碼演示:
public class ThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //建立執行緒池物件
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        
        //建立一個Callable介面子類物件
        //MyCallable c = new MyCallable();
        MyCallable c = new MyCallable(100, 200);
        MyCallable c2 = new MyCallable(10, 20);
        
        //獲取執行緒池中的執行緒,呼叫Callable介面子類物件中的call()方法, 完成求和操作
        //<Integer> Future<Integer> submit(Callable<Integer> task)
        // Future 結果物件
        Future<Integer> result = threadPool.submit(c);
        //此 Future 的 get 方法所返回的結果型別
        Integer sum = result.get();
        System.out.println("sum=" + sum);
        
        //再演示
        result = threadPool.submit(c2);
        sum = result.get();
        System.out.println("sum=" + sum);
        //關閉執行緒池(可以不關閉)
        
    }
}
//    Callable介面實現類
public class MyCallable implements Callable<Integer> {
    //成員變數
    int x = 5;
    int y = 3;
    //構造方法
    public MyCallable(){
    }
    public MyCallable(int x, int y){
        this.x = x;
        this.y = y;
    }

    @Override
    public Integer call() throws Exception {
        return x+y;
    }
}

多執行緒

執行緒安全

我們通過一個案例,演示執行緒的安全問題:

電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “功夫熊貓3”,本次電影的座位共100個(本場電影只能賣100張票)。

我們來模擬電影院的售票視窗,實現多個視窗同時賣 “功夫熊貓3”這場電影票(多個視窗一起賣這100張票)

需要視窗,採用執行緒物件來模擬;需要票,Runnable介面子類來模擬

//    測試類
public class ThreadDemo {
    public static void main(String[] args) {
        //建立票物件
        Ticket ticket = new Ticket();
        
        //建立3個視窗
        Thread t1  = new Thread(ticket, "視窗1");
        Thread t2  = new Thread(ticket, "視窗2");
        Thread t3  = new Thread(ticket, "視窗3");
        
        t1.start();
        t2.start();
        t3.start();
    }
}
//    模擬票
public class Ticket implements Runnable {
    //共100票
    int ticket = 100;

    @Override
    public void run() {
        //模擬賣票
        while(true){
            if (ticket > 0) {
                //模擬選坐的操作
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--);
            }
        }
    }

同步程式碼塊

同步程式碼塊: 在程式碼塊宣告上 加上synchronized

synchronized (鎖物件) {
    可能會產生執行緒安全問題的程式碼
}
//同步程式碼塊中的鎖物件可以是任意的物件;但多個執行緒時,要使用同一個鎖物件才能夠保證執行緒安全。

使用同步程式碼塊,對電影院賣票案例中Ticket類進行如下程式碼修改:

public class Ticket implements Runnable {
    //共100票
    int ticket = 100;
    //定義鎖物件
    Object lock = new Object();
    @Override
    public void run() {
        //模擬賣票
        while(true){
            //同步程式碼塊
            synchronized (lock){
                if (ticket > 0) {
                    //模擬電影選坐的操作
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--);
                }
            }
        }
    }
}

同步方法

同步方法:在方法宣告上加上synchronized

public synchronized void method(){
       可能會產生執行緒安全問題的程式碼
}
//同步方法中的鎖物件是 this

使用同步方法,對電影院賣票案例中Ticket類進行如下程式碼修改:

public class Ticket implements Runnable {
    //共100票
    int ticket = 100;
    //定義鎖物件
    Object lock = new Object();
    @Override
    public void run() {
        //模擬賣票
        while(true){
            //同步方法
            method();
        }
    }

//同步方法,鎖物件this
    public synchronized void method(){
        if (ticket > 0) {
            //模擬選坐的操作
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--);
        }
    }
}

Lock介面

我們使用Lock介面,以及其中的lock()方法和unlock()方法替代同步,對電影院賣票案例中Ticket類進行如下程式碼修改:

public class Ticket implements Runnable {
    //共100票
    int ticket = 100;
    
    //建立Lock鎖物件
    Lock ck = new ReentrantLock();
    
    @Override
    public void run() {
        //模擬賣票
        while(true){
            //synchronized (lock){
            ck.lock();
                if (ticket > 0) {
                    //模擬選坐的操作
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--);
                }
            ck.unlock();
            //}
        }
    }
}