• 合集目录

    • Java多线程专题1: 并发与并行的基础概念

什么是多线程并发和并行?

  • 并发: Concurrency

    特指单核可以处理多任务, 这种机制主要实现于操作系统层面, 用于充分利用单CPU的性能, 时分复用同时处理多个任务
  • 并行: Parallelism

    特指使用多核处理单任务或多任务, 这种机制需要同时在操作系统层面和应用层面实现, 用于充分利用多核环境下多CPU的整体性能, 并行处理同一个任务或多个任务.

什么是线程安全问题?

线程安全问题, 就是多个线程同时访问共享的数据, 如果未合理使用volatile或synchronized, 有线程在其线程内存对共享的数据进行了写操作, 并且将其更新回了主内存, 但是其他线程不知道数据已经修改, 导致运行结果与预期不一致的问题.

If two or more threads are sharing an object, without the proper use of either volatile declarations or synchronization, updates to the shared object made by one thread may not be visible to other threads, which will lead to threadsafe problem.

什么是共享变量的内存可见性问题?

需要阅读Java内存模型(Java Memory Model)(JMM), 其中描述了Java程序中各种变量(线程共享变量)的访问规则, 以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节.

  1. 所有的变量都储存在主内存中
  2. 每个线程都有自己独立的工作内存, 里面保存了该线程使用到的变量的副本, 这些副本是主内存中该变量的一份拷贝
  3. 线程对共享变量的所有操作都必须在自己的工作内存中进行, 不能从主内存中读写
  4. 线程无法直接访问其它线程工作内存中的变量
  5. 线程间变量值的传递需要通过主内存来完成

线程1对共享变量的修改要想被线程2及时看到, 必须要经过如下两个步骤:

  1. 把工作内存1中更新过的共享变量刷新到主内存中
  2. 把主内存中最新的共享变量的值更新到工作内存2中

Imagine that the shared object is initially stored in main memory. A thread running on CPU one then reads the shared object into its CPU cache. There it makes a change to the shared object. As long as the CPU cache has not been flushed back to main memory, the changed version of the shared object is not visible to threads running on other CPUs. This way each thread may end up with its own copy of the shared object, each copy sitting in a different CPU cache.

For example, in 2-core CPU, one thread running on the first core copies the shared object into its core cache, and changes its count variable to 2. This change is not visible to other threads running on the second core, because the update to count has not been flushed back to main memory yet.

以上就是对共享变量的内存可见性的说明. Java语言层面支持的可见性实现方式有两种: synchronized 和 volatile

参考:

什么是Java中的原子性操作?

原子性操作, 指的是程序指令的最小操作单元, 这种操作一次完成, 不会被中断, 不会出现意外的结果.

回答这个问题前, 需要确认一下当前系统的位数: 对于32位系统的原子性操作, 例如i是一个int型整数, i = 1就是一个原子性操作, 这个过程只涉及一个赋值操作. 而i++就不是一个原子操作, 它相当于语句i = i + 1, 这里包括读取i, i + 1, 结果写入内存三个操作单元. 如果操作不符合原子性操作, 那么整个语句的执行就会出现混乱, 导致出现错误的结果, 从而导致线程安全问题.

因此, 在多线程中需要保证线程安全问题, 就应该保证操作的原子性. 如何保证操作的原子性? 一是加锁, 二是使用atomic类型的对象.

在32-bit JVM中的原子操作

  • all assignments of primitive types except for long and double 除了long, double以外所有的赋值操作
  • all assignments of references 所有引用的赋值
  • all operations of java.concurrent.Atomic... classes 使用java.concurrent.Atomic开头的类方法进行的操作
  • all assignments to volatile longs and doubles 添加了volatile的long和double赋值操作

为什么long型赋值不是原子操作

回答这个问题前, 也需要确认一下当前系统的位数, 这个问题对于32位系统是对的

long foo = 65465498L;

在32位JVM中会分两步写入这个long变量: 先写低32位, 再写高32位, 这个操作是可以被硬件中断的, 因此是线程不安全的. 为保证线程安全需要加上volatile

private volatile long foo;

而在64位系统中, 因为64位的引用类型其赋值是原子的, 所以对long和double的赋值也是原子的

什么是Java中的CAS(Compare And Swap)操作, AtomicLong实现原理

要实现无锁(lock-free)的非阻塞算法有多种实现方法, 其中 CAS(Compare and swap) 是一种重要的无锁实现.

在大多数处理器架构, 包括IA32, Space中采用的都是CAS指令, CAS的语义是我认为K的值应该为A, 如果是那么将K的值更新为B, 否则不修改并告诉K的值实际为多少.

CAS属于乐观锁, 当多个线程尝试使用CAS同时更新同一个变量时, 只有其中一个线程能成功更新, 其它线程会失败, 但是失败的线程并不会被挂起, 而是自旋再次尝试.

