java 多線程和並行程序設計


多線程使得程序中的多個任務可以同時執行

在一個程序中允許同時運行多個任務。在許多程序設計語言中,多線程都是通過調用依賴系統的過程或函數來實現的

 

 

 

 為什么需要多線程?多個線程如何在單處理器系統中同時運行?

多線程可以使您的程序更具響應性和交互性,並提高性能。在許多情況下需要多線程,例如動畫和客戶端/服務器計算。因為大多數時候CPU處於空閑狀態 - 例如,CPU在用戶輸入數據時什么都不做 - 多個線程在單處理器系統中共享CPU時間是切實可行的。

 

什么是可運行的對象?什么是線程?
Runnable的一個實例是一個可運行的對象。線程是用於執行可運行任務的可運行對象的包裝對象。

 

1、創建任務和線程

一個任務類必須實現Runnable接口。任務必須從線程運行。

一旦定義了一個TaskClass,就可以用它的構造方法創建一個任務。

例子:

TaskClass task = new TaskClass();

任務必須在線程中執行。使用下面的語句創建任務的線程

Thread thread = new Thread(task);

然后調用start()方法告訴Java虛擬機該線程准備運行

thread.start()

 

 

例子:

public class TaskThreadDemo {    
    
    public static void main(String [] args) {
        
        Runnable printA = new PrintChar('a', 100);
        Runnable printB = new PrintChar('b', 100);
        Runnable printNum = new PrintNum(100);
        
        
        Thread thread1 = new Thread(printA);
        Thread thread2 = new Thread(printB);
        Thread thread3 = new Thread(printNum);
        
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class PrintChar implements Runnable{
    private char charToPrint;
    private int times;
    public PrintChar(char c,int t) {
        charToPrint = c;
        times = t;
    }

    @Override
    public void run() {
        for(int i=0; i<times; i++) {
            System.out.print(charToPrint);
        }
    }
}

class PrintNum implements Runnable{
    private int lastNumber;
    public PrintNum(int n) {
        lastNumber = n;
    }
    @Override
    public void run() {
        for(int i=1; i<=lastNumber; i++) {
            System.out.print(" " + i);
        }
    }
}

 

 

 2、Thread類

 

 

 

 

 

 

 

 

 

3、線程池

之前運用實現Runnable接口來定義一個任務類,以及如何創建一個線程來運行一個任務

該方法對大量的任務而言是不夠高效的,為每個任務開始一個新線程可能會限制吞吐量並且造成性能降低。

線程池是管理並發執行任務個數的理想方法。

Java提供Executor接口來執行線程池中的任務,提供ExecutorService接口來管理和控制任務。

Executorservice是Executor的子接口

 

 

為了創建一個Executor對象,可以使用Executor類中的靜態方法

 

 

 例子:

public class TaskThreadDemo {    
    
    public static void main(String [] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);
//        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(new PrintChar('a', 100));
        executor.execute(new PrintChar('b', 100));
        executor.execute(new PrintNum(100));
        
        executor.shutdown();
        
    }
    
}

 

4、線程同步

例子:

public class AccountWithoutSync {

    private static Account account = new Account();

    public static void main(String[] args) {
        
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 100; i++) {
            executorService.execute(new AddAPennyTask());
        }
        
        executorService.shutdown();
        
        //等待 全部任務 完成
        while(!executorService.isTerminated()) {
        
        }
        
        System.out.println(account.getBalance());
    
    }
    
    private static class AddAPennyTask implements Runnable{
        @Override
        public void run() {
            // TODO Auto-generated method stub
            account.deposit(1);
        }
    }
    
    
    public static class Account {
        private int balance = 0;
            
        public int getBalance() {
            return balance;
        }

        public void deposit(int amount) {
            int newBalance = balance + amount;
            try {
                Thread.sleep(5);
            }catch (InterruptedException e) {
                // TODO: handle exception
            }
            balance = balance + newBalance;
        }
    }

}

這個例子中出錯了

原因是:

因為線程不安全,所以數據遭到破壞

 

synchronized關鍵字

為避免競爭狀態,應該防止多個線程同時進入程序的某一特定部分,程序中的這部分稱為臨界區。

 

使用關鍵字synchronized來同步方法,以便一次只有一個線程可以訪問這個方法

例子:

調用一個對象上的同步實例方法,需要給該對象加鎖。而調用一個類上的同步靜態方法,需要給該類加鎖。

 

 同步語句

當執行方法中某一個代碼塊時,同步語句不僅可用與this對象加鎖,而且可用於對任何對象加鎖。這個代碼塊稱為同步塊。同步語句的一般形式如下:

 

 

利用加鎖同步

之前的用的同步實例方法

實際上 在執行之前都隱式地需要一個加在實例上的鎖

 

 

 

例子:

部分代碼:

public static class Account {
        private int balance = 0;
        
        private Lock lock = new ReentrantLock();  //創建一個鎖
        
        public int getBalance() {
            return balance;
        }

        public void deposit(int amount) {
            lock.lock();//獲取該鎖
            try {
                int newBalance = balance + amount;
                
                Thread.sleep(5);
                balance = newBalance;
            }catch (InterruptedException e) {
                // TODO: handle exception
            }finally {
                lock.unlock();//釋放該鎖
            }
            
        }
    }

 

 

線程間協作

例子:

public class ThreadCooperation {
    private static Account account = new Account();
    
    public static void main(String [] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(new DepositTask());
        executorService.execute(new withdrawTask());
        executorService.shutdown();
        
        System.out.println("Thread 1 \t Thread 2 \t Balance");
        System.out.println();
    }

