JUC並發編程(一)


JUC並發編程

1、什么是JUC

​ JUC即java.util.concurrent

​ **涉及三個包: **

 	- java.util.concurrent
 	- java.util.concurrent.atomic
 	- java.util.concurrent.locks

image-20210319155131299

2、線程和進程

2.1 什么是線程和進程

​ **進程: **一個程序。如QQ、wechat等

​ **線程: **程序中的 某些操作。如打開了Typora,輸入、保存等就是線程。

​ 一個進程至少包含一個線程!

​ java中默認有兩個線程,一個是main線程,另外一個是GC線程。

​ **java能自己開啟線程嗎? **

​ 答:不能,java是通過調用本地c++方法開啟的線程。如下圖所示:

image-20210319161507931

2.2 並發與並行的區別

**並發: **單核CPU模擬出多條線程,不斷交替,為快不破。

**並行: **多核CPU,多線程同時執行。

如何查看CPU核數?

public static void main(String[] args) {
    System.out.println(Runtime.getRuntime().availableProcessors());
}
2.3 線程的六種狀態

Thread中有個內部枚舉類Thread.State其中定義了線程的六種狀態,源碼如下:

/** 新生
 * Thread state for a thread which has not yet started.
 */
NEW,

/** 運行
 * Thread state for a runnable thread.  A thread in the runnable
 * state is executing in the Java virtual machine but it may
 * be waiting for other resources from the operating system
 * such as processor.
 */
RUNNABLE,

/** 阻塞
  * Thread state for a thread blocked waiting for a monitor lock.
  * A thread in the blocked state is waiting for a monitor lock
  * to enter a synchronized block/method or
  * reenter a synchronized block/method after calling
  * {@link Object#wait() Object.wait}.
  */
BLOCKED,

/** 一直等待,等到地老天荒
  * Thread state for a waiting thread.
  * A thread is in the waiting state due to calling one of the
  * following methods:
  * <ul>
  *   <li>{@link Object#wait() Object.wait} with no timeout</li>
  *   <li>{@link #join() Thread.join} with no timeout</li>
  *   <li>{@link LockSupport#park() LockSupport.park}</li>
  * </ul>
  *
  * <p>A thread in the waiting state is waiting for another thread to
  * perform a particular action.
  *
  * For example, a thread that has called <tt>Object.wait()</tt>
  * on an object is waiting for another thread to call
  * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
  * that object. A thread that has called <tt>Thread.join()</tt>
  * is waiting for a specified thread to terminate.
  */
WAITING,

/** 有時間的等待
  * Thread state for a waiting thread with a specified waiting time.
  * A thread is in the timed waiting state due to calling one of
  * the following methods with a specified positive waiting time:
  * <ul>
  *   <li>{@link #sleep Thread.sleep}</li>
  *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
  *   <li>{@link #join(long) Thread.join} with timeout</li>
  *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
  *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
  * </ul>
  */
TIMED_WAITING,

/** 線程終止
  * Thread state for a terminated thread.
  * The thread has completed execution.
  */
2.4 wait與sleep的區別
  • 來着不同的類

    wait =>Object,sleep=>Thread

  • 關於鎖得釋放

    wait =>釋放鎖,sleep=>占用鎖,抱着鎖睡

  • 使用范圍

    wait =>同步代碼塊中使用,sleep=>想在哪睡在哪睡,任何地方使用

3、Lock鎖

3.1 什么是Lock鎖

如下圖:

image-20210319173049146

3.2 如何使用
Lock lock = new ReentrantLock();
//加鎖
lock.lock();
try{
    //業務代碼
}finally {
    //解鎖
    lock.unlock();
}
3.3 Lock與Synchronize的區別
  • 來源不同

    synchronize => java的內置關鍵字,在jvm層;Lock =>java的一個接口

  • 獲取鎖得方式不同

    synchronize => 自動獲取鎖,不能判斷鎖得狀態;Lock => 手動獲取鎖,可判斷是否獲取到鎖

  • 線程阻塞方面

    synchronize => 線程1阻塞會導致線程2永遠等待;Lock=>不一定會等下去

  • 鎖得類型不同

    synchronize=>可重入鎖、不可中斷、非公平;Lock=>可重入鎖、可判斷鎖、非公平(可設置成公平)

  • 使用范圍不同

    synchronize=>適用於少量代碼塊同步;Lock=>適合鎖大量的同步代碼塊