CAS有3个操作数, 内存地址K, 旧值A, 新值B. 当且仅当旧值A和内存K的值相同时, 将内存V的值修改为B, 否则什么都不做. CAS的伪代码可以表示为:

do{
备份旧值;
基于旧旧值构造新值;
} while (!CAS( 内存地址, 旧值, 新值 ))

在Java中的CAS实现

    public final long getAndIncrement() {
     // 当设置失败时, 循环再次尝试直至成功
while (true) {
long current = get();
long next = current + 1;
       //调用compareAndSet方法
if (compareAndSet(current, next))
return current;
}
} public final boolean compareAndSet(long expect, long update) {
    // valueOffSet为内存中的值, expect的值为旧的预期值, 该线程执行getAndIncrement()函数时, 通过get()获取的当时的变量值
     // update=expect+1
    // 只有valueOffset=expect时才会把变量的值设置为update
     return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}

AtomicInteger这些常用于counter, 以及生成uuid

参考

CAS底层是怎么实现的?

一般来讲, 问到unsafe就差不多了. 对于有些打算问到底的, 可以对这部分了解一下.

不同的系统架构使用了不同的实现, 但是底层都是通过汇编直接调用芯片的指令实现的. 当今的多核架构直接在硬件上支持CAS指令(一个指令完成CAS)

  • x86架构, 调用的指令为CMPXCHG, 这个指令可以实现4个字节(32bit)的CAS
  • x64为CMPXCHGQCMPXCHG8B, 这个指令可以实现8个字节(64bit)的CAS.

In the x86 (since 80486) and Itanium architectures this is implemented as the compare and exchange (CMPXCHG) instruction (on a multiprocessor the LOCK prefix must be used).

As of 2013, most multiprocessor architectures support CAS in hardware, and the compare-and-swap operation is the most popular synchronization primitive for implementing both lock-based and non-blocking concurrent data structures.

对于更早期的系统, 可以通过多条指令完成, 在指令前后通过lock/unlock, 或criticial进行保护.

参考

什么是Java指令重排序?

There are a number of cases in which accesses to program variables (object instance fields, class static fields, and array elements) may appear to execute in a different order than was specified by the program. The compiler is free to take liberties with the ordering of instructions in the name of optimization. Processors may execute instructions out of order under certain circumstances. Data may be moved between registers, processor caches, and main memory in different order than specified by the program.

For example, if a thread writes to field a and then to field b, and the value of b does not depend on the value of a, then the compiler is free to reorder these operations, and the cache is free to flush b to main memory before a. There are a number of potential sources of reordering, such as the compiler, the JIT, and the cache.

The compiler, runtime, and hardware are supposed to conspire to create the illusion of as-if-serial semantics, which means that in a single-threaded program, the program should not be able to observe the effects of reorderings. However, reorderings can come into play in incorrectly synchronized multithreaded programs, where one thread is able to observe the effects of other threads, and may be able to detect that variable accesses become visible to other threads in a different order than executed or specified in the program.

Most of the time, one thread doesn't care what the other is doing. But when it does, that's what synchronization is for.

指令重排序有两个层面

  • 在虚拟机层面

    为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响, 虚拟机会按照自己的一些规则将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行, 而写在前面的代码会后执行, 以尽可能充分地利用CPU. 假定一段代码不是a = 1, 而是a = new byte[1024x1024](分配1M空间), 那么它会运行很慢, 此时CPU是等待其执行结束, 还是先执行下面那句flag = true? 显然, 先执行flag = true可以提前使用CPU, 加快整体效率, 当然这样的前提是不会产生错误. 虽然这里有两种情况:

    • 后面的代码先于前面的代码开始执行;
    • 前面的代码先开始执行, 但当效率较慢, 于是后面的代码开始执行并先于前面的代码执行结束. 不管谁先开始, 总之后面的代码在一些情况下存在先结束的可能.
  • 在硬件层面

    CPU会将接收到的一批指令按照其规则重排序, 同样是基于CPU速度比缓存速度快的原因, 和上一点的目的类似, 只是硬件处理的话, 每次只能在接收到的有限指令范围内重排序, 而虚拟机可以在更大层面、更多指令范围内重排序. 硬件的重排序机制参见 《从JVM并发看CPU内存指令重排序(Memory Reordering)》

