Java并发编程之美——第二章 并发编程的其他知识

Java并发编程之美——第一章 Java并发编程基础


**并发处理的广泛应用是Amdahl定律代替摩尔定律成为计算机性能发展源动力的根本原因,也是人类压榨计算机运算能力的最有力武器。**

Time 2021-12-27——Hireek

以下源码使用jdk11

什么是多线程并发编程

首先要澄清并发和并行的概念,并发是指同一个时间段内多个任务同时都在执行,并且都没有执行结束,而并行是说在单位时间内多个任务同时在执行。并发任务强调在一个时间段内同时执行,而一个时间段由多个单位时间累积而成,所以说并发的多个任务在单位时间内不一定同时在执行。

为什么要并发

这阐述的还不够清楚,应该从线程的出现开始谈起。为什么要引入线程之前说过了。并发编程线程基础

  • 提高资源的利用率
  • 优先级
  • 拆分任务(互不影响,必要时相互通信)

不仅是因为计算机的运算能力强大了,还有一个很重要的原因是计算机的运算速度与它的存储和通信子系统的速度差距太大,大量的时间都花费在磁盘I/O、网络通信或者数据库访问上。如果不希望处理器在大部分时间里都处于等待其他资源的空闲状态,就必须使用一些手段去把处理器的运算能力“压榨”出来,否则就会造成很大的性能浪费,而让计算机同时处理几项任务则是最容易想到,也被证明是非常有效的“压榨”手段。

线程就可以并发,最大化利用cpu资源,通过时间片的轮转,线程切换。举个例子,点外卖。等外卖的时候我们可以继续工作,继续看电视。等外卖就相当于遇到磁盘I/O,可以切换线程去做别的任务。

Java中的线程安全

什么是线程安全

we can now define thread safety in a somewhat less circular way: a class is thread-safe when it continues to behave correctly when accessed from multiple threads. ——Java Concurrency In Practice

进一步补充解释:“当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。”

反之,即会出现线程安全问题。

一般会围绕原子性、可见性、有序性三个特性展开。

原子性

可见性

有序性

synchronized

volatile

我们先了解下JMM(屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果)。

Java内存模型规定了所有的变量都存储在主内存(Main Memory)中(此处的主内存与介绍物理硬件时提到的主内存名字一样,两者也可以类比,但物理上它仅是虚拟机内存的一部分)。每条线程还有自己的工作内存(Working Memory,可与前面讲的处理器高速缓存类比),线程的工作内存中保存了被该线程使用的变量的主内存副本[2],线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据[3]。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者的交互关系如图。

在这里插入图片描述

volatile变量对所有线程是立即可见的,对volatile变量所有的写操作都能立刻反映到其他线程之中。

写操作后会跟上lock addl$0x0,(%esp)。Lock前缀的指令在多核处理器下有两个作用

  • 将当前处理器缓存行的数据写回到系统内存。
  • 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。

这也是volatie实现可见性以及内存屏障的原因。具体这个lock前缀指令,目前一般是锁定缓存,通过缓存一致性协议实现。

JMM中也有对volatile变量定义的特殊规则的定义。

CAS(Compare-and-Swap)

基于冲突检测的乐观并发策略的一种。依赖硬件指令集。在IA64、x86指令集中有用cmpxchg指令完成的CAS功能,在SPARC-TSO中也有用casa指令实现的,而在ARM和PowerPC架构下,则需要使用一对ldrex/strex指令来完成LL/SC的功能。

CAS指令需要有三个操作数,分别是内存位置(在Java中可以简单地理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和准备设置的新值(用B表示)。CAS指令执行时,当且仅当V符合A时,处理器才会用B更新V的值,否则它就不执行更新。但是,不管是否更新了V的值,都会返回V的旧值,上述的处理过程是一个原子操作,执行期间不会被其他线程中断。

在JDK5之后,Java类库中才开始使用CAS操作,该操作由sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等几个方法包装提供。JDK9之后,Java类库才在VarHandle类里开放了面向用户程序使用的CAS操作。

还有个经典的ABA问题。

Unsafe类

Unsafe故名思义,不安全的类,注释的第一句,A collection of methods for performing low-level, unsafe operations。提供了硬件级别的原子性操作。具体方法有很多,包括上面的CAS。

官方不推荐开发者直接使用,在JDK10时才将Unsafe的部分功能通过VarHandle开放给外部使用。

Jdk8,rt.jar下sun.misc.Unsafe。

jdk9,sun.misc.Unsafe被移动到com.unsupported模块下;又新增了jdk.internal.misc.Unsafe。

如何使用?

jdk.internal.misc.Unsafe笔者做了下尝试仍报错,之后会再尝试一下。。。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

于是就针对sun.misc.Unsafe,尽管该类是一个工具类,通过单例模式,我们似乎可以直接调用,实际会检查类加载器类型。

public static Unsafe getUnsafe() {
    Class<?> caller = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(caller.getClassLoader())) // 判断是不是根类加载器或者平台类加载器,
        throw new SecurityException("Unsafe");
    return theUnsafe;
}

/**
     * Returns true if the given class loader is the bootstrap class loader
     * or the platform class loader.
     */
public static boolean isSystemDomainLoader(ClassLoader loader) {
  return loader == null || loader == ClassLoader.getPlatformClassLoader();
}

但反射可以直接调用。

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * UnsafeTest demo
 *
 * @author Hireek
 * @date 2021/12/27 22:49
 */
public class UnsafeTest {
    static final Unsafe unsafe;

    static final long stateOffest;

    private volatile long state = 0;

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            stateOffest = unsafe.objectFieldOffset(UnsafeTest.class.getDeclaredField("state"));
        } catch (Exception ex) {
            System.out.println(ex.getLocalizedMessage());
            throw new Error(ex);
        }
    }

    public static void main(String[] args) {
        UnsafeTest test = new UnsafeTest();
        Boolean success = unsafe.compareAndSwapInt(test, stateOffest, 0, 1);
        System.out.println(success);
    }
}
/** run result
 true
*/

伪共享问题(False Sharing)

伪共享是处理并发底层细节时一种经常需要考虑的问题,现代中央处理器的缓存系统中是以缓存行(Cache Line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰好共享同一个缓存行(局部性原理),就会彼此影响(写回、无效化或者同步)而导致性能降低。

如何避免伪共享问题

JDK8提供了一个sun.misc.Contended注解,用来解决伪共享问题。

锁的概述

悲观锁与乐观锁

乐观即尽可能的无锁化,加锁即悲观。

独占锁与共享锁

独占锁即排他锁,共享锁故名思义是锁是共享的,即多个线程能占有锁。

公平锁与非公平锁

类比排队,是否可以插队。

可重入锁

当一个线程要获取一个被其他线程持有的独占锁时,该线程会被阻塞,那么当一个线程再次获取它自己己经获取的锁时是否会被阻塞呢?如果不被阻塞,那么我们说该锁是可重入的,也就是只要该线程获取了该锁,那么可以无限次数(在高级篇中我们将知道,严格来说是有限次数)地进入被该锁锁住的代码。

愿与君共勉

未完待遇…

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值