併發編程(3)線程同步的方式及死鎖

概述

線程自己因爲建立和切換的開銷,採用多線程不會提升程序的執行速度,反而會下降速度,可是對於頻繁IO操做的程序,多線程能夠有效的併發。 對於包含不一樣任務的程序,能夠考慮每一個任務使用一個線程。這樣的程序在設計上相對於單線程作全部事的程序來講,更爲清晰明瞭,若是是單純的計算操做,多線程並無單線程的計算效率高,可是對於一些刻意分散使用計算機系統資源的操做,則適合使用多線程。 在實際的開發中對於性能優化的問題須要考慮到具體的場景來考慮是否使用多線程技術。通常來講一個程序是運行在一個進程中的,進程是具備必定獨立功能的程序、它是計算機系統進行資源分配和調度的一個獨立單位。而線程是進程的一個實體,是CPU調度和分派的基本單位,他是比進程更小的能獨立運行的基本單位。html

在JMM中,線程能夠把變量保存在本地內存(好比機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能形成一個線程在主存中修改了一個變量的值,而另外一個線程還在繼續使用它在寄存器中的變量值的拷貝,形成數據的不一致,這樣就會致使線程不安全,下面介紹幾種Java中常見的線程同步的方式。java

正文

關於線程不安全的緣由是由於JMM定義了主內存跟工做內存,形成多個線程同事訪問同一個資源時致使的不一致問題,那麼要想解決這個問題其實也很簡單,也是從JMM入手,主要有如下3種方式, 程序員

synchronization

  • 保證每一個線程訪問資源的時候獲取到的都是資源的最新值(可見性)
  • 當有線程 操做該資源的時候鎖定該資源,禁止別的線程訪問(鎖)
  • 線程本地私有化一份本地變量,線程每次讀寫本身的變量(ThreadLocal)

synchronized

採用synchronized修飾符實現的同步機制叫作互斥鎖機制,它所得到的鎖叫作互斥鎖。每一個對象都有一個鎖標記,當線程擁有這個鎖標記時才能訪問這個資源,沒有鎖標記便進入鎖池,互斥鎖分兩種一種是類鎖,一種是對象鎖。 類鎖:用於類的靜態方法或者一個類的class,一個對象只有一個 對象鎖:用於實例化的對象的普通方法,能夠有多個數組

下面仍是用程序員改bug這個例子來示範一下synchronized的使用方式緩存

Bug類安全

public class Bug {

    private static Integer bugNumber = 0;

    public static int getBugNumber() {
        return bugNumber;
    }

    //普通同步方法
    public synchronized void addNormal() {
        bugNumber++;
        System.out.println("normalSynchronized--->" + getBugNumber());
    }

    //靜態同步方法
    public static synchronized void addStatic() {
        bugNumber++;
        System.out.println("staticSynchronized--->" + getBugNumber());

    }

    //同步代碼塊
    public synchronized void addBlock() {
        synchronized (bugNumber) {
            this.bugNumber = ++bugNumber;
            System.out.println("blockSynchronized--->" + getBugNumber());

        }
    }
}

複製代碼

Runnable性能優化

public class BugRunnable implements Runnable {
    private Bug mBug=new Bug();
    @Override
    public void run() {
        mBug.addNormal();//普通方法同步
//        mBug.addBlock();//同步代碼塊
//        Bug.addStatic();//靜態方法同步
    }
}
複製代碼

測試代碼bash

public static void main(String[] args) {
        BugRunnable bugRunnable = new BugRunnable();
        for (int i = 0; i < 6; i++) {
            new Thread(bugRunnable).start();
        }
    }
複製代碼
同步代碼塊
//同步代碼塊
    public synchronized void addBlock() {
        synchronized (bugNumber) {
            this.bugNumber = ++bugNumber;
            System.out.println("blockSynchronized--->" + getBugNumber());

        }
    }
複製代碼

測試結果多線程

blockSynchronized--->1
blockSynchronized--->2
blockSynchronized--->3
blockSynchronized--->4
blockSynchronized--->5
blockSynchronized--->6
複製代碼
普通方法同步
//普通同步方法
    public synchronized void addNormal() {
        bugNumber++;
        System.out.println("normalSynchronized--->" + getBugNumber());
    }
複製代碼

測試結果併發

normalSynchronized--->1
normalSynchronized--->2
normalSynchronized--->3
normalSynchronized--->4
normalSynchronized--->5
normalSynchronized--->6
複製代碼
靜態方法同步
//靜態同步方法
    public static synchronized void addStatic() {
        bugNumber++;
        System.out.println("staticSynchronized--->" + getBugNumber());

    }
複製代碼

測試結果

staticSynchronized--->1
staticSynchronized--->2
staticSynchronized--->3
staticSynchronized--->4
staticSynchronized--->5
staticSynchronized--->6
複製代碼
對比分析
  • 類的每一個實例都有本身的對象鎖。當一個線程訪問實例對象中的synchronized同步代碼塊或同步方法時,該線程便獲取了該實例的對象級別鎖,其餘線程這時若是要訪問同一個實例(由於對象能夠有多個實例)同步代碼塊或同步方法,必須等待當前線程釋放掉對象鎖才能夠,若是是訪問類的另一個實例,則不須要。
  • 若是一個對象有多個同步方法或者代碼塊,沒有獲取到對象鎖的線程將會被阻塞在全部同步方法以外,可是能夠訪問非同步方法
  • 對於靜態方法,實際上能夠把它轉化成同步代碼塊,就拿上面的靜態方法,實際上至關於:
//靜態同步方法
    public static synchronized void addStatic() {
        bugNumber++;
        System.out.println("staticSynchronized--->" + getBugNumber());

    }
    //用同步代碼塊
    public static void changeStatic() {
        synchronized (Bug.class) {
            ++bugNumber;
            System.out.println("blockSynchronized--->" + getBugNumber());

        }
    }
複製代碼

下面具體來總結一下三者的區別

  • 同步代碼塊:同步代碼塊的範圍較小,只是鎖定了某個對象,因此性能較高
  • 普通同步方法:給整個方法上鎖,性能較低
  • 靜態同步方法:至關於整個類的同步代碼塊,性能較低

ReentrantLock

除了synchronized這個關鍵字外,咱們還能經過concurrent包下的Lock接口來實現這種效果,ReentrantLock是lock的一個實現類,能夠在任何你想要的地方進行加鎖,比synchronized關鍵字更加靈活,下面看一下使用方式 使用方式

//ReentrantLock同步
    public void addReentrantLock() {
        mReentrantLock.lock();//上鎖
        bugNumber++;
        System.out.println("normalSynchronized--->" + getBugNumber());
        mReentrantLock.unlock();//解鎖
    }
複製代碼

運行測試

ReentrantLock--->1
ReentrantLock--->2
ReentrantLock--->3
ReentrantLock--->4
ReentrantLock--->5
ReentrantLock--->6
複製代碼

咱們發現也是能夠達到同步的目的,看一下ReentrantLock的繼承關係

ReentrantLock

ReentrantLock實現了lock接口,而lock接口只是定義了一些方法,因此至關於說ReentrantLock本身實現了一套加鎖機制,下面簡單分析一下ReentrantLock的同步機制,在分析前,須要知道幾個概念:

  • CLH:AbstractQueuedSynchronizer中「等待鎖」的線程隊列。在線程併發的過程當中,沒有得到鎖的線程都會進入一個隊列,CLH就是管理這些等待鎖的隊列。
  • CAS:比較並交換函數,它是原子操做函數,也就是說全部經過CAS操做的數據都是以原子方式進行的。

成員變量

private static final long serialVersionUID = 7373984872572414699L;
  /** Synchronizer providing all implementation mechanics */
private final Sync sync;//同步器
複製代碼

成員變量除了序列化ID以外,只有一個Sync,那就看一看具體是什麼

Sync
Sync有兩個實現類,一個是FairSync,一個是NonfairSync,從名字能夠大體推斷出一個是公平鎖,一個是非公平鎖,

FairSync(公平鎖) lock方法:

final void lock() {
            acquire(1);
        }

複製代碼

ReentrantLock是獨佔鎖,1表示的是鎖的狀態state。對於獨佔鎖而言,若是所處於可獲取狀態,其狀態爲0,當鎖初次被線程獲取時狀態變成1,acquire最終調用的是tryAcquire方法

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
           // 當c==0表示鎖沒有被任何線程佔用
        (hasQueuedPredecessors),
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
            //鎖已經被線程佔用
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
複製代碼

tryAcquire主要是去嘗試獲取鎖,獲取成功則設置鎖狀態並返回true,不然返回false

NonfairSync(非公平鎖) 非公平鎖NonfairSync的lock()與公平鎖的lock()在獲取鎖的流程上是一直的,可是因爲它是非公平的,因此獲取鎖機制仍是有點不一樣。經過前面咱們瞭解到公平鎖在獲取鎖時採用的是公平策略(CLH隊列),而非公平鎖則採用非公平策略它無視等待隊列,直接嘗試獲取。

final void lock() {
            if (compareAndSetState(0, 1))
           setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
複製代碼

lock()經過compareAndSetState嘗試設置鎖的狀態,若成功直接將鎖的擁有者設置爲當前線程(簡單粗暴),不然調用acquire()嘗試獲取鎖,對比一下,公平鎖跟非公平鎖的區別在於tryAcquire中

//NonfairSync 
  if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
 //FairSync 
 if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
複製代碼

公平鎖中要經過hasQueuedPredecessors()來判斷該線程是否位於CLH隊列頭部,是則獲取鎖;而非公平鎖則無論你在哪一個位置都直接獲取鎖。

unlock

public void unlock() {
        sync.release(1);//釋放鎖
    }

  public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
複製代碼

對比分析

等待可中斷
  • synchronized:線程A跟線程B同時競爭同一把鎖,若是線程A得到鎖以後不釋放,那麼線程B會一直等待下去,並不會釋放。

  • ReentrantLock:能夠在線程等待了很長時間以後進行中斷,不須要一直等待。

鎖的公平性

公平鎖:是指多個線程在等待同一個鎖時,必須按照申請的時間順序來依次得到鎖;非公平鎖:在鎖被釋放時,任何一個等待鎖的線程都有機會得到鎖;

  • synchronized:是非公平鎖
  • ReentrantLock:能夠是非公平鎖也能夠是公平鎖
綁定條件
  • synchronized中默認隱含條件。
  • ReentrantLock能夠綁定多個條件

可見性

volatile

內存語義

因爲多個線程方法同一個變量,致使了線程安全問題,主要緣由是由於線程的工做副本的變量跟主內存的不一致,若是可以解決這個問題就能夠保證線程同步,而Java提供了volatile關鍵字,能夠幫助咱們保證內存可見性,當咱們聲明瞭一個volatile關鍵字,實際上有兩層含義;

  • 禁止進行指令重排序。
  • 一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的。

volatile是一種稍弱的同步機制,在訪問volatile變量時不會執行加鎖操做,也就不會執行線程阻塞,所以volatile變量是一種比synchronized關鍵字更輕量級的同步機制。

原理

在使用volatile關鍵字的時候,會多出一個lock前綴指令,lock前綴指令實際上至關於一個內存屏障實際上至關於一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:

1)它確保指令重排序時不會把其後面的指令排到內存屏障以前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操做已經所有完成;

2)它會強制將對緩存的修改操做當即寫入主存;

