线程并发基础

简介:

前言

本篇博客将对线程并发的一些基础知识进行阐述,大家也可以参考楼主以前关于线程的2篇博客:《Java多线程感悟一》《Java多线程感悟二》 。


CPU、进程、线程

我们知道进程是操作系统进行资源分配的最小单位,一个进程内部可以有多个线程进行资源的共享,线程作为CPU调度的最小单位,CPU会依据某种原则(比如时间片轮转)对线程进行上下文切换,从而并发执行多个线程任务。打个比喻,CPU就像高速公路一样,每条高速公路会有并排的车道,而线程就像在路上行驶的汽车一样。我们可以通过/proc/cpuinfo来查看服务器有几个CPU,以及每个CPU支持的核心线程数,这样我们就了解了服务器有几条高速公路,以及每条高速公路有几个并排的车道。


多线程引发的思考

粗粒度的来讲,JAVA对内存的划分可以分为:堆和栈。对于多线程而言,堆就好像主内存,而栈就像是工作内存。堆是多线程共享的,线程工作时要将堆中的数据 COPY TO 工作内存才能进行工作。而线程什么时候COPY DATA TO工作内存?工作内存中的数据计算完毕又什么时候写回主内存?当多个线程之间对共享的数据进行读写,那么这一瞬间的读写是个什么顺序呢?一个线程能否看到或者什么时候才能看到另一个线程的改变呢?读和读的线程是否不需要控制并发呢?当有写线程参与时,对读线程有什么影响呢?写线程存在时,读线程是否一定要等呢?线程需要完成一连串的读写操作,是否允许其他线程插入进来呢?


多线程的基础:可见性

正如上面所言,由于存在主内存以及工作内存,每一个线程都是在自己的工作内存中进行工作的,如果线程在自己的区域埋头苦干,却不知道其他线程已经对共享的数据做出修改,这将会引发“可见性”问题。比如我们有一个配置文件,有一个写线程会对配置参数进行修改,其他很多读线程读取配置进行业务上的计算,如果写线程修改了,可是读线程依旧按照老的配置进行,也不知道读线程什么时候能“醒悟”,这多么可怕!当然JAVA已经为我们提供了轻量级的volatile来解决这个问题。(volatile不仅仅提供可见性,而且对于CPU/编译器优化带来的代码重排性也做了限制)


不仅仅可见

可见,这只不过是一瞬间的事情,更多时候,我们要的是一段时间内的操作的封闭性,即原子性。一个对象,它可以执行很多代码,但是我们希望它在执行某段代码(即临界区)时能够有一些限制,比如只允许一个线程对这个对象进行这段代码的操作,第二个线程要想操作必须等待第一个线程结束后。说的直白点,这个对象就好像一把锁,它存在3个临界区,那么这个对象在任意时刻只能处在一个临界区内!


synchronized

通过synchronized来对对象的代码进行临界区划分,从而完成可见性以及原子性的要求。synchronized是隐式的锁方式,因为加锁和解锁的过程是JAVA帮助我们来进行的,无需我们关心。正是由于这种隐式的方式,我们应重点关注的是synchronized锁住的是什么?锁住的是对象?还是锁住的是对象的临界区?锁对象的生命周期是什么?锁对象的粒度多大,是否可以优化?是否因为锁对象的粒度太大导致代码的串行,使得系统效率低下?


Lock

synchronized是JAVA最为古老的,也在不断优化的锁机制,在JAVA发展过程中也推出了新的锁机制:Lock。Lock是显式的锁,需要手动的上锁以及解锁。特别需要注意的是必须fiannly解锁,否则会出现死锁现象。第一个常用的锁是:ReentrantLock ,这是一个排他锁,和synchronized功能类似,不管线程是读,还是写,都是互斥的。第二个常用的锁是:ReentrantWriteReadLock,这是读写锁,如果读,用readLock,如果写,用writeLock,从而达到读与读的并发,读写之间的互斥。


Atomic与CAS机制

很多时候,我们仅仅希望对某个变量做一系列简单的动作,希望保证可见性以及原子性的操作,JAVA已经为我们提供了Atomic相关的类,使用最为广泛的就是AtomicInteger。这类Atomic虽然没有利用synchronized/Lock这样的锁机制,但是通过CAS达到了同样的目的。看一段AtomicInteger的代码:


wKioL1blH5ixjXhrAAAWUfXLJbQ920.png


一段死循环,先获取old值,然后尝试对比修改为新值,虽然没有临界区的锁控制,多个线程并发进行修改,但是显然compareAndSet保证了只会有一个线程能成功(相当于获得锁),这就是CAS机制。如果我们将死循环改成有限几次尝试CAS修改的话,就是自己设置了自旋的次数了。


用空间换时间:CopyOnWrite机制

在前文涉及的锁机制,都无法避免一个问题:一旦存在写线程,那么读线程势必无法并发进行。那么可否让读写并发进行呢?

