并发与多线程【四】——同步与互斥

引言

这部分内容详解线程的同步与互斥,解决线程同步与互斥的主要方式是 CASsynchronizedlock

CAS 与 ABA 问题

什么是 CAS ?

CAS 是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现都是基于 CAS 的,用于解决线程的同步与互斥。解决线程同步与互斥的主要方式除了 CAS 外,还有另外两种:synchronized 和 lock

CAS 的操作流程:当线程在读取数据时不进行加锁,在准备回写数据时,比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。这是一种乐观策略,认为并发操作并不总会发生。比较并写回的操作是通过操作系统原语实现的,保证执行过程中不会被中断。

但是,通过上述操作流程可以发现,CAS 容易引发 ABA 问题。

什么是 ABA 问题?

ABA 问题是指如果线程 T1 读取 A 值以后,发生两次写入,先由线程 T2 写回了 B,又由线程 T3 写回了 A,此时 T1 在写回比较时,值还是 A,就无法判断是否发生过修改。

ABA 问题不一定会影响结果,但是需要防范,可以通过增加额外的标志位或时间戳来解决。而 JUC 工具包中就提供了这样的类。

synchronized

synchronized 相信大家都不陌生,个别公司面试还有让面试者特意写一下这个单词的。。大概是太长而且过分依赖开发工具,相信有部分猿友能写出来但还是不太敢确信。

synchronized 是最常用的线程同步手段之一,那它是怎样保证同一时刻只有一个线程可以进入临界区的呢?

synchronized 对对象进行加锁,在 JVM 中,对象在内存中分为三块区域:对象头、实例数据和对齐填充。在对象头中保存了锁标志位和指向 monitor 对象的起始地址。如图所示,右侧就是对象对应的 monitor 对象。当 monitor 被某个线程持有后,就会处于锁定状态,如图中 owner 部分,会指向持有 monitor 对象的线程。另外 monitor 中还有两个队列,用来存放进入和等待获取锁的线程。

当 synchronized 应用在方法上时,在字节码中是通过方法的 ACC_SYNCHRONIZED 标志来实现的。

当 synchronized 应用在同步块上时,在字节码中是通过 monitorenter 和 monitorexit 来实现的。

针对 synchronized 获取锁的方式,JVM 使用了升级锁的优化方式。就是先使用偏向锁优先同一线程然后再次获取锁,如果失败,就升级为 CAS 轻量锁,如果失败,就会短暂自旋,防止线程被挂起。最后如果以上都失败,就升级为重量锁。

AQS 与 Lock

在介绍 Lock 之前,先介绍 AQS,也就是队列同步器,这是实现 Lock 的基础。下图是 AQS 的结构图:

从图中可以看出 AQS 有一个 state 标志位,值为1时表示有线程占用,其他线程需要进入同步队列等待。同步队列是一个双向链表。

当获得锁的线程需要等待某个条件时,会进入 condition 的等待队列,等待队列可以有多个。当 condition 条件满足时,线程会从等待队列重新进入同步队列进行获取锁的竞争。ReentrantLock(重入锁) 就是基于 AQS 实现的,如下图所示,ReentrantLock 内部有公平锁和非公平锁两种实现,差别就在于新来的线程是否比已经在同步队列中的等待线程更早获得锁。

和 ReentrantLock 类似,Semaphore 也是基于 AQS 的,差别在于 ReentrantLock 是独占锁,Semaphore 是共享锁。

原文地址:https://www.cnblogs.com/yadongliang/p/12325611.html

时间: 2024-10-16 23:33:21

并发与多线程【四】——同步与互斥的相关文章

转载自~浮云比翼:Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)

Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥) 介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等.但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage). 一

(转载)Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)

介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等.但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage). 一个进程可以有很多线程,每条线程并行执行不同的任务. 线程可以提高应用程序在多核环境下处理诸

同步、互斥、阻塞

目录 更新记录 1.概念 1.1 并发概念 1.2 同步和互斥 1.3 同步和异步 1.4 阻塞与非阻塞 1.5 竞态 1.6 Linux 中实现互斥的方法 2.原子操作 2.1 常用函数 2.2 案例 3.信号量 3.1 常用函数 3.2 案例 参考 更新记录 version status description date author V1.0 C Create Document 2019.1.15 John Wan status: C―― Create, A-- Add, M-- Modi

Android多线程研究(3)——线程同步和互斥及死锁

为什么会有线程同步的概念呢?为什么要同步?什么是线程同步?先看一段代码: package com.maso.test; public class ThreadTest2 implements Runnable{ private TestObj testObj = new TestObj(); public static void main(String[] args) { ThreadTest2 tt = new ThreadTest2(); Thread t1 = new Thread(tt,

多线程同步之互斥对象

多线程同步之互斥对象 作者:vpoet mail:[email protected] 在http://blog.csdn.net/u013018721/article/details/46637215一文中介绍了使用临界区 对卖票问题进行线程间同步,本文将在上文的基础上,使用互斥对象对线程进行同步. 首先看看windows API 该函数创建一个命名或者不命名的互斥对象 lpMutexAttributes [in] Pointer to a SECURITY_ATTRIBUTES structu

Linux多线程编程——线程的同步与互斥

前言:无论是多线程编程还是多进程编程,控制好不同线程或不同进程之间同步和互斥问题是非常有必要的.同步是多个进程或线程共同完成某个任务,举例说,一个缓冲区的生产者和消费者问题,当生产者生产了一个商品时,等待的消费者就获得了一个消息知道可以去取走商品了,当消费者取走一个商品后,生产者就知道可以继续生产一个商品了,这是同步问题,所谓互斥问题,是指某个共享资源在一次操作中,只能被一个线程或进程占有,其他的线程或进程不能对它进行操作,比如对一个共享内存的读写操作,当一个进程对它写的时候,另一个进程就不能对

Java学习笔记—多线程(同步容器和并发容器)

简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同步的容器,比如Vector.Hashtable以及SynchronizedList等容器,如果有多个线程调用同步容器的方法,它们将会串行执行. 可以通过查看Vector.Hashtable等同步容器的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字synchronized,但在某

线程同步(互斥锁与信号量的作用与区别)

“信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里).而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源.比如对全局变量的访问,有时要加锁,操作完了,在解锁.有的时候锁和信号量会同时使用的” 也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后

线程概念及线程的同步与互斥

线程概念:它是运行在进程内部的的一个基本执行流,多线程的控制流程可以长期并存,一个进程中的数据段和代码段都是被该进程中的多个线程共享的,若定义一个函数,每个线程都可以调用,若定义一个全局变量,每个线程都可以访问. 线程还共享进程的以下内容:1.文件描述符表 2.当前的工作目录 3.用户id(uid)和组id(gid) 4.每种信号的处理方式. 但每个线程还必须有自己的私有部分:1.线程id 2.硬件上下文(硬件寄存器的值,栈指针等) 3.自己的栈空间(运行时的临时数据都要保存在自己的栈空间上)