3)若是是寫操做,它會致使其餘CPU中對應的緩存行無效。

使用場景

這裏須要強調一點,volatile關鍵字並不必定能保證線程同步,若是非要採用volatile關鍵字來保證線程同步,則須要知足如下條件:

  • 對變量的寫操做不依賴於當前值
  • 該變量沒有包含在具備其餘變量的不變式中

其實看了一些書跟博客,都是這麼寫的,按照個人理解實際上就是隻有當volatile修飾的對象是原子性操做,纔可以保證線程同步,爲何呢。

測試代碼:

class Volatile {
    volatile static int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Volatile.add();
                }
            }).start();
        }

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count--->" + ++count);

    }

    private static void add() {
        count++;
    }
}

複製代碼

運行結果

count--->1001
複製代碼

理論上是1000纔對,可是輸出的值是1001,爲何呢,這個其實在以前的JMM中已經分析過了,下面再貼一張圖

volatile

跟以前同樣,咱們每次從主內存中獲取到的count確實是最新的,可是因爲對count的操做不是原子性操做,假如如今有兩個線程,線程1跟線程2,若是線程1讀取到了count值是5,而後read--->load進內存了,而後如今被線程2搶佔了CPU,那麼線程2就開始read--->load,而且完成了工做副本的賦值操做,而且將count 的值回寫到主內存中,因爲線程1已經進行了load操做,因此不會再去主內存中讀取,會接着進行本身的操做,這樣的話就出現了線程不安全,因此volatile必須是原子性操做才能保證線程安全。 基於以上考慮,volatile主要用來作一些標記位的處理:

