Java多線程與高併發(二)

synchronized同步

加synchronization的前提

  1.必須兩個或者兩個以上的線程同時訪問一個共享資源

  2.必須保證同步中只能有一個線程在運行

synchronization鎖的是共享對象,而不是代碼

 1.同步代碼塊

語法synchronization(共享資源,共享對象,需要是Object的子類){

核心業務邏輯

}

代碼示例:

package com.juc.ticket;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/25 20:13
 * @Description 同步代碼塊的用法
 */
public class Ticket implements Runnable {
    private static int ticket = 5;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (ticket > 0) {
                synchronized (this) {
                    System.out.println(Thread.currentThread().getName() + "----正在出售第" + (ticket--) + "張票");
                }
            }
        }
    }

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        for (int i = 1; i <= 5; i++) {
            new Thread(ticket, "買票口" + i).start();
        }
    }
}

2.同步方法的用法

直接在要鎖定的資源上加上synchronized關鍵字就好。

package com.juc.ticket;

public class T_002 implements Runnable {

    private static int ticket = 5;

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //這裏調用同步方法不加this,調用的方法只屬於當前對象,加this就屬於類
            this.test();
        }
    }

    private synchronized void test() {
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "---正在出售第" + (ticket--) + "張票");
        }
    }

    public static void main(String[] args) {
        T_002 t_002 = new T_002();
        for (int i = 1; i <= 5; i++) {
            new Thread(t_002, "買票口" + i).start();
        }
    }
}

以上兩種加鎖方式有什麼區別

同步代碼塊需要指定加鎖對象

同步方法不需要指定加鎖對象,方法調用的時候記得加上this,讓這個方法歸屬於類,而不是當前對象。

線程同步小結

同步監視器 

1.synchronized(obj){} 的obj成爲同步監視器

2.同步代碼塊中的同步監視器可以是任何對象,推薦共享資源對象作爲同步監視器

3.同步方法中無需指定同步監視器,因爲同步方法的同步監視器是this,也是該對象本身。

同步監視器執行的過程

1.線程一訪問cpu資源並鎖定同步監視器,讓其他線程等待,自己執行其中代碼

2.線程二訪問cpu資源,發現監視器被鎖,無法訪問,只能等待釋放。

3.線程一訪問完畢並解鎖同步監視器

4.線程二訪問cpu資源,發現同步監視器未鎖,鎖定並訪問,直至最後訪問完畢釋放鎖,其他的線程才能搶佔資源,誰先搶到誰先用。

小練習 

 使用synchronized實現張三和妻子同時去銀行取錢的過程。

示例代碼:

package com.juc.bank;

import java.math.BigDecimal;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/25 21:12
 * @Description 張三和張三的妻子同時取走500元
 */
public class Take implements Runnable {
    private BigDecimal account = new BigDecimal(1500);

    @Override
    public void run() {

        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (account) {
                if (account.compareTo(BigDecimal.valueOf(500)) >0) {
                    System.out.println(Thread.currentThread().getName() + "----取走了500.00元");
                    System.out.println("剩餘:" + (account = account.subtract(BigDecimal.valueOf(500))));
                }
            }
        }
    }

    public static void main(String[] args) {
        Take take = new Take();
        new Thread(take, "張三").start();
        new Thread(take, "張三的妻子").start();
    }

}

死鎖問題

1.同步可以保證資源共享操作的正確性,但是過多同步也會產生死鎖問題。

2.那麼死鎖什麼原因產生的呢?

    死鎖一般情況下表示相互等待,是程序運行時出現的一種情況

例如下圖

生產者與消費者

1.生產者不斷的生產,消費者不斷的取走生產者生產的產品

2.生產者將生產的產品放在一個公共的區域中,消費者去這個區域消費生產者生產的產品。

過程圖

示例代碼1

共享資源

package com.juc.pc.c_001;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/25 23:05
 * @Description 共享資源
 */
public class Goods {
    /**
     * @Author zcm
     * @Email [email protected]
     * @date 2020/5/25 23:04
     * @Description 產品名稱
     */
    private String name;
    /**
     * @Author zcm
     * @Email [email protected]
     * @date 2020/5/25 23:05
     * @Description 品牌
     */
    private String brand;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }
}

消費者代碼

package com.juc.pc.c_001;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/25 23:40
 * @Description 消費者
 */
public class Consumer implements Runnable {
    private Goods goods;

    public Consumer() {
    }

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消費者消費了" + goods.getBrand() + "--" + goods.getName());
        }
    }
}

生產者代碼

package com.juc.pc.c_001;

import com.juc.bank.Take;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/25 23:06
 * @Description 生產者
 */
public class Producer implements Runnable {
    private Goods goods;

    public Producer() {
    }

    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {

            if (i % 2 == 0) {
                goods.setBrand("哇哈哈");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                goods.setName("礦泉水");
            }else{
                goods.setBrand("旺仔");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                goods.setName("小饅頭");
            }
            System.out.println("生產者生產了"+goods.getBrand()+"---"+goods.getName());
        }
    }
}

測試代碼

package com.juc.pc.c_001;