3.4 synchronize與lock模擬多線程搶票

synchronize

public class SaleTicket {

    public static void main(String[] args) {

        Ticket ticket = new Ticket();

        new Thread(() ->{
            for (int i = 0; i < 100; i++) {
                ticket.saleTicket();
            }
        },"A").start();

        new Thread(() ->{
            for (int i = 0; i < 100; i++) {
                ticket.saleTicket();
            }
        },"B").start();

        new Thread(() ->{
            for (int i = 0; i < 100; i++) {
                ticket.saleTicket();
            }
        },"C").start();
    }
}

class Ticket{

    private static int ticketAmount = 100;

    public synchronized void saleTicket(){
        if(ticketAmount>0){
            System.out.println(Thread.currentThread().getName()+"購買了第"+ticketAmount--+"張票");
        }
    }
}

Lock

public class SaleTicket {

    public static void main(String[] args) {

        Ticket ticket = new Ticket();

        new Thread(() ->{
            for (int i = 0; i < 100; i++) {
                ticket.saleTicket();
            }
        },"A").start();

        new Thread(() ->{
            for (int i = 0; i < 100; i++) {
                ticket.saleTicket();
            }
        },"B").start();

        new Thread(() ->{
            for (int i = 0; i < 100; i++) {
                ticket.saleTicket();
            }
        },"C").start();
    }
}

class Ticket{

    private static int ticketAmount = 100;

    public void saleTicket(){
        //創建lock鎖
        Lock lock = new ReentrantLock();
        //加鎖
        lock.lock();
        try {//業務代碼
            if(ticketAmount>0){
                System.out.println(Thread.currentThread().getName()+"購買了第"+ticketAmount--+"張票");
            }
        }finally {
            //解鎖
            lock.unlock();
        }
    }
}

4、生產者和消費者模式

​ 線程之間的通信問題就是生產者和消費者問題,即線程的交替執行。比如兩個線程同時操作一個變量,就涉及到通知、等待和喚醒。其實線程間的通信主要就是通知、業務執行和喚醒。

4.1 synchronize版的生產者和消費者模式
public class Test {
    public static void main(String[] args) {
        PC pc = new PC();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.add();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.decrease();
            }
        },"B").start();
    }
}

class PC{

    private static int amount=0;

    public synchronized void add(){
        if (amount>0){//如果大於0,不"生產"(加1),線程進入等待狀態
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        amount++;
        System.out.println(Thread.currentThread().getName()+"執行了加法,amount="+amount);
        //喚醒其他線程
        notifyAll();
    }

    public synchronized void decrease(){
        if(amount == 0){//如果amount=0,不消費(不減1),線程進入等待狀態
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        amount--;
        System.out.println(Thread.currentThread().getName()+"執行了減法,amount="+amount);
        //喚醒其他線程
        notifyAll();
    }
}

執行結果如下圖:

image-20210320180001807

4.2 synchronize模擬生產者和消費者模式產生的問題和解決方法

同樣是4.1中的代碼如果同時開啟4個(超過2個線程)代碼如下

public class Test {
    public static void main(String[] args) {
        PC pc = new PC();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.add();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.decrease();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.add();
            }
        },"C").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.decrease();
            }
        },"D").start();
    }
}

