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