金沙贵宾会官网|金沙贵宾会登录-官网

【A】金沙贵宾会官网超高的返奖率为娱乐者提供资金保障,所以金沙贵宾会登录官网更加的方便了你的娱乐,申请88元彩金,因为在当中不仅仅只有游戏。

内存可见性,8源码剖析

日期:2019-10-05编辑作者:网络软件

声明,本文用的是jdk1.8

1.Java JUC简介

在java5.0之后提供了一个java.util.concurrent包(简称JUC),此包中增加了很多在并发编程中常用的工具类,用于定义类似于线程的自定义系统,包括线程池,异步IO等等。

前面章节回顾:

2.volatile关键字-内存可见性

内存可见性问题:当多个线程同时操作共享数据时,彼此不可见。图解如下:

图片 1

image

当主存有一个flag数据时,线程1负责修改flag的值,Main线程负责读取flag的值,线程1修改值之前,先将flag= false取到自己手中,然后再修改其值,当线程1还没将修改好的值放进主存中,Main线程已经从主存读到了flag= false,然后main线程就执行自己的逻辑,这就是内存可见性的问题,就是说Main线程不知道其他线程执行结束没有。
代码说明如下:

public class TerstVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            if(td.isFlag()){
                System.out.println("-------------------");
                break;
            }

        }
    }

}
class ThreadDemo implements Runnable{
    private boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("flaf="+isFlag());

    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

解决办法之一:使用synchronize关键字加锁

public class TerstVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            synchronized(td){
                if(td.isFlag()){
                    System.out.println("-------------------");
                    break;
                }
            }
        }
    }
}

但是加锁会导致效率变低,解决办法之二使用volatile关键字,如下图:

图片 2

image

使用了volatile关键字就是线程直接操作主存中的数据。在保证数据的同步性,效率也得以提升。volatile相较于synchronize是一种较为轻量的同步策略。

private volatile boolean flag = false;

注意:

  1. volatile不具备互斥性,互斥性就是说如果多个线程共同抢一把锁的时候,如果其中一个线程抢到锁,那么其他线程就只能等待,volatile就是多个线程共同在主存中完成。
  2. volatile不能变量的原子性,

有关原子性的问题,我们看一个i++的实例

public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}
class AtomicDemo implements Runnable{
    private int serialNumber = 0;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+getSerialNumber());
    }
    public int getSerialNumber(){
        return serialNumber++;
    }

}

结果为:

Thread-1:0
Thread-0:1
Thread-8:2
Thread-9:5
Thread-6:7
Thread-5:3
Thread-3:3
Thread-2:4
Thread-4:8
Thread-7:6

说明:

  1. i++在程序底层其实是三步走,读-改-写,本来是不可分割的操作,但是在多个线程访问时,一个线程还没写成功,另外一个线程也开始写了,这也就是线程安全问题。
  2. 使用volatile也不能解决这个问题,因为volatile是让多个线程在内存中共同操作共享数据,i++的操作也会被分割成三步。
  3. 解决办法就是使用原子变量。
  • Collection总览
  • List集合就这么简单
  • Map集合、散列表、红黑树介绍
  • HashMap就是这么简单
  • LinkedHashMap就这么简单
  • TreeMap就这么简单

3.原子变量-CAS算法

jdk1.5之后java.concurrent.atomic包下提供了常用的原子变量,原子变量使用volatile保证内存可见性,使用CAS算法保证数据的原子性。CAS算法是硬件对于并发操作共享数据的支持,CAS包含了三个操作数,分别为:内存值V,预估值A,更新值B,当且仅当V==A,V=B,否则,不做任何操作。

本篇主要讲解ConCurrentHashMap~

java.concurrent.atomic包下的类结构

图片 3

image

看这篇文章之前最好是有点数据结构的基础:

AtomicInteger类的方法如下,其他类的参考API

  • Java实现单向链表
  • 栈和队列就是这么简单
  • 二叉树就这么简单
