JUC以及并发,线程同步,线程安全的概念加深

从Java线程到kotlin协程 专栏收录该内容
15 篇文章 2 订阅

上一篇:线程间的通信 wait、notify

JUC

JUC:就是JDK给我们提供好的一个工具包,提供了很多用于处理并发的类和接口。包名是 java.util.concurrent .取前三个字母就是JUC

我们先大致看一眼这个包里都有啥
在这里插入图片描述

可以看到,JUC包中有很多类和接口,我们之前用的Callable,FutureTask以及用来简化线程休眠的TimeUnit都在这个包中。

所以说,基本上,不管你是Android开发还是Java开发,只要涉及到多线程并发的知识,这个包的代码你是一定会用到的,所以java.util.concurrent这个包里的类和接口非常重要

之前在本专栏的第一篇文章中已经提到过多线程和并发的概念。本篇我们再来加深下对并发线程安全,和线程同步概念的理解。

并发(concurrent)

首先并发的种类分为两种

  • 交替执行 : 在单核的cpu中,多个线程轮流占用cpu资源交替去执行任务(操作资源)
  • 并行执行 : 在多核的cpu中,多个线程并行的去执行任务(操作资源)

以上两种情况,交替执行并行执行都是并发.

线程安全

上面我们说到了多线程并行执行,那假如多个线程同时去操作一个资源,两个线程同时一个值,另外一个线程又同时在这个值,那这个值最终读出来的是哪个线程写进去的值呢?
因为多线程的执行时机是由cpu调度的,所以最终读出来的值到底是哪个我们是没办法保证的,这个就是线程安全问题。

之前的多线程卖票和线程不安全的单例模式都属于线程安全问题,我们再来看看另外一个例子。

以下代码中,我们在for循环中给一个ArrayList中添加元素。

    public static void main(String[] args) {
        ArrayList<Integer> integers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            /*在单个线程向数组中添加元素*/
            integers.add(i);
            System.out.println(Thread.currentThread().getName() + ":integers = " + integers);
        }
    }

这个运行是肯定没问题的,因为我们是在一个线程中操作的ArrayList

运行结果如下:
在这里插入图片描述

下面我们来改一改代码,在多线程中往ArrayList中添加元素试一下:

       public static void main(String[] args) {
        ArrayList<Integer> integers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            /*启动多个线程向数组中添加元素*/
            int finalI = i;
            new Thread(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                integers.add(finalI);
                System.out.println(Thread.currentThread().getName() + ":integers = " + integers);
                
            }).start();
        }
    }

然后我们多运行几次试试
在这里插入图片描述

运行结果可以看到,有很大几率出现打印的值有不正确的值,且会出现ConcurrentModificationException这个异常。这个异常翻译起来很好理解,叫做: 并发修改异常

原因是ArrayList的add方法是线程不安全的方法,所以在多线程中通过add方法向集合中添加数据时,就可能会出现该异常。

解决这个异常也很简单,在修改数据的地方做线程同步操作

线程同步

当多个线程要对同一个内存地址进行操作时(一般都是写操作),同一时间只能有一个线程对该内存地址进行操作,其他线程不可以对该内存地址进行操作,此时,其他线程处于BLOCKED (阻塞)状态,这个就是线程同步。

线程同步是为了解决线程安全问题。

在之前文章的多线程卖票例子中已经处理过线程同步了,使用的是JVM提供的synchronized关键字。

其实,jdk也给我提供了一些线程安全的集合来给我们使用。

比如使用java.util包中的Vector

我们先来看看ArrayList的add方法和Vector的add方法有什么区别:

首先是ArrayList的add
在这里插入图片描述

再来看看Vector的add
在这里插入图片描述

可以看到,Vector的add方法使用了synchronized修饰,是同步方法。

下面我们将ArrayList替换为Vector试一下。

    public static void main(String[] args) {

        Vector<Integer> integerVector = new Vector<>();

        for (int i = 0; i < 10; i++) {
            /*启动多个线程向数组中添加元素*/
            int finalI = i;
            new Thread(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                /*Vector的add是线程安全的*/
                integerVector.add(finalI);

                System.out.println(Thread.currentThread().getName() + ":integerVector = " + integerVector);

            }).start();
        }


    }

运行看看效果:
在这里插入图片描述

可以看到,数据是正常的,且不会报ConcurrentModificationException这个异常。

本篇文章主要是初步对JUC的认识,以及加深一下对多线程并发以及线程同步,线程安全这些概念的理解。

除了使用线程安全的集合和synchronized关键字以外,我们还可以使用JUC提供的锁来实现线程同步,下一篇我们来看一下JUC中的锁。

下一篇:

java.util.concurrent.locks 包中的接口和实现类


如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

  • 0
    点赞
  • 1
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值