class PC{
    private static int amount=0;
    public synchronized void add(){
        if (amount>0){//如果大於0,不"生產"(加1),線程進入等待狀態
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        amount++;
        System.out.println(Thread.currentThread().getName()+"執行了加法,amount="+amount);
        //喚醒其他線程
        notifyAll();
    }

    public synchronized void decrease(){
        if(amount == 0){//如果amount=0,不消費(不減1),線程進入等待狀態
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        amount--;
        System.out.println(Thread.currentThread().getName()+"執行了減法,amount="+amount);
        //喚醒其他線程
        notifyAll();
    }
}

執行結果如下圖:

image-20210320180328139

上圖中的執行出問題了,得到的結果並不是0,1了。出現了意料之外的結果。為什么會出現這樣的結果?

答:從執行結果中我們發現感覺是線程B等待失效。然后我們去官方文檔中找到Object類中的wait方法,我們看到這樣一段話,如下圖:

image-20210320181404855

按照jdk幫助文檔的提示,造成錯誤的原因是wait是不占用鎖的,在多個線程同時被喚醒的時候有一個隨機搶鎖的過程,導致了不該喚醒的線程搶到了鎖被喚醒了,即虛假喚醒。解決方法也很簡單,就是將判斷方法中的if改成while即可。while與if的不同在於,當等待的線程被喚醒后,if判斷的線程會直接往下走,而while判斷的線程需要重新判斷才能往下走。所以jdk官方文檔推薦使用while代替if從而解決虛假喚醒的現象。

image-20210320182911609

4.3 Lock鎖模擬生產者和消費者模式

​ 使用Condition工具類去作為Object監視器,用signal()和await()方法代替synchronized中的notify()和wait()
Condition的優勢在於能精准的喚醒線程

image-20210320183700999

public class Test02 {

    public static void main(String[] args) {
        PC2 pc = new PC2();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.add();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.decrease();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.add();
            }
        },"C").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.decrease();
            }
        },"D").start();
    }
}