1. int addAndGet(int delta) ;以原子方式将给定值与当前值相加。

当然了,如果讲得有错的地方还请大家多多包涵并不吝在评论去指正~

2. boolean compareAndSet(int expect, int update) ;如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

ConCurrentHashMap在初学的时候反正我是没有接触过的,不知道你们接触过了没有~

3. int decrementAndGet() ;以原子方式将当前值减 1。

这个类听得也挺少的,在集合中是比较复杂的一个类了,它涉及到了一些多线程的知识点。

4. double doubleValue() ; 以 double 形式返回指定的数值。

不了解或忘记多线程知识点的同学也不要怕,哪儿用到了多线程的知识点,我都会简单介绍一下,并给出对应的资料去阅读的~

5. float floatValue() ;以 float 形式返回指定的数值。

好了,我们就来开始吧~

6. int get() ;获取当前值。

ConCurrentHashMap的底层是:散列表+红黑树,与HashMap是一样的。

7. int getAndAdd(int delta) ;以原子方式将给定值与当前值相加。

图片 4image

8. int getAndDecrement() ;以原子方式将当前值减 1。

从前面的章节我们也可以发现:最快了解一下类是干嘛的,我们看源码的顶部注释就可以了!

9. int getAndIncrement() ;以原子方式将当前值加 1。

我简单翻译了一下顶部的注释(我英文水平渣,如果有错的地方请多多包涵~欢迎在评论区下指正)

10. int getAndSet(int newValue) ; 以原子方式设置为给定值,并返回旧值。

图片 52018-04-14_092943.png

11. int incrementAndGet(); 以原子方式将当前值加 1。

根据上面注释我们可以简单总结:

12. int intValue() ;以 int 形式返回指定的数值。
  • JDK1.8底层是散列表+红黑树
  • ConCurrentHashMap支持高并发的访问和更新,它是线程安全
  • 检索操作不用加锁,get方法是非阻塞的
  • key和value都不允许为null
13. void lazySet(int newValue) ;最后设置为给定值。

上面指明的是JDK1.8底层是:散列表+红黑树,也就意味着,JDK1.7的底层跟JDK1.8是不同的~

14. long longValue() ;以 long 形式返回指定的数值。

JDK1.7的底层是:segments+HashEntry数组:

15. void set(int newValue) ;设置为给定值。

图片 6Xhimkj8.png

16. String toString() ;返回当前值的字符串表示形式。

图来源:

17. boolean weakCompareAndSet(int expect, int update) ;如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。

使用原子变量改写上述程序如下:

class AtomicDemo implements Runnable{
    private AtomicInteger serialNumber = new AtomicInteger();
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+getSerialNumber());
    }
    public int getSerialNumber(){
        return serialNumber.getAndIncrement();
    }
}
  • Segment继承了ReentrantLock,每个片段都有了一个锁,叫做“锁分段

4.ConcurrentHashMap锁分段机制

ConcurrentHashMap同步容器类是java5增加的一个线程安全的哈希表,对于多线程的操作,介于HashMap和HashTable之间,内部采用锁分段的机制替代HashTable的独占锁,进而提高了性能。包结构如下:

图片 7

image

当期望许多线程访问一个给定Collection时,ConcurrentHashMap通常优于HashMap,当期望的读数和遍历远远大于列表的更新读数时CopyOnWriteArrayList优于ArrayList。
ConcurrentHashMap锁分段机制图解如下:

图片 8

image

CopyOnWriteArrayList实例如下:如果我们不使用 CopyOnWriteArrayList,使用 Collections.synchronizedList运行下面程序就会报并发修改异常。

public class TestCopyOnWriteArrayList {
    public static void main(String[] args) {
        HelloThread ht = new HelloThread();
        for (int i = 0; i < 10; i++) {
            new Thread(ht).start();
        }
    }
}
class HelloThread implements Runnable{
    private static List<String> list = Collections.synchronizedList(new ArrayList<String>());
    static{
        list.add("AA");
        list.add("BB");
        list.add("CC");
    }
    @Override
    public void run() {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
            list.add("DD");
        }
    }
}