volatile boolean flag = false;
 //線程1
while(!flag){
    doSomething();
}
  //線程2
public void setFlag() {
    flag = true;
}
複製代碼

當有多個線程進行訪問的時候,只要有一個線程改變了flag的狀態,那麼這個狀態會被刷新到主內存,就會對全部線程可見,那麼就能夠保證線程安全。

automatic

automatic是JDK1.5以後Java新增的concurrent包中的一個類,雖然volatile能夠保證內存可見性,大部分操做都不是原子性操做,那麼volatile的使用場景就比較單一,而後Java提供了automatic這個包,能夠幫助咱們來保證一些操做是原子性的。

使用方式

替換以前的volatile代碼

public static AtomicInteger atomicInteger = new AtomicInteger(0);
 private static void add() {
        atomicInteger.getAndIncrement();
    }
複製代碼

測試一下:

AtomicInteger: 1000
複製代碼
原理解析

AtomicInteger既保證了volatile保證不了的原子性,同時也實現了可見性,那麼它是如何作到的呢?

成員變量

private static final long serialVersionUID = 6214790243416807050L;
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    private volatile int value;
複製代碼

運算方式

public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
 int compare_and_swap(int reg, int oldval, int newval) {
        ATOMIC();
        int old_reg_val = reg;
        if (old_reg_val == oldval)
            reg = newval;
        END_ATOMIC();
        return old_reg_val;
    }