class PC2{
    private static int amount=0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void add(){
        lock.lock();
        try{
            while(amount>0){//如果大於0,不"生產"(加1),線程進入等待狀態
                try {
                    //等待
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            amount++;
            System.out.println(Thread.currentThread().getName()+"執行了加法,amount="+amount);
            //喚醒其他線程
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void decrease(){
        lock.lock();
        try{
            while(amount == 0){//如果amount=0,不消費(不減1),線程進入等待狀態
                try {
                    //等待
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            amount--;
            System.out.println(Thread.currentThread().getName()+"執行了減法,amount="+amount);
            //喚醒其他線程
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

執行結果如下圖:

image-20210320184806058

4.4 Lock鎖實現精確喚醒

​ 實現目標:A、B、C、D四個線程順序執行。

public class Test02 {

    public static void main(String[] args) {
        PC2 pc = new PC2();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.add();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.decrease();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.add();
            }
        },"C").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                pc.decrease();
            }
        },"D").start();
    }
}

class PC2{
    private static int amount=0;
    Lock lock = new ReentrantLock();
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();
    Condition conditionD = lock.newCondition();

    public void add(){

        lock.lock();
        try{
            while (amount>0){//如果大於0,不"生產"(加1),線程進入等待狀態
                try {
                    //等待
                    if("A".equals(Thread.currentThread().getName())){
                        conditionA.await();
                    }else if("C".equals(Thread.currentThread().getName())){
                        conditionC.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            amount++;
            System.out.println(Thread.currentThread().getName()+"執行了加法,amount="+amount);
            //喚醒其他線程
            if("A".equals(Thread.currentThread().getName())){
                conditionB.signal();
            }else if("C".equals(Thread.currentThread().getName())){
                conditionD.signal();
            }
        }finally {
            lock.unlock();
        }
    }

    public void decrease(){
        lock.lock();
        try{
            while(amount == 0){//如果amount=0,不消費(不減1),線程進入等待狀態
                try {
                    if("B".equals(Thread.currentThread().getName())){
                        conditionB.await();
                    }else if("D".equals(Thread.currentThread().getName())){
                        conditionD.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            amount--;
            System.out.println(Thread.currentThread().getName()+"執行了減法,amount="+amount);
            //喚醒其他線程
            if("B".equals(Thread.currentThread().getName())){
                conditionC.signal();
            }else if("D".equals(Thread.currentThread().getName())){
                conditionA.signal();
            }
        }finally {
            lock.unlock();
        }
    }
}

​ 執行結果:

image-20210320185726025

5、八個關於鎖的問題

5.1 先發短信還是先打電話
public class One {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread( () -> {
            phone.call();
        },"A").start();

        new Thread( () -> {
            phone.sendMsg();
        },"B").start();
    }
}
class Phone{
    public synchronized void call(){
        System.out.println("打電話");
    }
    public synchronized void sendMsg(){
        System.out.println("發短信");
    }
}

先打電話,再發短信

5.2 延遲后是先發短信還是先打電話
public class Two {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread( () -> {
            phone.call();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread( () -> {
            phone.sendMsg();
        },"B").start();
    }
}

class Phone2{
    public synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }
    public synchronized void sendMsg(){
        System.out.println("發短信");
    }
}

還是先打電話再發短信

5.3 兩個對象調用不同的方法,執行的先后順序
public class Three {
    public static void main(String[] args) {
        Phone3 phoneA = new Phone3();
        Phone3 phoneB = new Phone3();
        new Thread( () -> {
            phoneA.call();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread( () -> {
            phoneB.sendMsg();
        },"B").start();
    }
}

class Phone3{
    public synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }
    public synchronized void sendMsg(){
        System.out.println("發短信");
    }
}

先發短信后打電話

5.4 一個同步方法一個普通方法,誰先執行
public class Four {
    public static void main(String[] args) {
        Phone4 phone = new Phone4();
        new Thread( () -> {
            phone.call();
        },"A").start();
        new Thread( () -> {
            phone.sendMsg();
        },"B").start();
    }
}
class Phone4{
    public synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }
    public void sendMsg(){
        System.out.println("發短信");
    }
}

先執行普通方法發短信,再執行打電話

5.5 同步方法都是靜態方法,同一個對象,先執行哪個
public class Five {
    public static void main(String[] args) {
        Phone5 phone = new Phone5();
        new Thread( () -> {
            phone.call();
        },"A").start();
        new Thread( () -> {
            phone.sendMsg();
        },"B").start();
    }
}
class Phone5{
    public static synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }
    public static synchronized void sendMsg(){
        System.out.println("發短信");
    }
}

先執行打電話,后執行發短信。

5.6 兩個對象調用不同的靜態同步方法,誰先執行
public class Six {
    public static void main(String[] args) {
        Phone6 phoneA = new Phone6();
        Phone6 phoneB = new Phone6();
        new Thread( () -> {
            phoneA.call();
        },"A").start();
        new Thread( () -> {
            phoneB.sendMsg();
        },"B").start();
    }
}
class Phone6{
    public static synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }
    public static synchronized void sendMsg(){
        System.out.println("發短信");
    }
}

先執行打電話,然后執行發短信

5.7 一個靜態同步方法,一個普通同步方法,只有一個對象,先執行哪個
public class Seven {
    public static void main(String[] args) {
        Phone7 phone = new Phone7();
        new Thread( () -> {
            phone.call();
        },"A").start();

        new Thread( () -> {
            phone.sendMsg();
        },"B").start();
    }
}
class Phone7{
    public synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }
    public static synchronized void sendMsg(){
        System.out.println("發短信");
    }
}

先執行發短信,再執行打電話

5.8 一個靜態同步方法,一個普通同步方法,兩個對象,先執行哪個方法
public class Eight {
    public static void main(String[] args) {
        Phone8 phoneA = new Phone8();
        Phone8 phoneB = new Phone8();
        new Thread( () -> {
            phoneA.call();
        },"A").start();
        new Thread( () -> {
            phoneB.sendMsg();
        },"B").start();
    }
}
class Phone8{
    public synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }
    public synchronized void sendMsg(){
        System.out.println("發短信");
    }
}

先執行發短信,后執行打電話

5.9 總結

​ 鎖只會鎖兩個東西,一個是new 出來的對象,另外一個是唯一的class模板。普通的synchronize修飾的同步方法或同步代碼塊,鎖得是調用該方法的對象。static修飾的同步方法或同步代碼塊,鎖的是class唯一的模板,因此調用該同步方法的線程其對象是否一樣,鎖都是同一個。

6、集合不安全

6.1 ArrayList線程不安全
    1. 不安全的ArrayList

    我們開啟多個線程向同一個list中添加,結果如下圖所示:

image-20210323173029134

  • 2.ArrayList線程不安全的原因

​ 我們進入ArrayList的源碼可以看到以下代碼:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

​ 我們從源碼中可以發現,ArrayList的底層是個Object數組。在add方法中,先是多數組長度加1,然后將需要添加的對象加到數組的最后。這樣就會存在一個問題,就是多個線程同時對數組長度進行了加1,后面線程的值將覆蓋前面線程的值。

  • 3.如何解決ArraList線程不安全問題
public class UnSafeArrayList {
    public static void main(String[] args) {
        //1.將ArrayList 改為 Vector
        //List<String>  list = new Vector<>();
        //2.用Collections工具類,將ArrayList轉為線程安全的集合類
        //List<String>  list = Collections.synchronizedList(new ArrayList<>());
        //3.用CopyOnWriteArrayList 代替ArrayList
        List<String>  list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 100; i++) {
            new Thread( () ->{
                list.add(Thread.currentThread().getName());
            },""+i).start();
        }
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

總結:Vector之所以是線程安全的,是因為Vector中的add方法是用synchronize修飾的;Collections.synchronizedList()之所以線程安全,是因為Collections.synchronizedList()得到了一個SynchronizedList對象,SynchronizedList中的add、set、get等方法中都使用了synchronized代碼塊;CopyOnWriteArrayList 之所以是線程安全的是因為CopyOnWriteArrayList 的原理是寫入時復制,即在在執行add方法時,將原數組復制一份(長度+1),然后將需要添加的對象加到復制數組的末尾,最后用復制的數組替代原數組,在add方法中使用的是Lock鎖,從而保證線程安全,源碼如下:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

綜上所述推薦使用CopyOnWriterArrayList代替ArrayList解決ArrayList的線程不安全問題,理由是synchronize等於低效率。

6.2 HashSet線程不安全
  • 1.不安全的HashSet

我們開啟多個線程向同一個set中添加,結果如下圖所示:

image-20210323173159030

  • 2.Set線程不安全的原因

​ HashSet的底層就是HashMap

public HashSet() {
    map = new HashMap<>();
}
//hashSet的add方法 本質是hashMap的put,set的值就是map中的key,PRESENT是一個常量,所有set是不可重復的
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
  • 3.如何解決HashSet的線程不安全問題
public class UnSafeHashSet {
    public static void main(String[] args) {
        //Set<String> list = new HashSet();
        //1.用Collections工具類將HashSet轉為線程安全的集合類SynchronizedSet
        //Set<String> list = Collections.synchronizedSet(new HashSet<>());
        //2.用CopyOnWriteArraySet代替HashSet
        Set<String> list = new CopyOnWriteArraySet();
        for (int i = 0; i < 30; i++) {
            new Thread( () -> {
                list.add(Thread.currentThread().getName());
                System.out.println(list);
            },""+i).start();
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

6.3 HashMap線程不安全
  • 1.不安全的HashMap

image-20210323182133330

  • 2.如何解決HashMap的線程不安全問題
public class UnSafeHashMap {
    public static void main(String[] args) {
        //1. map 是這么用的嗎? 不是,工作中不用HashMap
        //2. 默認等價於什么? new HashMap<>(16,0.75)
        //Map<String,String> map = new HashMap<>();
        //1.用Collections工具類將HashMap轉變成線程安全的SynchronizedMap
        //Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
        //2.用ConcurrentHashMap 代替HashMap
        Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            new Thread( () -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

7、Callable

image-20210323185945489

  • 1.可以有返回值
  • 2.可以拋出異常
  • 3.方法不同,run()/call()
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
      //Thread中只能傳Runnable對象,Callable對象無法傳入,怎么解決?
      //我們在官方文檔中找到Runnable接口的一個實現類——FutureTask,其中有個構造方法可傳入Callable對象
        FutureTask futureTask = new FutureTask(myThread);
        new Thread(futureTask,"A").start();
        //多個線程調用只會打印一次call(),結果有緩存
        new Thread(futureTask,"B").start();
        //獲取返回值
        //可能會阻塞,因為如果call方法中是一個耗時的業務將導致阻塞
        //解決阻塞的辦法:1.放到最后,2.異步通信
        Integer o = (Integer) futureTask.get();
        System.out.println(o);
    }
}
class MyThread implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 1024;
    }
}

image-20210323192836574


免責聲明!

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



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