public class Test {
    public static void main(String[] args) {
        Goods goods = new Goods();
        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

執行結果:

解決方案:

加鎖synchronized

示例代碼2:

共享代碼

package com.java0526.pc.c_002;

/**
 * @program: untitled
 * @ClassName Goods
 * @Description
 * @Author zcm
 * @Date 2020/5/26 12:58
 * @Version V1.0
 */

public class Goods {
    private String brand;

    private String name;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * @Description: 生產者開始生產
     * @Author: zcm
     * @return: Param:
     * @Date:2020/5/26 13:03
     */
    public synchronized void set(String brand, String name) {
        this.setBrand(brand);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        System.out.println("生產者開始生產產品:" + this.getBrand() + "---" + this.getName());
    }

    /**
     * @Description: 消費者開始消費產品
     * @Author: zcm
     * @return: Param:
     * @Date:2020/5/26 13:03
     */
    public synchronized void get() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("消費者開始消費產品:" + this.getBrand() + "---" + this.getName());
    }

}

生產者代碼

package com.java0526.pc.c_002;

/**
 * @program: untitled
 * @ClassName Producer
 * @Description
 * @Author zcm
 * @Date 2020/5/26 13:18
 * @Version V1.0
 */
public class Producer implements Runnable {
    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }

    public Producer() {
    }

    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {

            if (i % 2 == 0) {
                this.goods.set("娃哈哈", "礦泉水");
            } else {
                this.goods.set("旺仔", "小饅頭");
            }

        }
    }
}

消費者代碼

package com.java0526.pc.c_002;

/**
 * @program: untitled
 * @ClassName Consumer
 * @Description
 * @Author zcm
 * @Date 2020/5/26 13:18
 * @Version V1.0
 */
public class Consumer implements Runnable {

    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    public Consumer() {
    }


    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            goods.get();
        }
    }
}

測試代碼

package com.java0526.pc.c_002;

/**
 * @program: untitled
 * @ClassName Test
 * @Description
 * @Author zcm
 * @Date 2020/5/26 13:25
 * @Version V1.0
 */
public class Test {
    public static void main(String[] args) {
        Goods goods=new Goods();
        Producer producer=new Producer(goods);
        Consumer consumer=new Consumer(goods);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

執行結果:

Java提供3方法解決線程之間通信問題
 final void wait() 表示線程一直等待,直到其它線程通知
final void wait(long timeout) 線程等待指定毫秒參數的時間
final void wait(long timeout,int nanos) 線程等待指定毫秒、微妙的時間
final void notify() 喚醒一個處於等待狀態的線程
final void notifyAll() 喚醒同一個對象上所有調用wait()方法的線程, 優先級別高的線程優先運行 
使用以上方法需注意:
  只能在同步方法或者同步代碼塊中使用,非同步方法使用會拋出異常IllegalMonitorStateException
Object喚醒與等待過程

實例三:

共享對象代碼:

package com.java0526.pc.c_003;

/**
 * @program: untitled
 * @ClassName Goods
 * @Description
 * @Author zcm
 * @Date 2020/5/26 12:58
 * @Version V1.0
 */

public class Goods {
    private String brand;

    private String name;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * @Description: 默認是不存在商品的,如果值等於true的話,代表有商品
     * @Author: zcm
     * @return: Param:
     * @Date:2020/5/26 13:40
     */
    private Boolean flag = false;

    /**
     * @Description: 生產者開始生產
     * @Author: zcm
     * @return: Param:
     * @Date:2020/5/26 13:03
     */
    public synchronized void set(String brand, String name) {
        if (flag) {
            try {
//消費者還沒消費,那麼讓生產者阻塞
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.setBrand(brand);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        System.out.println("生產者開始生產產品:" + this.getBrand() + "---" + this.getName());
        flag = true;//生產完畢,等待消費者消費
        notify();//喚醒消費者去消費商品
    }

    /**
     * @Description: 消費者開始消費產品
     * @Author: zcm
     * @return: Param:
     * @Date:2020/5/26 13:03
     */
    public synchronized void get() {
        //生產者還沒有生產商品,不能去消費
        if (!flag) {
            try {
                //生產者還沒生產,讓消費者阻塞
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("消費者開始消費產品:" + this.getBrand() + "---" + this.getName());

        //消費完畢,設爲false。提醒生產者該去生產商品了
        flag = false;
        notify();//喚醒生產者去生產

    }

}

生產者代碼:

package com.java0526.pc.c_003;

/**
 * @program: untitled
 * @ClassName Producer
 * @Description
 * @Author zcm
 * @Date 2020/5/26 13:18
 * @Version V1.0
 */
public class Producer implements Runnable {
    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }


    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            if (i % 2 == 0) {
                this.goods.set("娃哈哈", "礦泉水");
            } else {
                this.goods.set("旺仔", "小饅頭");
            }

        }
    }
}

消費者代碼:

package com.java0526.pc.c_003;

/**
 * @program: untitled
 * @ClassName Consumer
 * @Description
 * @Author zcm
 * @Date 2020/5/26 13:18
 * @Version V1.0
 */
public class Consumer implements Runnable {

    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    public Consumer() {
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            goods.get();
        }
    }
}

執行結果:

以上只是針對簡單的消費者和生產者。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章