CopyOnWrite机制:对于一个容器而言,多个读线程可以并发的读取该容器的内容;如果存在写线程,那么先COPY一份此容器,写线程对COPY的容器进行操作,待写线程操作完毕后,将老的容器的引用重置为COPY后的容器。这样一来,读写线程操作的容器不是同一个容器,当然可以并发进行操作。通过Copy的机制,利用空间来换取时间,需要注意的是当大量存在写线程时对内存的消耗。


并发编程集合类

  • StringBuffer 和 StringBuilder


StringBuffer的方法都打上了synchronized标签,自然是线程安全的;后来JDK走了一个极端,为我们提供了StringBuilder这样的非线程安全类,在单线程的环境下,提升了性能。


  • Hashtable  、 HashMap 、ConcurrentHashMap


Hashtable和HashMap同上面的StringBuffer/StringBuilder一样。


后来JDK出现了java.util.concurrent并发包,比如ConcurrentHashMap就通过分解锁的粒度,提高并发能力。下面我们来仔细剖析下ConcurrentHashMap的实现原理:


对于Hashtable/HashMap而言,其实里面存放的K/V并没有分层处理,对于Hashtable而言,如果锁,那么意味着锁住整个Hashtable的内容,意味着就算是读与读也得串行进行。而ConcurrentHashMap则将K/V进行划分,多个K/V成为一个segment,默认有16个segment,显然不同segment之间的读写可以并发进行,自然将锁的粒度一下子降低16倍。在每个segment内部,实际上借助于extends ReentrantLock实现读写互斥;而不同segment之间则不存在互斥关系。


  • CopyOnWriteArrayList 、 CopyOnWriteArraySet 、ArrayList 、Vector


Vector和ArrayList类似于StringBuffer/StringBuilder一样。


我们来看一段CopyOnWriteArrayList的代码,揭开CopyOnWrite机制:


wKiom1blKN6Sb8muAAAvM-vLojs480.png

add时利用排他锁达到互斥,在代码中可以看到Arrays.copyOf进行COPY,增加完元素后,利用setArray达到引用重置的目的。


再来看看获取元素的代码:

wKiom1blKZzxQH9ZAAAKwZuOXkg475.png

wKioL1blKsuh_oNWAAAI6PlkqUQ571.png

可以看到,没有锁的限制,读写并发进行操作!


本文转自zfz_linux_boy 51CTO博客,原文链接:http://blog.51cto.com/zhangfengzhe/1750569,如需转载请自行联系原作者


相关文章
|
16天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
3天前
|
安全 C++
C++多线程编程:并发与同步
C++多线程编程:并发与同步
7 0
|
5天前
|
安全 Java
Java中的并发编程:理解并发性与线程安全
Java作为一种广泛应用的编程语言,在并发编程方面具有显著的优势和特点。本文将探讨Java中的并发编程概念,重点关注并发性与线程安全,并提供一些实用的技巧和建议,帮助开发人员更好地理解和应用Java中的并发机制。
|
11天前
|
算法 安全
AtomicInteger使用非阻塞算法,实现并发控制多线程实现售票
AtomicInteger使用非阻塞算法,实现并发控制多线程实现售票
|
11天前
|
存储 安全 Java
【亮剑】`ConcurrentHashMap`是Java中线程安全的哈希表,采用锁定分离技术提高并发性能
【4月更文挑战第30天】`ConcurrentHashMap`是Java中线程安全的哈希表,采用锁定分离技术提高并发性能。数据被分割成多个Segment,每个拥有独立锁,允许多线程并发访问不同Segment。当写操作发生时,计算键的哈希值定位Segment并获取其锁;读操作通常无需锁定。内部会根据负载动态调整Segment,减少锁竞争。虽然使用不公平锁,但Java 8及以上版本提供了公平锁选项。理解其工作原理对开发高性能并发应用至关重要。
|
17天前
|
设计模式 Java 编译器
深入理解Java中的多线程并发控制
Java作为一种流行的编程语言,其多线程并发控制机制一直是开发者关注的焦点。本文旨在通过探讨Java中的多线程并发控制原理、常用同步工具及设计模式,帮助读者深入理解并有效应用多线程并发控制技术,以提高程序性能和稳定性。
|
17天前
|
监控 安全 Java
一文讲明白Java中线程与进程、并发与并行、同步与异步
一文讲明白Java中线程与进程、并发与并行、同步与异步
8 1
|
22天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。
|
23天前
|
存储 Java Python
【Python小知识】如何解决代理IP在多线程环境下的并发问题?
【Python小知识】如何解决代理IP在多线程环境下的并发问题?
|
24天前
|
安全 算法 Java
Java中的多线程并发控制与同步机制
【4月更文挑战第17天】 在现代软件开发中,Java作为一种广泛使用的编程语言,其对多线程的支持是构建高性能应用程序的关键。本文将深入探讨Java中的多线程并发控制与同步机制,包括基本的线程创建、生命周期管理,以及高级的并发工具如synchronized关键字、ReentrantLock类、并发集合和原子变量等。通过理论分析与实例演示,旨在为读者提供一个清晰的多线程并发控制与同步的实现框架,并指出在实践中如何避免常见的并发问题,如死锁、竞态条件和资源争用等。

相关实验场景

更多