Java多线程专题1: 并发与并行的基础概念的更多相关文章

  1. java多线程与线程并发一:线程基础回顾

    本文章内容整理自:张孝祥_Java多线程与并发库高级应用视频教程 线程简单来讲就是程序正在做的事情.多线程即一个程序同时做多件事情,一个线程就是一件事情. 在java中创建线程的方法有两种. 方法一是 ...

  2. Java多线程专题3: Thread和ThreadLocal

    合集目录 Java多线程专题3: Thread和ThreadLocal 进程, 线程, 协程的区别 进程 Process 进程提供了执行一个程序所需要的所有资源, 一个进程的资源包括虚拟的地址空间, ...

  3. Java多线程专题5: JUC, 锁

    合集目录 Java多线程专题5: JUC, 锁 什么是可重入锁.公平锁.非公平锁.独占锁.共享锁 可重入锁 ReentrantLock A ReentrantLock is owned by the ...

  4. Java多线程专题6: Queue和List

    合集目录 Java多线程专题6: Queue和List CopyOnWriteArrayList 如何通过写时拷贝实现并发安全的 List? CopyOnWrite(COW), 是计算机程序设计领域中 ...

  5. Java多线程专题2: JMM(Java内存模型)

    合集目录 Java多线程专题2: JMM(Java内存模型) Java中Synchronized关键字的内存语义是什么? If two or more threads share an object, ...

  6. Java多线程专题4: 锁的实现基础 AQS

    合集目录 Java多线程专题4: 锁的实现基础 AQS 对 AQS(AbstractQueuedSynchronizer)的理解 Provides a framework for implementi ...

  7. Python 多线程教程:并发与并行

    转载于: https://my.oschina.net/leejun2005/blog/398826 在批评Python的讨论中,常常说起Python多线程是多么的难用.还有人对 global int ...

  8. java核心知识点学习----并发和并行的区别,进程和线程的区别,如何创建线程和线程的四种状态,什么是线程计时器

    多线程并发就像是内功,框架都像是外功,内功不足,外功也难得精要. 1.进程和线程的区别 一个程序至少有一个进程,一个进程至少有一个线程. 用工厂来比喻就是,一个工厂可以生产不同种类的产品,操作系统就是 ...

  9. Java多线程学习(一)---并发与多线程

    Java并发与多线程 摘要: 1. 并发与并行的区别,何为并发编程,并发编程的优势在哪 2. 多线程.多任务.多进程机制概述 3. 多线程.多任务.多进程机制与编程思想的关系 一.并发 1.1 并发与 ...

随机推荐

  1. Learning a Similarity Metric Discriminatively, with Application to Face Verification

    目录 概 主要内容 genuine 和 impostor 文1 文2 Chopra S, Hadsell R, Lecun Y, et al. Learning a similarity metric ...

  2. Django项目部署到Apache服务器上

    之前写了把Django部署到XAMPP上,但是有bug,翻apache日志的时候发现会无法import _ssl,然后我就怒而直接装apache2了 配置方法大约和这篇文章差不多 安装必要的包 sud ...

  3. FreeBSD 物理机下显卡的配置

    FreeBSD 已从 Linux 移植了显卡驱动,理论上,A 卡 N 卡均可在 amd64 架构上正常运行. 支持情况 对于 FreeBSD 11,支持情况同 Linux 内核 4.11: 对于 Fr ...

  4. ret2dl_resolve

    ret2dl_resolve是一种比较复杂的高级ROP技巧,利用它之前需要先了解动态链接的基本过程以及ELF文件中动态链接相关的结构. 我根据raycp师傅的文章,动手调试了一下: https://r ...

  5. 编写Java程序,前方有 3km 的道路障碍,4 辆普通车不能通过,必须等到清障车完成作业离开后,才能继续行驶。用程序来模拟这一过程的发生

    查看本章节 查看作业目录 需求说明: 前方有 3km 的道路障碍,4 辆普通车不能通过,必须等到清障车完成作业离开后,才能继续行驶.用程序来模拟这一过程的发生 实现思路: 创建清障车Wrecker类和 ...

  6. 论文翻译:2020_Acoustic Echo Cancellation Based on Recurrent Neural Network

    论文地址:https://ieeexplore.ieee.org/abstract/document/9306224 基于RNN的回声消除 摘要 本文提出了一种基于深度学习的语音分离技术的回声消除方法 ...

  7. SpringCloud创建Config模块

    1.说明 本文详细介绍Spring Cloud创建Config模块的方法, 基于已经创建好的Spring Cloud父工程, 请参考SpringCloud创建项目父工程, 创建Config模块这个子工 ...

  8. 4 - 基于ELK的ElasticSearch 7.8.x技术整理 - 高级篇( 续 ) - 更新完毕

    0.前言 这里面一些理论和前面的知识点挂钩的,所以:建议看一下另外3篇知识内容 基础篇:https://www.cnblogs.com/xiegongzi/p/15684307.html java操作 ...

  9. 双buffer实现无锁切换

    大家好,我是雨乐! 在我们的工作中,多线程编程是一件太稀松平常的事.在多线程环境下操作一个变量或者一块缓存,如果不对其操作加以限制,轻则变量值或者缓存内容不符合预期,重则会产生异常,导致进程崩溃.为了 ...

  10. IdentityServer4 综合应用实战系列 (一)登录

    这篇文章主要说登录,这里抛开IdentityServer4的各种模式,这里只说登录 我们要分别实现 4中登录方式来说明,  IdentityServer4本地登陆 . Windows账户登录(本地的电 ...