複製代碼

分析以前須要知道兩個概念:

  • 悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。

  • 樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可使用版本號等機制。

compare_and_swap這個纔是核心方法,也就是上面提到的CAS,由於CAS是基於樂觀鎖的,也就是說當寫入的時候,若是寄存器舊值已經不等於現值,說明有其餘CPU在修改,那就繼續嘗試。因此這就保證了操做的原子性。

變量私有化

這種方式實際上指的就是ThreadLocal,翻譯過來是線程本地變量,ThreadLocal會爲每一個使用該變量的線程提供獨立的變量副本,可是這個副本並非從主內存中進行讀取的,而是本身建立的,每一個副本相互之間獨立,互不影響。相對於syncronized的以時間換空間,ThreadLocal恰好相反,能夠減小線程併發的複雜度。

簡單使用

class ThreadLocalDemo {
    public static ThreadLocal<String> local = new ThreadLocal<>();//聲明靜態的threadlocal變量

    public static void main(String[] args) {
        local.set("Android");
        for (int i = 0; i < 5; i++) {
            SetThread localThread = new SetThread();//建立5個線程
            new Thread(localThread).start();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(local.get());

      
    }

    static class SetThread implements Runnable {

        @Override
        public void run() {
            local.set(Thread.currentThread().getName());
        }

    }
}
複製代碼

進行 測試

Android
複製代碼

雖然我用for循環建立了好幾個線程,可是並無改變ThreadLocal中的值,依然是個人大Android,這個就可以說明我賦的值是跟個人線程綁定的,每一個線程有特定的值。

源碼分析

成員變量
private final int threadLocalHashCode = nextHashCode();//當前線程的hash值
 private static AtomicInteger nextHashCode =//下一個線程的hash值
        new AtomicInteger();
 private static final int HASH_INCREMENT = 0x61c88647;//hash增加因子
複製代碼
構造函數
public ThreadLocal() {
    }
複製代碼

空實現。。。。

set方法
public void set(T value) {
        Thread t = Thread.currentThread();//獲取到當前線程
        ThreadLocalMap map = getMap(t);//獲取一個map
        if (map != null)
        //map不爲空,直接進行賦值
            map.set(this, value);
        else
        //map爲空,建立一個Map
            createMap(t, value);
    }
       
     ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
複製代碼
ThreadLocalMap

上面建立的Map其實是一個ThreadLocalMap,也便是用來保存跟線程綁定的數據的,之間看過HashMap的源碼,既然也叫Map,那麼其實應該是差很少的

基本方法

ThreadLocalMap

成員變量
private static final int INITIAL_CAPACITY = 16;//初始容量,2的冪
 