    public static class DepositTask implements Runnable{
        @Override
        public void run() {
            try {
                while(true) {
                    account.deposit((int)(Math.random() * 10) + 1);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    public static class withdrawTask implements Runnable{
        @Override
        public void run() {
            while(true) {
                account.withdraw((int)(Math.random() * 10) + 1);
            }
        }
    }
    
    
    
    public static class Account {
         
        private static Lock lock = new ReentrantLock();
        
        private static Condition newDeposit = lock.newCondition();  //線程間的協作
        
        private int balance = 0;
            
        public int getBalance() {
            return balance;
        }
        
        
        public void withdraw(int accountNumber) {
            lock.lock();
            try {
                while(balance < accountNumber) {
                    System.out.println("\t\tWait for a deposit, wait to withdraw :" + accountNumber);
                    newDeposit.await();
                }    
                balance -= accountNumber;
                System.out.println("\t\tWithdraw: " + accountNumber + "\t\t" + "balance:  " + getBalance());        
            }catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        
        public void deposit(int accountNumber) {
            lock.lock();
            try {
                balance += accountNumber;
                System.out.println("Deposit " + accountNumber + "\t\t\t\t balance: " + getBalance());
                newDeposit.signalAll();
            }
            finally {
                lock.unlock();
            }
            
        }
    }
    
}

 

 

 

 

 

如何在鎖上創建條件?什么是await(),signal()和signalAll()方法?
可以使用lock.newCondition()創建鎖上的條件。await()方法使當前線程等待,直到發出條件信號。signal()方法喚醒一個等待線程,signalAll()方法喚醒所有等待線程。

 

 

消費者/生產者

 

 

 

public class ComsumerProducer {
    
    private static Buffer buffer = new Buffer();
    
    
    public static void main(String [] args) {
        
        ExecutorService excurtor = Executors.newFixedThreadPool(2);
        excurtor.execute(new ProducerTask());
        excurtor.execute(new ConsumerTask());
        
        excurtor.shutdown();
        
    }
    
    private static class ProducerTask implements Runnable{

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                int i = 1;
                while(true) {
                    System.out.println("Producer writes " + i);
                    buffer.write(i++);
                    Thread.sleep((int)(Math.random() * 1000));
                }
            }catch (InterruptedException e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
    }
    
    private static class ConsumerTask implements Runnable{

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                while(true) {
                    System.out.println("\t\tConsumer reads " + buffer.read());
                    Thread.sleep((int)(Math.random() * 1000));
                }
            }catch (InterruptedException e) {
                // TODO: handle exception
                e.printStackTrace();
            }
            
        }
        
    }
    
    
    
    private static class Buffer{
        private static final int CAPACITY = 3;
        
        private LinkedList<Integer> queue = new LinkedList<>();
        
        private static Lock lock = new ReentrantLock();
        
        private static Condition notFull = lock.newCondition();
        
        private static Condition notEmpty = lock.newCondition();
        
        
        public void write(int value) {
            lock.lock();
            
            try{    
                while(queue.size() == CAPACITY) {
                    System.out.println("Wait for notNull condition");
                    notFull.await();
                }
                queue.offer(value);
                notEmpty.signal();  
            } catch (InterruptedException e) {
                    
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            
        }
            
        
        public int read() {
            lock.lock();
            int value = 0;
            try {
                while(queue.isEmpty()) {
                    System.out.println("\t\tWait for notEmpty condition");
                    notEmpty.await();
                }
                value = queue.pop();
                notFull.signal();
            } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                e.printStackTrace();
            }finally {
                lock.unlock();
                return value;
            }
        
        }
    }
    
}

 

阻塞隊列

 

阻塞隊列在試圖向一個滿隊列添加元素或者從空隊列中刪除元素時會導致線程阻塞。BlockingQueue接口繼承了Queue,並且提供同步的put和take方法向隊列尾部添加元素,以及從隊列頭部刪除元素

 

java支持三個具體的阻塞隊列ArrayBlockingQueue、LinkedBlockingQueue和PriorityBlockingQueue

 

 

例子:

用阻塞隊列做的消費者/生產者

public class ConsumerProducerUsingBlockingQueue {
    
    private static ArrayBlockingQueue<Integer> buffer = new ArrayBlockingQueue<Integer>(2);
    
    public static void main(String [] args) {
        ExecutorService excutor = Executors.newFixedThreadPool(2);
        excutor.execute(new ProducerTask());
        excutor.execute(new ConsumerTask());
        excutor.shutdown();
        
    }
    
    
    private static class ProducerTask implements Runnable{

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                int i = 1;
                while(true) {
                    System.out.println("Producer writes " + i);
                    buffer.put(i++);
                    Thread.sleep((int)(Math.random() * 1000));
                }
            }catch (InterruptedException e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
    }
    
    private static class ConsumerTask implements Runnable{
        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                while(true) {
                    System.out.println("\t\tConsumer reads " + buffer.take());
                    Thread.sleep((int)(Math.random() * 1000));
                }
            }catch (InterruptedException e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
        
    }
}

 

 信號量

 

 

例子:

 

 

 鎖和信號量有什么相似之處和不同之處?
鎖和信號量都可用於限制對共享資源的訪問。在資源上使用鎖可確保只有一個線程可以訪問它。在資源上使用信號量允許一個或多個指定數量的線程訪問資源。

 

 如何創建允許三個並發線程的信號量?你如何獲得信號量?你如何發布信號量?
使用新的信號量(numberOfPermits)來創建信號量。調用aquire()獲取信號量並調用release()來釋放信號量。
 
 
避免死鎖

 

 

 

 什么是死鎖?你怎么能避免死鎖?
在兩個或多個線程獲取多個對象上的鎖並且每個對象都鎖定一個對象並且正在等待另一個對象上的鎖定的情況下發生死鎖。資源排序技術可用於避免死鎖。

 

 

同步合集

 

 並行編程

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM