1. 程式人生 > >多線程之 線程安全

多線程之 線程安全

方法 格式 name 加鎖 解決 ESS test nts system

一.線程安全出現原因:

  原因:  原本不應該拆開的兩個步驟中間,被其他線程插足。

  解決方案:(java中的同步機制 [synchronized] 來解決),具體有下面三種
   a. 同步代碼塊
   b. 同步方法
   c. Lock接口
   創建:Lock lock = new ReentrantLock();
   霸占鎖:lock();
  釋放鎖:unlock();

  代碼演示線程安全問題:

public class Ticket implements Runnable{

    //定義一個成員變量,表示還有多少張票
    int num = 100;

    //要在run方法中定義賣票的任務
    @Override
    public void run() {
        //因為要一直賣票,所以使用死循環
        while (true) {
            //如果還有票,那麽就賣票
            if(num > 0) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                }
                System.out.println(Thread.currentThread().getName() + "正在賣票:" + num);
                num--;
            }
        }
    }
}

public class Demo01TicketTest {
    public static void main(String[] args) {
        //創建三個線程,執行這個賣票任務
        Ticket t = new Ticket();
        //等同於創建了3個窗口,每個窗口都執行一樣的任務:售票
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();

    }
}

  上面代碼Demo01TicketTest,運行後可在控制臺查看:

      1.出現了票 0 ,-1

      2.還有售出重復的票

  這就是多個線程會出現的線程安全問題,而在現實中這是不允許的

二.解決線程安全的同步代碼塊方法

  同步代碼塊: synchronized關鍵字可以用於方法中的某個區塊中,表示支隊這個區塊的資源實行互斥訪問

  格式: synchronized( 同步鎖 ){

       需要同步操作的代碼

     }

  同步鎖: 對象的同步鎖知識一個概念,就是個標記,(就好像獅子占地盤,撒了尿這就是我的地盤)

      1. 鎖對象,可以是任意類型

      2. 多個線程對象,要使用同一把鎖

  註意:  一個同步鎖最多被一個線程鎖擁有,誰拿到鎖就可以進入代碼塊,其他的線程只能外面等待,等待同步鎖被上一個線程釋放後,再進行搶占cpu

  使用此方法解決,上面多線程售票安全問題

public class Ticket implements Runnable{

    int num = 100;

    //定義一個對象,這個對象表示鎖對象
    //鎖對象沒有特殊的含義,只是做一個標記
    //多個線程必須使用同一個鎖對象,否則不能保證線程安全。
    Object lock = new Object();

    @Override
    public void run() {
        while (true) {
            //當線程執行到synchronized這句代碼的時候,會看一下這個上面還有沒有鎖
            //如果同步代碼塊上面還有鎖,那麽線程就獲取到這個鎖, 然後進入到同步代碼塊中。
            //如果同步代碼塊上面沒有鎖, 那麽這個線程就一直在這個位置等著獲取鎖, 如果鎖一直不來,那麽就一直等著.
            synchronized (lock) {
                if(num > 0) {
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                    }
                    System.out.println(Thread.currentThread().getName() + "正在賣票:" + num);
                    num--;
                }
            }
            //當一個線程離開同步代碼塊,那麽他會釋放鎖。
            //之後其他線程就可以去競爭這個鎖,誰搶到了,那麽誰就去執行這個同步代碼塊。
        }
    }
}

三.解決線程安全的同步方法方法

  同步方法: 使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外面等著

  格式:  public synchronized void method(){

        可能會產生線程安全問題的代碼

      }

  註意:  同步方法中也有同步鎖

        對於非static方法,同步鎖就是this,

        對於static方法,同步鎖是類名.class (我們使用當前方法所在類的字節碼對象)

  使用同步方法代碼解決售票的線程安全問題:

package cn.itcast.demo02;

@SuppressWarnings("all")
public class Ticket implements Runnable{
    int num = 100;
    @Override
    public void run() {
        while (true) {
            sell();
        }
    }

    //同步方法,表示對整個方法體都加上了同步代碼塊
    public synchronized void sell() {
        if(num > 0) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread().getName() + "正在賣票:" + num);
            num--;
        }
    }
}

四.解決線程安全的Lock接口方法

  Lock鎖: lock機制提供了比synchronized代碼塊和synchronized方法更加廣泛的操作

       Lock,更加強大,更加體現面向對象

  Lock鎖也成為同步鎖,將加鎖和釋放所拆分兩個方法,如下:

     public void lock(): 加同步鎖

     public void unlock(): 釋放同步鎖

  使用該方法解決多線程售票安全問題

public class Ticket4 implements Runnable{
    int num = 100;
    //創建Lock對象
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //調用lock對象的lock()方法,手動的獲取鎖
            lock.lock();
            if(num > 0) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                }
                System.out.println(Thread.currentThread().getName() + "正在賣票:" + num);
                num--;
            }
            //調用lock對象的unlock方法,手動的釋放鎖
            lock.unlock();
        }
    }
}

  

多線程之 線程安全