异常如下:

java.util.ConcurrentModificationException

使用 CopyOnWriteArrayList修改代码如下所示:

private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();

说明:CopyOnWriteArrayList:写入并复制,就是每次写入的时候都会复制,如果添加操作多时,效率低,每次都会进行复制,开销很大,并发迭代操作多时可以选择。

大概了解一下即可~

5.CountDownLatch闭锁

CountDownlatch闭锁,就是在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行。
实例如下:如果不使用CountDownlatch闭锁就无法计算出执行10子线程所需要的时间

public class TestCountDownlatch {
    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(5);
        LatchDemo ld = new LatchDemo(latch);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            new Thread(ld).start();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗费时间为:"+(end-start));
    }

}
class LatchDemo implements Runnable{
    private CountDownLatch latch;
    public LatchDemo(CountDownLatch latch) {
        this.latch = latch;
    }
    @Override
    public void run() {
        for (int i = 0; i < 50000; i++) {
            if(i % 2 ==0){
                System.out.println(i);
            }
        }
    }
}

使用CountDownlatch闭锁的实例如下,可以计算出所有子线程执行需要的时间。

public class TestCountDownlatch {
    public static void main(String[] args) {
        //5 和子线程数目一致
        final CountDownLatch latch = new CountDownLatch(5);
        LatchDemo ld = new LatchDemo(latch);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            new Thread(ld).start();
        }
        //等待所有的子线程全部执行完
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗费时间为:"+(end-start));
    }

}
class LatchDemo implements Runnable{
    private CountDownLatch latch;
    public LatchDemo(CountDownLatch latch) {
        this.latch = latch;
    }
    @Override
    public void run() {
        synchronized(this){
            try {
                for (int i = 0; i < 50000; i++) {
                    if(i % 2 ==0){
                        System.out.println(i);
                    }
                }
            }finally{
                latch.countDown();// 减一操作
            }
        }

    }
}
  • Hashtable是在每个方法上都加上了Synchronized完成同步,效率低下。
  • ConcurrentHashMap通过在部分加锁利用CAS算法来实现同步。

6.实现Callable接口

在看ConCurrentHashMap源码之前,我们来简单讲讲CAS算法和volatile关键字

创建执行线程的方式:四种方式

  1. 定义Thread子类,并重写run方法,创建Thread子类实例,也就是线程对象,再调用线程对象的start方法。 new Thread子类对象.start();
  2. A类实现Runnable接口,重写run方法,创建Runnable实现类的实例A a = new A(); new Thread(a).start();
  3. 实现Callable接口,方式如下:
public class TestCallable {
    public static void main(String[] args) {
        CallableThreadDemo ctd = new CallableThreadDemo();
        //执行Callable方法,需要FutureTask实现类的支持,用于接收运算返回的结果
        //FuterTask是Future接口
        FutureTask<Integer> result = new FutureTask<Integer>(ctd);
        new Thread(result).start();
        //接收线程的返回值,当子线程执行结束result.get()才执行,和闭锁类似
        //所以FutureTask也可以用于闭锁
        try {
            Integer sum = result.get();
            System.out.println(sum);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } 
    }
}
class CallableThreadDemo implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            sum +=i;
        }
        return sum;
    }
}

说明:实现Callable接口和实现Runnable接口的不同在于实现Callable接口有返回值和异常抛出。

CAS(比较与交换,Compare and swap) 是一种有名的无锁算法

CAS有3个操作数

  • 内存值V
  • 旧的预期值A
  • 要修改的新值B

当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做

  • 当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值(A和内存值V相同时,将内存值V修改为B),而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试

看了上面的描述应该就很容易理解了,先比较是否相等,如果相等则替换