        private Entry[] table;//用來存放entry的數組
        private int size = 0;//數組長度
        private int threshold; // 閾值

//Entry繼承了WeakReference,說明key弱引用,便於內存回收
  static class Entry extends WeakReference<ThreadLocal<?>> {
           /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

複製代碼
構造方法
ThreadLocalMap(java.lang.ThreadLocal<?> firstKey, Object firstValue) {
    // 初始化table數組
    table = new Entry[INITIAL_CAPACITY];
    // 經過hash值來計算存放的索引
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 建立entry節點
    table[i] = new Entry(firstKey, firstValue);
    // 數組長度由0到1
    size = 1;
    // 將閾值設置成爲初始容量
    setThreshold(INITIAL_CAPACITY);
}
複製代碼

還有一個構造方法是傳一個Map,跟傳key-value大同小異就不解釋了

getEntry
private Entry getEntry(ThreadLocal<?> key) {
			 //經過key來計算數組下標
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
            //遍歷到直接返回
                return e;
            else
            //沒有遍歷到就會調用getEntryAfterMiss,繼續遍歷
                return getEntryAfterMiss(key, i, e);
        }

複製代碼
set方法
private void set(ThreadLocal<?> key, Object value) {
     
        Entry[] tab = table;//拿到table數組
        int len = tab.length;//獲取table的長度
        int i = key.threadLocalHashCode & (len-1);//計算下標
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                if (k == key) {
                //如便利到相同的能夠,那麼取而代之
                    e.value = value;
                    return;
                }
                if (k == null) {
                //替換key值爲空的entry
                    replaceStaleEntry(key, value, i);//
                    return;
                }
            }
            tab[i] = new Entry(key, value);//進行賦值
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

複製代碼
remove方法
private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            //遍歷下標尋找i
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);//清理指定的key
                    return;
                }
            }
        }
複製代碼

基本上分析到這裏已經將ThreadLocal分析清楚了,它的核心是一個ThreadLocalMap,存放了一個entry數組,期中key是ThreadLocal的weakreference,value就是set的值,而後每次set跟get都會對已有的entry進行清理,加商weakreference就能夠最大限度的放置內存泄露。

死鎖

定義

死鎖:是指多個線程因競爭資源而形成的一種僵局(互相等待),若無外力做用,這些進程都將沒法向前推動。

下面舉一個死鎖的例子

public class DeadLock implements Runnable {
    public int flag = 1;
    //靜態對象是類的全部對象共享的
    private static Object o1 = new Object(), o2 = new Object();
    @Override
    public void run() {
        System.out.println("flag=" + flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("1");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeadLock td1 = new DeadLock();
        DeadLock td2 = new DeadLock();
        td1.flag = 1;
        td2.flag = 0;
        //td1,td2都處於可執行狀態,但JVM線程調度先執行哪一個線程是不肯定的。
        //td2的run()可能在td1的run()以前運行
        new Thread(td1).start();
        new Thread(td2).start();

    }
}
複製代碼

無論哪一個線程先啓動,啓動的線程都會先sleep500ms,讓另一個線程得到CPU的使用權,這樣一來就保證了線程td1獲取到了O1的對象鎖,在競爭O2的對象鎖,td2獲取到了O2的對象鎖,在競爭O1的對象鎖,呵呵,這就尷尬了,而後互不想讓,就卡死了,形成了死鎖。

死鎖產生的必要條件
  • 1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。若是此時還有其它進程請求資源,則請求者只能等待,直至佔有資源的進程用畢釋放。
  • 2)請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對本身已得到的其它資源保持不放。
  • 3)不剝奪條件:指進程已得到的資源,在未使用完以前,不能被剝奪,只能在使用完時由本身釋放。
  • 4)環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈。
預防死鎖
  • 打破互斥條件。即容許進程同時訪問某些資源。
  • 打破不可搶佔條件。即容許進程強行從佔有者那裏奪取某些資源。就是說,當一個進程已佔有了某些資源,它又申請新的資源,但不能當即被知足時,它必須釋放所佔有的所有資源,之後再從新申請。
  • 打破佔有且申請條件。能夠實行資源預先分配策略。即進程在運行前一次性地向系統申請它所須要的所有資源。
  • 打破循環等待條件,實行資源有序分配策略。採用這種策略,即把資源事先分類編號,按號分配,使進程在申請,佔用資源時不會造成環路。全部進程對資源的請求必須嚴格按資源序號遞增的順序提出。

參考資料

www.importnew.com/18126.html

ifeve.com/introduce-a…

www.blogjava.net/xylz/archiv…

blog.csdn.net/chenssy/art…

相關文章
相關標籤/搜索