接下来我们看看volatile关键字,在初学的时候也很少使用到volatile这个关键字。反正我没用到,而又经常在看Java相关面试题的时候看到它,觉得是一个挺神秘又很难的一个关键字。其实不然,还是挺容易理解的~

volatile经典总结:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性

我们将其拆开来解释一下:

  • 保证该变量对所有线程的可见性
    • 在多线程的环境下:当这个变量修改时,所有的线程都会知道该变量被修改了,也就是所谓的“可见性”
  • 不保证原子性
    • 修改变量实质上是在JVM中分了好几步,而在这几步内,它是不安全的

如果没看懂或者想要深入了解其原理和可参考下列博文:

域对象有这么几个:

图片 9rjvoGhc.png

我们来简单看一下他们是什么东东:

图片 10image

初次阅读完之后,有的属性我也不太清楚它是干什么的,在继续阅读之后可能就明朗了~

ConcurrentHashMap的构造方法有5个:

图片 112tXkMrh.png

具体的实现是这样子的:

图片 12image

可以发现,在构造方法中有几处都调用了tableSizeFor(),我们来看一下他是干什么的:

点进去之后发现,啊,原来我看过这个方法,在HashMap的时候.....

图片 13image

它就是用来获取大于参数且最接近2的整次幂的数...

赋值给sizeCtl属性也就说明了:这是下次扩容的大小~

终于来到了最核心的方法之一:put方法啦~~~~

我们先来整体看一下put方法干了什么事:

图片 14image

接下来,我们来看看初始化散列表的时候干了什么事:initTable()

图片 15NxMQu59.png

  • 只让一个线程对散列表进行初始化

从顶部注释我们可以读到,get方法是不用加锁的,是非阻塞的。

我们可以发现,Node节点是重写的,设置了volatile关键字修饰,致使它每次获取的都是最新设置的值

图片 16WxQJ58R.png图片 17tBI4n51.png

上面简单介绍了ConcurrentHashMap的核心知识,还有很多知识点都没有提及到,作者的水平也不能将其弄懂~~有兴趣进入的同学可到下面的链接继续学习。

下面我来简单总结一下ConcurrentHashMap的核心要点:

  • 底层结构是散列表+红黑树,这一点和HashMap是一样的。
  • Hashtable是将所有的方法进行同步,效率低下。而ConcurrentHashMap作为一个高并发的容器,它是通过部分锁定+CAS算法来进行实现线程安全的。CAS算法也可以认为是乐观锁的一种~
  • 在高并发环境下,统计数据(计算size...等等)其实是无意义的,因为在下一时刻size值就变化了。
  • get方法是非阻塞,无锁的。重写Node类,通过volatile修饰next来实现每次获取都是最新设置的值
  • ConcurrentHashMap的key和Value都不能为null

参考资料:

明天要是无意外的话,可能会写Set集合,敬请期待哦~~~~

文章的目录导航

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y。为了大家方便,刚新建了一下qq群:742919422,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友

本文由金沙贵宾会官网发布于网络软件,转载请注明出处:内存可见性,8源码剖析

关键词:

就这一篇,简单易懂详细

此地配置了二个 Spring MVC内置的一个视图解析器,该深入分析器是比照着一种约定:会 在视图名上丰盛前缀和后缀,...

详细>>

联合代码之

项目初始情况 git最常用方法之一,合并代码,大部分时候我们都是使用merge命令。其实还有rebase命令,既然都是合并...

详细>>

一波神奇的Python语句,Python奇技淫巧

当公布python第三方package时,并不指望代码中有着的函数或然class能够被外表import,在 __init__.py 中添加 __all__ 质量,该...

详细>>

11个原生JavaScript技艺分享,JavaScript的拾二个原生

原文地址: 1、实现字符串长度截取 JavaScript的10个原生技巧分享   本文给大家分享的是个人总结的10条非常常用的原...

详细>>