汉邦问答 / 问答 / 问答详情

java volatile变量为什么不能保证原子性

2023-06-10 08:31:56
TAG: 变量
小菜G的建站之路

被volatile修饰的变量保证的是可见性,不是原子性。这是两个不同的概念。

可见性是指不论在哪个线程中看,同一个对象同一时刻的值总是一样的,不会出现不一致的情况。

原子性是指一个操作要么完成,要么没有完成,不会出现完成了一半的情况。

Java多线程之Atomic:原子变量与原子类

   一 何谓Atomic?   Atomic一词跟原子有点关系 后者曾被人认为是最小物质的单位 计算机中的Atomic是指不能分割成若干部分的意思 如果一段代码被认为是Atomic 则表示这段代码在执行过程中 是不能被中断的 通常来说 原子指令由硬件提供 供软件来实现原子方法(某个线程进入该方法后 就不会被中断 直到其执行完成)   在x 平台上 CPU提供了在指令执行期间对总线加锁的手段 CPU芯片上有一条引线#HLOCK pin 如果汇编语言的程序中在一条指令前面加上前缀 LOCK 经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低 持续到这条指令结束时放开 从而把总线锁住 这样同一总线上别的CPU就暂时不能通过总线访问内存了 保证了这条指令在多处理器环境中的原子性    二 ncurrent中的原子变量   无论是直接的还是间接的 几乎 ncurrent 包中的所有类都使用原子变量 而不使用同步 类似 ConcurrentLinkedQueue 的类也使用原子变量直接实现无等待算法 而类似 ConcurrentHashMap 的类使用 ReentrantLock 在需要时进行锁定 然后 ReentrantLock 使用原子变量来维护等待锁定的线程队列   如果没有 JDK 中的 JVM 改进 将无法构造这些类 这些改进暴露了(向类库 而不是用户类)接口来访问硬件级的同步原语 然后 ncurrent 中的原子变量类和其他类向用户类公开这些功能   ncurrent atomic的原子类   这个包里面提供了一组原子类 其基本的特性就是在多线程环境下 当有多个线程同时执行这些类的实例包含的方法时 具有排他性 即当某个线程进入方法 执行其中的指令时 不会被其他线程打断 而别的线程就像自旋锁一样 一直等到该方法执行完成 才由JVM从等待队列中选择一个另一个线程进入 这只是一种逻辑上的理解 实际上是借助硬件的相关指令来实现的 不会阻塞线程(或者说只是在硬件级别上阻塞了) 其中的类可以分成 组   AtomicBoolean AtomicInteger AtomicLong AtomicReference   AtomicIntegerArray AtomicLongArray   AtomicLongFieldUpdater AtomicIntegerFieldUpdater AtomicReferenceFieldUpdater   AtomicMarkableReference AtomicStampedReference AtomicReferenceArray   其中AtomicBoolean AtomicInteger AtomicLong AtomicReference是类似的   首先AtomicBoolean AtomicInteger AtomicLong AtomicReference内部api是类似的 举个AtomicReference的例子   使用AtomicReference创建线程安全的堆栈   Java代码   public class LinkedStack<T> {   private AtomicReference<Node<T》 stacks = new AtomicReference<Node<T》()   public T push(T e) {   Node<T> oldNode newNode;   while (true) { //这里的处理非常的特别 也是必须如此的   oldNode = stacks get()   newNode = new Node<T>(e oldNode)   if (pareAndSet(oldNode newNode)) {   return e;   }   }   }   public T pop() {   Node<T> oldNode newNode;   while (true) {   oldNode = stacks get()   newNode = oldNode next;   if (pareAndSet(oldNode newNode)) {   return oldNode object;   }   }   }   private static final class Node<T> {   private T object;   private Node<T> next;   private Node(T object Node<T> next) {   this object = object;   this next = next;   }   }   }   然后关注字段的原子更新   AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T V>是基于反射的原子更新字段的值   相应的API也是非常简   单的 但是也是有一些约束的   ( )字段必须是volatile类型的!volatile到底是个什么东西 请查看   ( )字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致 也就是说调用者能够直接操作对象字段 那么就可以反射进行原子操作 但是对于父类的字段 子类是不能直接操作的 尽管子类可以访问父类的字段   ( )只能是实例变量 不能是类变量 也就是说不能加static关键字   ( )只能是可修改变量 不能使final变量 因为final的语义就是不可修改 实际上final的语义和volatile是有冲突的 这两个关键字不能同时存在   ( )对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段 不能修改其包装类型(Integer/Long) 如果要修改包装类型就需要使用AtomicReferenceFieldUpdater   在下面的例子中描述了操作的方法   [java]   import ncurrent atomic AtomicIntegerFieldUpdater;   public class AtomicIntegerFieldUpdaterDemo {   class DemoData{   public volatile int value = ;   volatile int value = ;   protected volatile int value = ;   private volatile int value = ;   }   AtomicIntegerFieldUpdater<DemoData> getUpdater(String fieldName) {   return AtomicIntegerFieldUpdater newUpdater(DemoData class fieldName)   }   void doit() {   DemoData data = new DemoData()   System out println( ==> +getUpdater( value ) getAndSet(data ))   System out println( ==> +getUpdater( value ) incrementAndGet(data))   System out println( ==> +getUpdater( value ) decrementAndGet(data))   System out println( true ==> +getUpdater( value ) pareAndSet(data ))   }   public static void main(String[] args) {   AtomicIntegerFieldUpdaterDemo demo = new AtomicIntegerFieldUpdaterDemo()   demo doit()   }   }   在上面的例子中DemoData的字段value /value 对于AtomicIntegerFieldUpdaterDemo类是不可见的 因此通过反射是不能直接修改其值的   AtomicMarkableReference类描述的一个<Object Boolean>的对 可以原子的修改Object或者Boolean的值 这种数据结构在一些缓存或者状态描述中比较有用 这种结构在单个或者同时修改Object/Boolean的时候能够有效的提高吞吐量   AtomicStampedReference类维护带有整数 标志 的对象引用 可以用原子方式对其进行更新 对比AtomicMarkableReference类的<Object Boolean> AtomicStampedReference维护的是一种类似<Object int>的数据结构 其实就是对对象(引用)的一个并发计数 但是与AtomicInteger不同的是 此数据结构可以携带一个对象引用(Object) 并且能够对此对象和计数同时进行原子操作   在本文结尾会提到 ABA问题 而AtomicMarkableReference/AtomicStampedReference在解决 ABA问题 上很有用    三 Atomic类的作用   使得让对单一数据的操作 实现了原子化   使用Atomic类构建复杂的 无需阻塞的代码   访问对 个或 个以上的atomic变量(或者对单个atomic变量进行 次或 次以上的操作)通常认为是需要同步的 以达到让这些操作能被作为一个原子单元    无锁定且无等待算法   基于 CAS (pare and swap)的并发算法称为 无锁定算法 因为线程不必再等待锁定(有时称为互斥或关键部分 这取决于线程平台的术语) 无论 CAS 操作成功还是失败 在任何一种情况中 它都在可预知的时间内完成 如果 CAS 失败 调用者可以重试 CAS 操作或采取其他适合的操作   如果每个线程在其他线程任意延迟(或甚至失败)时都将持续进行操作 就可以说该算法是 无等待的 与此形成对比的是 无锁定算法要求仅 某个线程总是执行操作 (无等待的另一种定义是保证每个线程在其有限的步骤中正确计算自己的操作 而不管其他线程的操作 计时 交叉或速度 这一限制可以是系统中线程数的函数 例如 如果有 个线程 每个线程都执行一次CasCounter increment() 操作 最坏的情况下 每个线程将必须重试最多九次 才能完成增加 )   再过去的 年里 人们已经对无等待且无锁定算法(也称为 无阻塞算法)进行了大量研究 许多人通用数据结构已经发现了无阻塞算法 无阻塞算法被广泛用于操作系统和 JVM 级别 进行诸如线程和进程调度等任务 虽然它们的实现比较复杂 但相对于基于锁定的备选算法 它们有许多优点 可以避免优先级倒置和死锁等危险 竞争比较便宜 协调发生在更细的粒度级别 允许更高程度的并行机制等等    常见的   非阻塞的计数器Counter   非阻塞堆栈ConcurrentStack lishixinzhi/Article/program/Java/gj/201311/27474
2023-06-09 22:43:111

临界资源可以用原子变量表示吗

可以用原子表示。原子变量是多线程编程中用于实现同步的一种机制,可以保证多个线程对该变量的访问是原子性的,即线程在访问该变量时不会被其他线程打断,从而避免了线程安全问题。因此,使用原子变量可以保证临界资源的互斥访问,从而实现线程安全。
2023-06-09 22:43:181

linux 2.6内核有关原子变量的定义问题

原子意味着不可分割,所谓原子操作就是对变量的读写不能被打断的操作。举个简单点儿的例子:1. 假如在一个i386体系架构上;2. 如果有一个进程要将一个int型的变量改成0x12345678;3. 另一个进程也希望把这同一个变量改成0x87654321。4. 如果这个变量的地址没有4字节对齐,那么cpu要改写它的值的话需要两次总线操作。那么(假设下面的场景,即下列事件先后发生):1. 第一个进程刚把高字节写入(x=0x1234xxxx)内存(xxxx表示不确定);2. 第二进程就抢占了第一个进程的运行,把第一个进程改了一半的变量改成0x87654321。(当然他也需要两次总线操作,但我们假设他的优先级比第一个进程高)3. 第二个进程结束运行后,第一个进程又得到了调度,它并不知道自己对变量的操作被另一个进程打断过,所以他会继续更改变量的低字节。所以,最后这个变量的值就是0x87655678,显然这是两个进程都不想要得到的结果。通过上面的分析你应该知道问题的关键就在于对存储空间的访问被打断了造成的。所以在内核中定义了一系列的原子操作来保证对变量的操作是“原子”的。这种互斥不是高级语言能实现的,必须用汇编,而且依赖于体系架构。对i386来说就是在读写变量的时候先把总线锁住,你可以仔细看看ATOMIC_INIT这个宏的定义。
2023-06-09 22:43:261

在ConcurrentHashMap中为什么不使用原子变量存储size?

ConcurrentHashMap的size方法是弱一致性的,size大小可以认为是数量的一个估计。那为什么不使用原子变量保证强一致性呢?有以下两点原因: 最后,如果程序中需要依靠准确的数量,那可以封装一个currentHashMap,在外部控制数量和进行相关的判断。
2023-06-09 22:43:331

关系演算是用什么来表达查询要求的方式

谓词知识拓展关系演算是以数理逻辑中的谓词演算为基础的。以谓词演算为基础的查询语言称为关系演算语言。用谓词演算作为数据库查询语言的思想最早见于Kuhns的论文。把谓词演算用于关系数据库语(即关系演算的概念)是出E.F.Codd提出来的。语言ALPHA:元组关系演算以元组变量作为谓词变元的基本对象。典型的元组关系演算语言是E.F.Codd提出的ALPHA语言,但这一语言并没有实际实现。现在关系库管理系统INGRES所用的QUEL语言是参会照ALPHA语言研制的,与ALPHA十分相似ALPHA语言语句的基本格式是:操作语句 工作空间名(表达式):操作条件基本格式中:操作语句主要有GET、PUT、HOLD、UPDATE、DELETE和DROP六条语句;工作空间是用户与系统的通信区,它可以用一个字母表示,通常用W表示;表达式用于指定语句的操作对象,它可以是关系名和属性名,一条语句可以同时操作多个关系或多个属性;操作条件是一个逻辑表达式,它用于将操作结果限定在满足条件的元组中,操作条件可以为空;呆以在基本格式的基础上加上排序要求,定额要求等。语言QBE:域关系演算是另一种形式的关系演算。域关系演算以元组变量的分量(即域变量)作为谓词变元的基本对象。QBE是一个很特色的域关系演算语言,穹由M.MZloof于1975年提出,关于1978年在IBM370上得以实现。QBE是Query By Example(即通过例子进行查询)的简称,它是一种关系语言,同时也指使用此语言的关系数据库时系统,QBE具有以下特点。(1)QBE是交互式语言操作方式非常特别。它是一种高度非过程化的基于屏幕表格的查询语言,用户通过终端屏幕编辑旗号斑蝥 以真写表格的方式构造查询要求,而查询结果也是以表格形式显示,因此具有直观和可对话的特点。(2)QBE是表格语言QBE是在显示屏幕的表格上进行查询,所以具有"二维语法"的特点,而其他语言的语法则是线形的。(3)QBE是基于例子的查询语言QBE的意思就是通过例子查询,它的操作方工对用户来讲容易掌握,特别为缺乏计算机和数学知识的非计算机专业人员乐于接受QBE中用示例元素来表示查询结果可能的例子,示例元素实质上就是域变量。元组关系:在关系运算中,用谓词公式来表达查询要求的方式称为关系演算。元组关系演算是一元组变量作为谓词变元的基本对象。元组关系演算语言释义谓词公式来定义查询要求的。在谓词公式中存在客体变元,这里称为元组变量。原子变量是一个变量,其变化范围为某一个命名的关系。
2023-06-09 22:43:421

如何保护静态变量

保护静态变量的方法有使用互斥锁,使用原子变量,使用局部静态变量。1、使用互斥锁:在访问静态变量前加锁,执行完后释放锁,确保同一时间只有一个线程可以访问该变量。2、使用原子变量:原子变量可以保证自增、自减等操作的原子性,从而避免数据竞争。3、使用局部静态变量:将静态变量放在函数内部作为局部静态变量,可以避免多个线程同时访问和修改该变量的问题。
2023-06-09 22:44:021

linux内核同步问题

Linux内核设计与实现 十、内核同步方法 手把手教Linux驱动5-自旋锁、信号量、互斥体概述 == 基础概念: == 并发 :多个执行单元同时进行或多个执行单元微观串行执行,宏观并行执行 竞态 :并发的执行单元对共享资源(硬件资源和软件上的全局变量)的访问而导致的竟态状态。 临界资源 :多个进程访问的资源 临界区 :多个进程访问的代码段 == 并发场合: == 1、单CPU之间进程间的并发 :时间片轮转,调度进程。 A进程访问打印机,时间片用完,OS调度B进程访问打印机。 2、单cpu上进程和中断之间并发 :CPU必须停止当前进程的执行中断; 3、多cpu之间 4、单CPU上中断之间的并发 == 使用偏向: == ==信号量用于进程之间的同步,进程在信号量保护的临界区代码里面是可以睡眠的(需要进行进程调度),这是与自旋锁最大的区别。== 信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。它负责协调各个进程,以保证他们能够正确、合理的使用公共资源。它和spin lock最大的不同之处就是:无法获取信号量的进程可以睡眠,因此会导致系统调度。 1、==用于进程与进程之间的同步== 2、==允许多个进程进入临界区代码执行,临界区代码允许睡眠;== 3、信号量本质是==基于调度器的==,在UP和SMP下没有区别;进程获取不到信号量将陷入休眠,并让出CPU; 4、不支持进程和中断之间的同步 5、==进程调度也是会消耗系统资源的,如果一个int型共享变量就需要使用信号量,将极大的浪费系统资源== 6、信号量可以用于多个线程,用于资源的计数(有多种状态) ==信号量加锁以及解锁过程:== sema_init(&sp->dead_sem, 0); / 初始化 / down(&sema); 临界区代码 up(&sema); ==信号量定义:== ==信号量初始化:== ==dowm函数实现:== ==up函数实现:== 信号量一般可以用来标记可用资源的个数。 举2个生活中的例子: ==dowm函数实现原理解析:== (1)down 判断sem->count是否 > 0,大于0则说明系统资源够用,分配一个给该进程,否则进入__down(sem); (2)__down 调用__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);其中TASK_UNINTERRUPTIBLE=2代表进入睡眠,且不可以打断;MAX_SCHEDULE_TIMEOUT休眠最长LONG_MAX时间; (3)list_add_tail(&waiter.list, &sem->wait_list); 把当前进程加入到sem->wait_list中; (3)先解锁后加锁; 进入__down_common前已经加锁了,先把解锁,调用schedule_timeout(timeout),当waiter.up=1后跳出for循环;退出函数之前再加锁; Linux内核ARM构架中原子变量的底层实现研究 rk3288 原子操作和原子位操作 原子变量适用于只共享一个int型变量; 1、原子操作是指不被打断的操作,即它是最小的执行单位。 2、最简单的原子操作就是一条条的汇编指令(不包括一些伪指令,伪指令会被汇编器解释成多条汇编指令) ==常见函数:== ==以atomic_inc为例介绍实现过程== 在Linux内核文件archarmincludeasmatomic.h中。 执行atomic_read、atomic_set这些操作都只需要一条汇编指令,所以它们本身就是不可打断的。 需要特别研究的是atomic_inc、atomic_dec这类读出、修改、写回的函数。 所以atomic_add的原型是下面这个宏: atomic_add等效于: result(%0) tmp(%1) (v->counter)(%2) (&v->counter)(%3) i(%4) 注意:根据内联汇编的语法,result、tmp、&v->counter对应的数据都放在了寄存器中操作。如果出现上下文切换,切换机制会做寄存器上下文保护。 (1)ldrex %0, [%3] 意思是将&v->counter指向的数据放入result中,并且(分别在Local monitor和Global monitor中)设置独占标志。 (2)add %0, %0, %4 result = result + i (3)strex %1, %0, [%3] 意思是将result保存到&v->counter指向的内存中, 此时 Exclusive monitors会发挥作用,将保存是否成功的标志放入tmp中。 (4) teq %1, #0 测试strex是否成功(tmp == 0 ??) (5)bne 1b 如果发现strex失败,从(1)再次执行。 Spinlock 是内核中提供的一种比较常见的锁机制,==自旋锁是“原地等待”的方式解决资源冲突的==,即,一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到,只能够原地“打转”(忙等待)。由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制 —— 自旋锁不应该被长时间的持有(消耗 CPU 资源),一般应用在==中断上下文==。 1、spinlock是一种死等机制 2、信号量可以允许多个执行单元进入,spinlock不行,一次只能允许一个执行单元获取锁,并且进入临界区,其他执行单元都是在门口不断的死等 3、由于不休眠,因此spinlock可以应用在中断上下文中; 4、由于spinlock死等的特性,因此临界区执行代码尽可能的短; ==spinlock加锁以及解锁过程:== spin_lock(&devices_lock); 临界区代码 spin_unlock(&devices_lock); ==spinlock初始化== ==进程和进程之间同步== ==本地软中断之间同步== ==本地硬中断之间同步== ==本地硬中断之间同步并且保存本地中断状态== ==尝试获取锁== == arch_spinlock_t结构体定义如下: == == arch_spin_lock的实现如下: == lockval(%0) newval(%1) tmp(%2) &lock->slock(%3) 1 << TICKET_SHIFT(%4) (1)ldrex %0, [%3] 把lock->slock的值赋值给lockval;并且(分别在Local monitor和Global monitor中)设置独占标志。 (2)add %1, %0, %4 newval =lockval +(1<<16); 相当于next+1; (3)strex %2, %1, [%3] newval =lockval +(1<<16); 相当于next+1; 意思是将newval保存到 &lock->slock指向的内存中, 此时 Exclusive monitors会发挥作用,将保存是否成功的标志放入tmp中。 (4) teq %2, #0 测试strex是否成功 (5)bne 1b 如果发现strex失败,从(1)再次执行。 通过上面的分析,可知关键在于strex的操作是否成功的判断上。而这个就归功于ARM的Exclusive monitors和ldrex/strex指令的机制。 (6)while (lockval.tickets.next != lockval.tickets.owner) 如何lockval.tickets的next和owner是否相等。相同则跳出while循环,否则在循环内等待判断; * (7)wfe()和smp_mb() 最终调用#define barrier() asm volatile ("": : :"memory") * 阻止编译器重排,保证编译程序时在优化屏障之前的指令不会在优化屏障之后执行。 == arch_spin_unlock的实现如下: == 退出锁时:tickets.owner++ == 出现死锁的情况: == 1、拥有自旋锁的进程A在内核态阻塞了,内核调度B进程,碰巧B进程也要获得自旋锁,此时B只能自旋转。 而此时抢占已经关闭,(单核)不会调度A进程了,B永远自旋,产生死锁。 2、进程A拥有自旋锁,中断到来,CPU执行中断函数,中断处理函数,中断处理函数需要获得自旋锁,访问共享资源,此时无法获得锁,只能自旋,产生死锁。 == 如何避免死锁: == 1、如果中断处理函数中也要获得自旋锁,那么驱动程序需要在拥有自旋锁时禁止中断; 2、自旋锁必须在可能的最短时间内拥有 3、避免某个获得锁的函数调用其他同样试图获取这个锁的函数,否则代码就会死锁;不论是信号量还是自旋锁,都不允许锁拥有者第二次获得这个锁,如果试图这么做,系统将挂起; 4、锁的顺序规则(a) 按同样的顺序获得锁;b) 如果必须获得一个局部锁和一个属于内核更中心位置的锁,则应该首先获取自己的局部锁 ;c) 如果我们拥有信号量和自旋锁的组合,则必须首先获得信号量;在拥有自旋锁时调用down(可导致休眠)是个严重的错误的;) == rw(read/write)spinlock: == 加锁逻辑: 1、假设临界区内没有任何的thread,这个时候任何的读线程和写线程都可以键入 2、假设临界区内有一个读线程,这时候信赖的read线程可以任意进入,但是写线程不能进入; 3、假设临界区有一个写线程,这时候任何的读、写线程都不可以进入; 4、假设临界区内有一个或者多个读线程,写线程不可以进入临界区,但是写线程也无法阻止后续的读线程继续进去,要等到临界区所有的读线程都结束了,才可以进入,可见:==rw(read/write)spinlock更加有利于读线程;== == seqlock(顺序锁): == 加锁逻辑: 1、假设临界区内没有任何的thread,这个时候任何的读线程和写线程都可以键入 2、假设临界区内没有写线程的情况下,read线程可以任意进入; 3、假设临界区有一个写线程,这时候任何的读、写线程都不可以进入; 4、假设临界区内只有read线程的情况下,写线程可以理解执行,不会等待,可见:==seqlock(顺序锁)更加有利于写线程;== 读写速度 : CPU > 一级缓存 > 二级缓存 > 内存 ,因此某一个CPU0的lock修改了,其他的CPU的lock就会失效;那么其他CPU就会依次去L1 L2和主存中读取lock值,一旦其他CPU去读取了主存,就存在系统性能降低的风险; mutex用于互斥操作。 互斥体只能用于一个线程,资源只有两种状态(占用或者空闲) 1、mutex的语义相对于信号量要简单轻便一些,在锁争用激烈的测试场景下,mutex比信号量执行速度更快,可扩展 性更好, 2、另外mutex数据结构的定义比信号量小;、 3、同一时刻只有一个线程可以持有mutex 4、不允许递归地加锁和解锁 5、当进程持有mutex时,进程不可以退出。 u2022 mutex必须使用官方API来初始化。 u2022 mutex可以睡眠,所以不允许在中断处理程序或者中断下半部中使用,例如tasklet、定时器等 ==常见操作:== struct mutex mutex_1; mutex_init(&mutex_1); mutex_lock(&mutex_1) 临界区代码; mutex_unlock(&mutex_1) ==常见函数:== =
2023-06-09 22:44:091

sig_atomic_t的Linux内核中的原子操作 atomic_t

原型:typedef struct {volatile int counter;} atomic_t;1 声明,定义并初始化原子变量atomic_t isopen = ATOMIC_INIT(1);2 使用方法原子变量自减1,并测试是否为0,如果为0,返回true,否则返回falseif( !atomic_dec_and_test(&isopen) ) {atomic_inc(&isopen); //加1操作return -EBUSY;}3 释放减1操作atomic_dec(&isopen);
2023-06-09 22:44:551

linux如何保护共享资源

主要有一下几种:中断屏蔽、 原子操作、自旋锁、信号量、环形缓冲区。在本文中对于这些机制的具体的实现函数,以及原理不再做任何的表述。本 一、原子变量 假设我们所需要保护的共享资源只是一个整数值,此时我们可以采用的机制有自旋锁,信号量,和原子变量,当然中断屏蔽也是可以的。但是如果选择最优的机制,我们应该选择原子变量。原因:1.对一个整数值的操作是很简单的,也就是说此对全局变量形成的临界区是很小的.如果我们采用其他的机制,例如锁的机制,信号量等就显得有些浪费.也就是说,你的锁机制的代码量可能比临界区的代码量还要多.2. 对一个缺乏经验的程序员来讲,由于思维缺乏逻辑,使用锁机制会存在很多潜在的风险.例如: 死锁.等问题;而采用原子变量的方法,可以避免锁机制产生的弊端. 二、自旋锁自旋锁机制和信号量机制都可以对资源进行互斥访问,但是从性能上讲,自旋锁的性能优于信号量。但是自旋锁机制本身在不停的自旋(也就是查询锁是否可用),导致此机制有一些缺点:1。假定我们的驱动程序获得了一个自旋锁,在临界区开始了他的工作期间,驱动程序丢掉了处理器。也许他调用了一个函数,这个函数使进程休眠。也许发生了内核的抢占。但是我们的代码拥有这个自旋锁,如果其他的进程想要获得此自旋锁,需要等很长时间,甚至造成死锁。所以我们应用自旋锁时应遵循:任何拥有自旋锁的代码必须是原子的。所以他不能休眠,所以不能调用能够引起休眠的函数。例如:copy_to_user,copy_from_user,kmalloc等。也不能因为任何原因放弃处理器,除了中断以外(有时中断也不行,可以采用禁止中断的自旋锁函数操作)。 三、信号量拥有信号量的进程是可以休眠的。这也正是他对于自旋锁的优势。 其他的暂不论述。
2023-06-09 22:45:071

java中线程同步的几种方法

java中多线程的实现方法有两种:1.直接继承thread类;2.实现runnable接口;同步的实现方法有五种:1.同步方法;2.同步代码块;3.使用特殊域变量(volatile)实现线程同步;4.使用重入锁实现线程同步;5.使用局部变量实现线程同步 。其中多线程实现过程中需注意重写或者覆盖run()方法,而对于同步的实现方法中使用较常使用的是利用synchronized编写同步方法和代码块。
2023-06-09 22:45:291

linux内核函数kmap_atomic用法

1.atomic_read与atomic_set函数是原子变量的操作,就是原子读和原子设置的作用. 2.原子操作,就是执行操作的时候,其数值不会被其它线程或者中断所影响 3.原子操作是linux内核中一种同步的方式
2023-06-09 22:45:361

如何实现线程安全

问题一:什么是线程安全,实现线程安全有哪些方法 自己学习的时候做了一些笔记,希望对你有帮助 当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”---我没有跑题... 5.线程的同步与死锁 1.什么是同步 通过synchronized关键字标识方法或者代码块,限制线程对其内容的操作(同步详细介绍参见 .) 2.为什么要同步 java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性. 3.进行同步的格式 同步代码块 synchronized (同步的线程对象){ 需要同步的代码块; } 同步方法 synchronized 其他访问修饰符返回值方法名称(){ 方法内容 } (synchronized也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类) 4.什么是死锁 死锁是进程死锁的简称,是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面。它是计算机操作系统乃至并发程序设计中最难处理的问题之一 死锁的解决 (死锁详细介绍参见进程死锁及解决办法.docx) 5.注意点 1.同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 问题二:如何做到java线程安全 字段用final修饰,除非需要变化 变量用锁来守护,一组作为不变量的变量要用同一把锁 在复杂的组合操作中要保持锁 文档化你的同步策略 主要就这些,java里可以用synchronized关键字来进行锁,也可以用并发包里提供的许多类来完成线程安全的操作 问题三:什么是线程安全怎么实现线程安全 如果每个线程中对全局变量、静态变量只有读操作,而无写操作,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据 问题四:如何实现线程安全的HashMap Map m = Collections.synchronizedMap(new HashMap()); 即可 问题五:java 如何实现一个线程安全的队列 java.util.concurrent ConcurrentLinkedQueue 类提供了高效的、可伸缩的、线程安全的非阻塞 FIFO 队列。 自己去参考一下jdk5或6的api文档,里面已经实现了 问题六:如何解决线程安全问题 有2种解决方法。 第一,是采用原子变量,毕竟线程安全问题最根本上是由于全局变量和静态变量引起的,只要保证了对于变量的写操作要么全写要么不写,就可以解决线程安全,定义变量用sig_atomic_t和volatile。 第二,就是实现线程间同步啦,用互斥索,信号量。让线程有序的访问变量就可以啦 问题七:threadlocal 怎么实现线程安全 Character类包含一些可用来处理char变量的static方法,这些方法包括isDigit()、isLetter()、isWhitespace()和toUpperCase()。 char值没有符号。 问题八:如何创建线程安全的list 解决这个问题通常有两种方法(个人认为) 一:使用synchronized关键字,这个大家应该都很熟悉了,不解释了; 二:使用Collections.synchronizedList();使用方法如下: 假如你创建的代码如下:List> data=new ArrayList>(); 那么为了解决这个线程安全问题你可以这么使用Collections.synchronizedList(),如: List> data=Collections.synchronizedList(new ArrayList>()); 其他的都没变,使用的方法也几乎与ArrayList一样,大家可以参考下api文档; 额外说下 ArrayList与LinkedList;这两个都是接口List下的一个实现,用法都一样,但用的场所的有点不同,ArrayList适合于进行大量的随机访问的情况下使用,LinkedList适合在表中进行插入、删除时使用,二者都是非线程安全,解决方法同上(为了避免线程安全,以上采取的方法,特别是第二种,其实是非常损耗性能的)。 问题九:servlet怎么实现线程安全 servlet是web开发需要用的。 你要在eclipse里面新建servlet, 你的eclipse首先要支持web开发。 问题十:java如何实现线程安全,synchronized和lock的区别 synchronized同步相当于排队, lock相当于等待。
2023-06-09 22:45:431

对于“多线程访问同一个变量是不是需要加锁”的研究

不需要研究了,网上研究的很多了。通常可以这么认为:原子变量不需要加锁,非原子变量需要加锁。
2023-06-09 22:45:501

原子操作的相关程序

{__asm__ __volatile__(LOCK "addl %1,%0":"=m" (v->counter):"ir" (i),"m" (v->counter));}* static __inline__ int atomic_sub_and_test(int i,atomic_t *v)----------------------------------------从v 指向的原子变量减去i,并测试是否为0。若为0,返回真,否则返回假。由于x86的subl指令会在结果为0时设置CPU的zero标志位,而且这个标志位是CPU私有的,不会被其它CPU影响。因此,可以执行一次加锁的减操作,再根据CPU的zero标志位来设置本地变量c,并相应返回。{unsigned char c;__asm__ __volatile__(LOCK "subl %2,%0; sete %1":"=m" (v->counter),"=qm" (c):"ir" (i),"m" (v->counter) : "memory");return c;}------------------------------------#define atomic_read(v) ((v)->counter)读取v指向的原子变量的值。#define atomic_set(v,i) (((v)->counter) = (i))设置v指向的原子变量的值为i。static __inline__ void atomic_sub(int i,atomic_t *v)从v指向的原子变量减去i。static __inline__ void atomic_inc(atomic_t *v)递增v指向的原子变量。static __inline__ void atomic_dec(atomic_t *v)递减v指向的原子变量。static __inline__ int atomic_dec_and_test(atomic_t *v)递减v指向的原子变量,并测试是否为0。若为0,返回真,否则返回假。static __inline__ int atomic_inc_and_test(atomic_t *v)递增v指向的原子变量,并测试是否为0。若为0,返回真,否则返回假。static __inline__ int atomic_add_negative(int i,atomic_t *v)将v指向的原子变量加上i,并测试结果是否为负。若为负,返回真,否则返回假。这个操作用于实现semaphore。
2023-06-09 22:45:571

atmoic variable什么意思

atmoic variable原子变量
2023-06-09 22:46:112

LongAdder (上)实现原理篇

之前有篇文章讲过AtomicLong通过CAS提供了非阻塞的原子性操作,相比使用阻塞算法的同步器来说它的性能已经很好了,但是JDK开发组并不满足于此。使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS操作,而这会白白浪费CPU资源。 因此JDK8新增了一个原子性递增或者递减类LongAdder用来克服在高并发下使用AtomicLong的缺点。既然AtomicLong的性能瓶颈是由于多线程同时去竞争一个变量的更新而产生的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源,是不是就解决了性能问题?是的,LongAdder就是这个思路。下面通过一张的图来理解两者设计的不同之处。 上图为使用AtomicLong时,是多个线程同时竞争一个原子变量。 上图所示,使用LongAdder时,则是在内部维护多个Cell变量,每个Cell里面有一个初始值为0的long类型变量,这样,在同等并发量的情况下,争夺单个变量更新操作的线程就会减少,这变相地减少了争夺共享资源的并发量。另外,多个线程在争夺同一个Cell原子变量时如果失败了,它并不是在当前Cell变量上一直自旋CAS重试,而是尝试在其他Cell的变量上进行CAS尝试,这个改变增加了当前线程重试CAS成功的可能性。最后在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base返回的。 LongAdder维护了一个延迟初始化的原子性更新数组 (默认情况下Cell数组是null) 和一个基值变量base。由于Cells占用的内存是相对比较大的,所以一开始并不创建它,而是在需要创建时,也就是懒加载。 当一开始判断Cell数组是null并且并发较少时,所有的累加操作都是对base变量进行的。保持Cell数组的大小为2的N次方,在初始化时Cell数组中的Cell元素个数为2,数组里面的变量实体是Cell类型。Cell类型是AtomicLong的一个改进,用来减少缓存的征用,也就是解决伪共享问题。 对于大多数孤立的多个原子操作进行字节填充是浪费的,原因原子性操作都是无规律地分散在内存中的(也就是说多个原子性变量的内存地址不是连续的),多个原子变量被放入同一个缓存行的可能性很小。但是原子性数组的内存地址是连续的,所对数组内多个元素能经常共享缓存行,因此这里使用@sun.misc.Contended注解对Cell类进行字节填充,这放置了数组中多个元素共享一个缓存行,在性能上是一个提升。
2023-06-09 22:46:171

多线程下变量原子操作的几种方法

Note:1.2两个方法主要用于应用中【2种操作的性能是第一种的7-8倍,性能上优于第一种】,第三个方法主要应用于驱动层的。线程锁:如下例子:pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&count_lock);global_int++;pthread_mutex_unlock(&count_lock);2._sync_fetch_and_add系列函数是直接指令集的的函数,由GCC直接支持。他是锁住CPU和RAM之间的数据线来仿止其它的操作的。效率高,但会影响其多线程性能type __sync_fetch_and_add (type *ptr, type value);type __sync_fetch_and_sub (type *ptr, type value);type __sync_fetch_and_or (type *ptr, type value);type __sync_fetch_and_and (type *ptr, type value);type __sync_fetch_and_xor (type *ptr, type value);type __sync_fetch_and_nand (type *ptr, type value);type __sync_add_and_fetch (type *ptr, type value);type __sync_sub_and_fetch (type *ptr, type value);type __sync_or_and_fetch (type *ptr, type value);type __sync_and_and_fetch (type *ptr, type value);type __sync_xor_and_fetch (type *ptr, type value);type __sync_nand_and_fetch (type *ptr, type value);3.linux驱动中的并发控制技术----原子操作,自旋锁,读写自旋锁 ,顺序锁,RCU机制,信号量,完成量,互斥体等方式来实现原子级的操作。
2023-06-09 22:46:241

关于线程安全问题分析?

在学习java编程开发语言的过程中,我们掌握了线程与线程池等相关技术知识。今天,北大青鸟昌平镇计算机学院就关于线程安全问题给大家做一个简单的说明和介绍,一起来了解一下吧。线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。什么时候考虑到线程安全:一个对象是否需要线程安全,取决于该对象是否被多线程访问。这指的是程序中访问对象的方式,而不是对象要实现的功能。要使得对象是线程安全的,要采用同步机制来协同对对象可变状态的访问。Java常用的同步机制是Synchronized,还包括volatile类型的变量,显示锁以及原子变量。在多个线程中,当它们同时访问同个类时,每次执行的结果和单线程结果一致,且变量值跟预期一致,这个类则是线程安全的。锁的特性锁机制的两种特性:互斥性:即同一时间只允许一个线程持有某个对象的锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的,否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。挂起、休眠、阻塞和非阻塞挂起:当线程被挂起时,其会失去CPU的使用时间,直到被其他线程(用户线程或调试线程)唤醒。休眠:同样是会失去CPU的使用时间,但是在过了指定的休眠时间之后,它会自动激活,无需唤醒(整个唤醒表面看是自动的,但实际上也得有守护线程去唤醒,只是不需编程者手动干预)。阻塞:在线程执行时,所需要的资源不能得到,则线程被挂起,直到满足可操作的条件。非阻塞:在线程执行时,所需要的资源不能得到,则线程不是被挂起等待,而是继续执行其余事情,等待条件满足了后,收到了通知(同样是守护线程去做)再执行。
2023-06-09 22:46:311

java并发常识

1.java并发编程是什么 1, 保证线程安全的三种方法: a, 不要跨线程访问共享变量b, 使共享变量是final类型的c, 将共享变量的操作加上同步 2, 一开始就将类设计成线程安全的, 比在后期重新修复它,更容易。 3, 编写多线程程序, 首先保证它是正确的, 其次再考虑性能。 4, 无状态或只读对象永远是线程安全的。 5, 不要将一个共享变量 *** 在多线程环境下(无同步或不可变性保护) 6, 多线程环境下的延迟加载需要同步的保护, 因为延迟加载会造成对象重复实例化 7, 对于volatile声明的数值类型变量进行运算, 往往是不安全的(volatile只能保证可见性,不能保证原子性)。 详见volatile原理与技巧中, 脏数据问题讨论。 8, 当一个线程请求获得它自己占有的锁时(同一把锁的嵌套使用), 我们称该锁为可重入锁。在jdk1。5并发包中, 提供了可重入锁的java实现-ReentrantLock。 9, 每个共享变量,都应该由一个唯一确定的锁保护。 创建与变量相同数目的ReentrantLock, 使他们负责每个变量的线程安全。 10,虽然缩小同步块的范围, 可以提升系统性能。 但在保证原子性的情况下, 不可将原子操作分解成多个synchronized块。 11, 在没有同步的情况下, 编译器与处理器运行时的指令执行顺序可能完全出乎意料。 原因是, 编译器或处理器为了优化自身执行效率, 而对指令进行了的重排序(reordering)。 12, 当一个线程在没有同步的情况下读取变量, 它可能会得到一个过期值, 但是至少它可以看到那个线程在当时设定的一个真实数值。 而不是凭空而来的值。 这种安全保证, 称之为最低限的安全性(out-of-thin-air safety) 在开发并发应用程序时, 有时为了大幅度提高系统的吞吐量与性能, 会采用这种无保障的做法。 但是针对, 数值的运算, 仍旧是被否决的。 13, volatile变量,只能保证可见性, 无法保证原子性。 14, 某些耗时较长的网络操作或IO, 确保执行时, 不要占有锁。 15, 发布(publish)对象, 指的是使它能够被当前范围之外的代码所使用。 (引用传递)对象逸出(escape), 指的是一个对象在尚未准备好时将它发布。 原则: 为防止逸出, 对象必须要被完全构造完后, 才可以被发布(最好的解决方式是采用同步) this关键字引用对象逸出 例子: 在构造函数中, 开启线程, 并将自身对象this传入线程, 造成引用传递。 而此时, 构造函数尚未执行完, 就会发生对象逸出了。 16, 必要时, 使用ThreadLocal变量确保线程封闭性(封闭线程往往是比较安全的, 但一定程度上会造成性能损耗)封闭对象的例子在实际使用过程中, 比较常见, 例如 hibernate openSessionInView机制, jdbc的connection机制。 17, 单一不可变对象往往是线程安全的(复杂不可变对象需要保证其内部成员变量也是不可变的)良好的多线程编程习惯是: 将所有的域都声明为final, 除非它们是可变的。 2.Java线程并发协作是什么 线程发生死锁可能性很小,即使看似可能发生死锁的代码,在运行时发生死锁的可能性也是小之又小。 发生死锁的原因一般是两个对象的锁相互等待造成的。 在《Java线程:线程的同步与锁》一文中,简述死锁的概念与简单例子,但是所给的例子是不完整的,这里给出一个完整的例子。 /** * Java线程:并发协作-死锁 * * @author Administrator 2009-11-4 22:06:13 */ public class Test { public static void main(String[] args) { DeadlockRisk dead = new DeadlockRisk(); MyThread t1 = new MyThread(dead, 1, 2); MyThread t2 = new MyThread(dead, 3, 4); MyThread t3 = new MyThread(dead, 5, 6); MyThread t4 = new MyThread(dead, 7, 8); t1。 start(); t2。 start(); t3。start(); t4。 start(); } } class MyThread extends Thread { private DeadlockRisk dead; private int a, b; MyThread(DeadlockRisk dead, int a, int b) { this。 dead = dead; this。 a = a; this。b = b; } @Override public void run() { dead。 read(); dead。write(a, b); } } class DeadlockRisk { private static class Resource { public int value; }。 3.如何学习Java高并发 1.学习 *** 并发框架的使用,如ConcurrentHashMAP,CopyOnWriteArrayList/Set等2.几种并发锁的使用以及线程同步与互斥,如ReentainLock,synchronized,Lock,CountDownLatch,Semaphore等3.线程池如Executors,ThreadPoolExecutor等4.Runable,Callable,RescureTask,Future,FutureTask等5.Fork-Join框架以上基本包含完了,如有缺漏请原谅。 4.并发编程的Java抽象有哪些呢 一、机器和OS级别抽象 (1)冯诺伊曼模型 经典的顺序化计算模型,貌似可以保证顺序化一致性,但是没有哪个现代的多处理架构会提供顺序一致性,冯氏模型只是现代多处理器行为的模糊近似。 这个计算模型,指令或者命令列表改变内存变量直接契合命令编程泛型,它以显式的算法为中心,这和声明式编程泛型有区别。 就并发编程来说,会显著的引入时间概念和状态依赖 所以所谓的函数式编程可以解决其中的部分问题。 (2)进程和线程 进程抽象运行的程序,是操作系统资源分配的基本单位,是资源cpu,内存,IO的综合抽象。 线程是进程控制流的多重分支,它存在于进程里,是操作系统调度的基本单位,线程之间同步或者异步执行,共享进程的内存地址空间。 (3)并发与并行 并发,英文单词是concurrent,是指逻辑上同时发生,有人做过比喻,要完成吃完三个馒头的任务,一个人可以这个馒头咬一口,那个馒头咬一口,这样交替进行,最后吃完三个馒头,这就是并发,因为在三个馒头上同时发生了吃的行为,如果只是吃完一个接着吃另一个,这就不是并发了,是排队,三个馒头如果分给三个人吃,这样的任务完成形式叫并行,英文单词是parallel。 回到计算机概念,并发应该是单CPU时代或者单核时代的说法,这个时候CPU要同时完成多任务,只能用时间片轮转,在逻辑上同时发生,但在物理上是串行的。 现在大多数计算机都是多核或者多CPU,那么现在的多任务执行方式就是物理上并行的。 为了从物理上支持并发编程,CPU提供了相应的特殊指令,比如原子化的读改写,比较并交换。 (4)平台内存模型 在可共享内存的多处理器体系结构中,每个处理器都有它自己的缓存,并且周期性的与主存同步,为什么呢?因为处理器通过降低一致性来换取性能,这和CAP原理通过降低一致性来获取伸缩性有点类似,所以大量的数据在CPU的寄存器中被计算,另外CPU和编译器为了性能还会乱序执行,但是CPU会提供存储关卡指令来保证存储的同步,各种平台的内存模型或者同步指令可能不同,所以这里必须介入对内存模型的抽象,JMM就是其中之一。 二、编程模型抽象 (1)基于线程模型 (2)基于Actor模型 (3)基于STM软件事务内存 …… Java体系是一个基于线程模型的本质编程平台,所以我们主要讨论线程模型。 三、并发单元抽象 大多数并发应用程序都是围绕执行任务进行管理的,任务是抽象,离散的工作单元,所以编写并发程序,首要工作就是提取和分解并行任务。 一旦任务被抽象出来,他们就可以交给并发编程平台去执行,同时在任务抽象还有另一个重要抽象,那就是生命周期,一个任务的开始,结束,返回结果,都是生命周期中重要的阶段。 那么编程平台必须提供有效安全的管理任务生命周期的API。 四、线程模型 线程模型是Java的本质模型,它无所不在,所以Java开发必须搞清楚底层线程调度细节,不搞清楚当然就会有struts1,struts2的原理搞不清楚的基本灾难(比如在struts2的action中塞入状态,把struts2的action配成单例)。 用线程来抽象并发编程,是比较低级别的抽象,所以难度就大一些,难度级别会根据我们的任务特点有以下几个类别 (1)任务非常独立,不共享,这是最理想的情况,编程压力为0。 (2)共享数据,压力开始增大,必须引入锁,Volatile变量,问题有活跃度和性能危险。 (3)状态依赖,压力再度增大,这时候我们基本上都是求助jdk 提供的同步工具。 五、任务执行 任务是一个抽象体,如果被抽象了出来,下一步就是交给编程平台去执行,在Java中,描述任务的一个基本接口是Runnable,可是这个抽象太有限了,它不能返回值和抛受检查异常,所以Jdk5。 0有另外一个高级抽象Callable。 任务的执行在Jdk中也是一个底级别的Thread,线程有好处,但是大量线程就有大大的坏处,所以如果任务量很多我们并不能就创建大量的线程去服务这些任务,那么Jdk5。 0在任务执行上做了抽象,将任务和任务执行隔离在接口背后,这样我们就可以引入比如线程池的技术来优化执行,优化线程的创建。 任务是有生命周期的,所以Jdk5。 0提供了Future这个对象来描述对象的生命周期,通过这个future可以取到任务的结果甚至取消任务。 六、锁 当然任务之间共享了数据,那么要保证数据的安全,必须提供一个锁机制来协调状态,锁让数据访问原子,但是引入了串行化,降低了并发度,锁是降低程序伸缩性的原罪,锁是引入上下文切换的主要原罪,锁是引入死锁,活锁,优先级倒置的绝对原罪,但是又不能没有锁,在Java中,锁是一个对象,锁提供原子和内存可见性,Volatile变量提供内存可见性不提供原子,原子变量提供可见性和原子,通过原子变量可以构建无锁算法和无锁数据结构,但是这需要高高手才可以办到。 5.Java高并发入门要怎么学习 1、如果不使用框架,纯原生Java编写,是需要了解Java并发编程的,主要就是学习Doug Lea开发的那个java.util.concurrent包下面的API;2、如果使用框架,那么我的理解,在代码层面确实不会需要太多的去关注并发问题,反而是由于高并发会给系统造成很大压力,要在缓存、数据库操作上要多加考虑。 3、但是即使是使用框架,在工作中还是会用到多线程,就拿常见的CRUD接口来说,比如一个非常耗时的save接口,有多耗时呢?我们假设整个save执行完要10分钟,所以,在save的时候,就需要采用异步的方式,也就是单独用一个线程去save,然后直接给前端返回200。 6.Java如何进行并发多连接socket编程呢 Java多个客户端同时连接服务端,在现实生活中用得比较多。 同时执行多项任务,第一想到的当然是多线程了。下面用多线程来实现并发多连接。 import java。。 *; import java。io。 *; public class ThreadServer extends Thread { private Socket client; public ThreadServer(Socket c) { this。 client=c; } public void run() { try { BufferedReader in=new BufferedReader(new InputStreamReader(client。 getInputStream())); PrintWriter out=new PrintWriter(client。 getOutputStream()); Mutil User but can"t parallel while (true) { String str=in。 readLine(); System。out。 println(str); out。 println("has receive。 "); out。 flush(); if (str。equals("end")) break; } client。 close(); } catch (IOException ex) { } finally { } } public static void main(String[] args)throws IOException { ServerSocket server=new ServerSocket(8000); while (true) { transfer location change Single User or Multi User ThreadServer mu=new ThreadServer(server。 accept()); mu。 start(); } } }J。 7.如何掌握java多线程,高并发,大数据方面的技能 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。 (线程是cpu调度的最小单位)线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。多进程是指操作系统能同时运行多个任务(程序)。 多线程是指在同一程序中有多个顺序流在执行。在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用。 8.java工程师需要掌握哪些知识 1.Core Java,就是Java基础、JDK的类库,很多童鞋都会说,JDK我懂,但是懂还不足够,知其然还要知其所以然,JDK的源代码写的非常好,要经常查看,对使用频繁的类,比如String, *** 类(List,Map,Set)等数据结构要知道它们的实现,不同的 *** 类有什么区别,然后才能知道在一个具体的场合下使用哪个 *** 类更适合、更高效,这些内容直接看源代码就OK了2.多线程并发编程,现在并发几乎是写服务端程序必须的技术,那对Java中的多线程就要有足够的熟悉,包括对象锁机制、synchronized关键字,concurrent包都要非常熟悉,这部分推荐你看看《Java并发编程实践》这本书,讲解的很详细3.I/O,Socket编程,首先要熟悉Java中Socket编程,以及I/O包,再深入下去就是Java NIO,再深入下去是操作系统底层的Socket实现,了解Windows和Linux中是怎么实现socket的4.JVM的一些知识,不需要熟悉,但是需要了解,这是Java的本质,可以说是Java的母体, 了解之后眼界会更宽阔,比如Java内存模型(会对理解Java锁、多线程有帮助)、字节码、JVM的模型、各种垃圾收集器以及选择、JVM的执行参数(优化JVM)等等,这些知识在《深入Java虚拟机》这本书中都有详尽的解释,或者去oracle网站上查看具体版本的JVM规范.5.一些常用的设计模式,比如单例、模板方法、代理、适配器等等,以及在Core Java和一些Java框架里的具体场景的实现,这个可能需要慢慢积累,先了解有哪些使用场景,见得多了,自己就自然而然会去用。 6.常用数据库(Oracle、MySQL等)、SQL语句以及一般的优化7.JavaWeb开发的框架,比如Spring、iBatis等框架,同样他们的原理才是最重要的,至少要知道他们的大致原理。8.其他一些有名的用的比较多的开源框架和包,ty网络框架,Apache mon的N多包,Google的Guava等等,也可以经常去Github上找一些代码看看。 暂时想到的就这么多吧,1-4条是Java基础,全部的这些知识没有一定的时间积累是很难搞懂的,但是了解了之后会对Java有个彻底的了解,5和6是需要学习的额外技术,7-8是都是基于1-4条的,正所谓万变不离其宗,前4条就是Java的灵魂所在,希望能对你有所帮助9.(补充)学会使用Git。如果你还在用SVN的话,赶紧投入Git的怀抱吧。 9.java 多线程的并发到底是什么意思 一、多线程1、操作系统有两个容易混淆的概念,进程和线程。 进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种资源和状态信息,包括打开的文件、子进程和信号处理。线程:表示程序的执行流程,是CPU调度执行的基本单位;线程有自己的程序计数器、寄存器、堆栈和帧。 同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源。2、Java标准库提供了进程和线程相关的API,进程主要包括表示进程的java.lang.Process类和创建进程的java.lang.ProcessBuilder类;表示线程的是java.lang.Thread类,在虚拟机启动之后,通常只有Java类的main方法这个普通线程运行,运行时可以创建和启动新的线程;还有一类守护线程(damon thread),守护线程在后台运行,提供程序运行时所需的服务。 当虚拟机中运行的所有线程都是守护线程时,虚拟机终止运行。3、线程间的可见性:一个线程对进程 *** 享的数据的修改,是否对另一个线程可见可见性问题:a、CPU采用时间片轮转等不同算法来对线程进行调度[java] view plaincopypublic class IdGenerator{ private int value = 0; public int getNext(){ return value++; } } 对于IdGenerator的getNext()方法,在多线程下不能保证返回值是不重复的:各个线程之间相互竞争CPU时间来获取运行机会,CPU切换可能发生在执行间隙。 以上代码getNext()的指令序列:CPU切换可能发生在7条指令之间,多个getNext的指令交织在一起。
2023-06-09 22:46:411

java一些能降低竞争锁的方法?

本文介绍一下提升并发可伸缩性的一些方式:减少锁的持有时间,降低锁的粒度,锁分段、避免热点域以及采用非独占的锁或非阻塞锁来代替独占锁。减少锁的持有时间降低发生竞争可能性的一种有效方式就是尽可能缩短锁的持有时间。例如,可以将一些与锁无关的代码移出同步代码块,尤其是那些开销较大的操作,以及可能被阻塞的操作,例如I/O操作。降低锁的粒度另一种减小锁的持有时间的方式是降低线程请求锁的频率(从而减小发生竞争的可能性)。这可以通过锁分解和锁分段等技术来实现,在这些技术中将采用多个相互独立的锁来保护独立的状态变量,从而改变这些变量在之前由单个锁来保护的情况。这些技术能减小锁操作的粒度,并能实现更高的可伸缩性,然而,使用的锁越多,那么发生死锁的风险也就越高。锁分段在某些情况下,可以将锁分解技术进一步扩展为对一组独立对象上的锁进行分解,这种情况被称为锁分段。例如,在ConcurrentHashMap的实现中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(Nmod16)个锁来保护。假设散列函数具有合理的分布性,并且关键字能够实现均匀分布,那么这大约能把对于锁的请求减少到原来的1/16。正是这项技术使得ConcurrentHashMap能够支持多达16个并发的写入器。(要使得拥有大量处理器的系统在高访问量的情况下实现更高的并发性,还可以进一步增加锁的数量,但仅当你能证明并发写入线程的竞争足够激烈并需要突破这个限制时,才能将锁分段的数量超过默认的16个。)避免热点域如果一个锁保护两个独立变量X和Y,并且线程A想要访问X,而线程B想要访问Y(这类似于在ServerStatus中,一个线程调用addUser,而另一个线程调用addQuery),那么这两个线程不会在任何数据上发生竞争,即使它们会在同一个锁上发生竞争。当每个操作都请求多个变量时,锁的粒度将很难降低。这是在性能与可伸缩性之间相互制衡的另一个方面,一些常见的优化措施,例如将一些反复计算的结果缓存起来,都会引入一些“热点域(HotField)”,而这些热点域往往会限制可伸缩性。当实现HashMap时,你需要考虑如何在size方法中计算Map中的元素数量。最简单的方法就是,在每次调用时都统计一次元素的数量。一种常见的优化措施是,在插入和移除元素时更新一个计数器,虽然这在put和remove等方法中略微增加了一些开销,以确保计数器是最新的值,但这将把size方法的开销从O(n)降低到O(l)。代替独占锁第三种降低竞争锁的影响的技术就是放弃使用独占锁,从而有助于使用一种友好并发的方式来管理共享状态。例如,使用并发容器、读-写锁、不可变对象以及原子变量。回龙观北大青鸟发现ReadWriteLock能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要加锁操作。
2023-06-09 22:47:001

java.util.concurrent.atomic的原子模块

设计原子类主要用作各种构造块,用于实现非阻塞数据结构和相关的基础结构类。compareAndSet 方法不是锁定的常规替换方法。仅当对象的重要更新限定于单个 变量时才应用它。原子类不是 java.lang.Integer 和相关类的通用替换方法。它们不定义诸如 hashCode 和 compareTo 之类的方法。(因为原子变量是可变的,所以对于哈希表键来说,它们不是好的选择。)另外,仅为那些通常在预期应用程序中使用的类型提供类。例如,没有表示 byte 的原子类。这种情况不常见,如果要这样做,可以使用 AtomicInteger 来保持 byte 值,并进行适当的强制转换。也可以使用 Float.floatToIntBits 和 Float.intBitstoFloat 转换来保持 float 值,使用 Double.doubleToLongBits 和 Double.longBitsToDouble 转换来保持 double 值。从以下版本开始:1.5
2023-06-09 22:47:131

java多线程中如何保证变量的可见性,原子性

首先,要知道原子性和可见性是在并发环境需要思考的问题,所以下面的回答是围绕了并发场景来描述的。如果大家不明白并发场景,请先了解java并发原子性,可以理解为CPU层面不能分割的操作,那么 i++是原子操作吗?不是的,实际它是i=i+1,这个操作首先要读取i的值,然后为i值加1。是需要拆分的。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。有好几种方式实现一个原子操作。java提供了 sychronized代码块,lock接口(它的实现重入锁是比较常用的)。还可以使用原子数据结构。AtomicInteger、AtomicLong、AtomicReference等。可见性。可以理解为线程层面各个线程之间对某个操作是透明的,各个线程可以及时知道引用的改变。volatile修饰的变量可以保证可见性,假如,一个变量只有 1或者0两种情况。那么volatile修饰之后,就不需要对这个变量加同步操作了。强调一下。volatile不能保证原子性。volatile修饰的整数i,在多线程下 i++之后,不能得到预期的值
2023-06-09 22:47:291

并发编程解惑之线程

主要内容: 进程是资源分配的最小单位,每个进程都有独立的代码和数据空间,一个进程包含 1 到 n 个线程。线程是 CPU 调度的最小单位,每个线程有独立的运行栈和程序计数器,线程切换开销小。 Java 程序总是从主类的 main 方法开始执行,main 方法就是 Java 程序默认的主线程,而在 main 方法中再创建的线程就是其他线程。在 Java 中,每次程序启动至少启动 2 个线程。一个是 main 线程,一个是垃圾收集线程。每次使用 Java 命令启动一个 Java 程序,就相当于启动一个 JVM 实例,而每个 JVM 实例就是在操作系统中启动的一个进程。 多线程可以通过继承或实现接口的方式创建。 Thread 类是 JDK 中定义的用于控制线程对象的类,该类中封装了线程执行体 run() 方法。需要强调的一点是,线程执行先后与创建顺序无关。 通过 Runnable 方式创建线程相比通过继承 Thread 类创建线程的优势是避免了单继承的局限性。若一个 boy 类继承了 person 类,boy 类就无法通过继承 Thread 类的方式来实现多线程。 使用 Runnable 接口创建线程的过程:先是创建对象实例 MyRunnable,然后将对象 My Runnable 作为 Thread 构造方法的入参,来构造出线程。对于 new Thread(Runnable target) 创建的使用同一入参目标对象的线程,可以共享该入参目标对象 MyRunnable 的成员变量和方法,但 run() 方法中的局部变量相互独立,互不干扰。 上面代码是 new 了三个不同的 My Runnable 对象,如果只想使用同一个对象,可以只 new 一个 MyRunnable 对象给三个 new Thread 使用。 实现 Runnable 接口比继承 Thread 类所具有的优势: 线程有新建、可运行、阻塞、等待、定时等待、死亡 6 种状态。一个具有生命的线程,总是处于这 6 种状态之一。 每个线程可以独立于其他线程运行,也可和其他线程协同运行。线程被创建后,调用 start() 方法启动线程,该线程便从新建态进入就绪状态。 NEW 状态(新建状态) 实例化一个线程之后,并且这个线程没有开始执行,这个时候的状态就是 NEW 状态: RUNNABLE 状态(就绪状态): 阻塞状态有 3 种: 如果一个线程调用了一个对象的 wait 方法, 那么这个线程就会处于等待状态(waiting 状态)直到另外一个线程调用这个对象的 notify 或者 notifyAll 方法后才会解除这个状态。 run() 里的代码执行完毕后,线程进入终结状态(TERMINATED 状态)。 线程状态有 6 种:新建、可运行、阻塞、等待、定时等待、死亡。 我们看下 join 方法的使用: 运行结果: 我们来看下 yield 方法的使用: 运行结果: 线程与线程之间是无法直接通信的,A 线程无法直接通知 B 线程,Java 中线程之间交换信息是通过共享的内存来实现的,控制共享资源的读写的访问,使得多个线程轮流执行对共享数据的操作,线程之间通信是通过对共享资源上锁或释放锁来实现的。线程排队轮流执行共享资源,这称为线程的同步。 Java 提供了很多同步操作(也就是线程间的通信方式),同步可使用 synchronized 关键字、Object 类的 wait/notifyAll 方法、ReentrantLock 锁、无锁同步 CAS 等方式来实现。 ReentrantLock 是 JDK 内置的一个锁对象,用于线程同步(线程通信),需要用户手动释放锁。 运行结果: 这表明同一时间段只能有 1 个线程执行 work 方法,因为 work 方法里的代码需要获取到锁才能执行,这就实现了多个线程间的通信,线程 0 获取锁,先执行,线程 1 等待,线程 0 释放锁,线程 1 继续执行。 synchronized 是一种语法级别的同步方式,称为内置锁。该锁会在代码执行完毕后由 JVM 释放。 输出结果跟 ReentrantLock 一样。 Java 中的 Object 类默认是所有类的父类,该类拥有 wait、 notify、notifyAll 方法,其他对象会自动继承 Object 类,可调用 Object 类的这些方法实现线程间的通信。 除了可以通过锁的方式来实现通信,还可通过无锁的方式来实现,无锁同 CAS(Compare-and-Swap,比较和交换)的实现,需要有 3 个操作数:内存地址 V,旧的预期值 A,即将要更新的目标值 B,当且仅当内存地址 V 的值与预期值 A 相等时,将内存地址 V 的值修改为目标值 B,否则就什么都不做。 我们通过计算器的案例来演示无锁同步 CAS 的实现方式,非线程安全的计数方式如下: 线程安全的计数方式如下: 运行结果: 线程安全累加的结果才是正确的,非线程安全会出现少计算值的情况。JDK 1.5 开始,并发包里提供了原子操作的类,AtomicBoolean 用原子方式更新的 boolean 值,AtomicInteger 用原子方式更新 int 值,AtomicLong 用原子方式更新 long 值。 AtomicInteger 和 AtomicLong 还提供了用原子方式将当前值自增 1 或自减 1 的方法,在多线程程序中,诸如 ++i 或 i++ 等运算不具有原子性,是不安全的线程操作之一。 通常我们使用 synchronized 将该操作变成一个原子操作,但 JVM 为此种操作提供了原子操作的同步类 Atomic,使用 AtomicInteger 做自增运算的性能是 ReentantLock 的好几倍。 上面我们都是使用底层的方式实现线程间的通信的,但在实际的开发中,我们应该尽量远离底层结构,使用封装好的 API,例如 J.U.C 包(java.util.concurrent,又称并发包)下的工具类 CountDownLath、CyclicBarrier、Semaphore,来实现线程通信,协调线程执行。 CountDownLatch 能够实现线程之间的等待,CountDownLatch 用于某一个线程等待若干个其他线程执行完任务之后,它才开始执行。 CountDownLatch 类只提供了一个构造器: CountDownLatch 类中常用的 3 个方法: 运行结果: CyclicBarrier 字面意思循环栅栏,通过它可以让一组线程等待至某个状态之后再全部同时执行。当所有等待线程都被释放以后,CyclicBarrier 可以被重复使用,所以有循环之意。 相比 CountDownLatch,CyclicBarrier 可以被循环使用,而且如果遇到线程中断等情况时,可以利用 reset() 方法,重置计数器,CyclicBarrier 会比 CountDownLatch 更加灵活。 CyclicBarrier 提供 2 个构造器: 上面的方法中,参数 parties 指让多少个线程或者任务等待至 barrier 状态;参数 barrierAction 为当这些线程都达到 barrier 状态时会执行的内容。 CyclicBarrier 中最重要的方法 await 方法,它有 2 个重载版本。下面方法用来挂起当前线程,直至所有线程都到达 barrier 状态再同时执行后续任务。 而下面的方法则是让这些线程等待至一定的时间,如果还有线程没有到达 barrier 状态就直接让到达 barrier 的线程执行任务。 运行结果: CyclicBarrier 用于一组线程互相等待至某个状态,然后这一组线程再同时执行,CountDownLatch 是不能重用的,而 CyclicBarrier 可以重用。 Semaphore 类是一个计数信号量,它可以设定一个阈值,多个线程竞争获取许可信号,执行完任务后归还,超过阈值后,线程申请许可信号时将会被阻塞。Semaphore 可以用来 构建对象池,资源池,比如数据库连接池。 假如在服务器上运行着若干个客户端请求的线程。这些线程需要连接到同一数据库,但任一时刻只能获得一定数目的数据库连接。要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程呢? 给方法加同步锁,保证同一时刻只能有一个线程去调用此方法,其他所有线程排队等待,但若有 10 个数据库连接,也只有一个能被使用,效率太低。另外一种方法,使用信号量,让信号量许可与数据库可用连接数为相同数量,10 个数据库连接都能被使用,大大提高性能。 上面三个工具类是 J.U.C 包的核心类,J.U.C 包的全景图就比较复杂了: J.U.C 包(java.util.concurrent)中的高层类(Lock、同步器、阻塞队列、Executor、并发容器)依赖基础类(AQS、非阻塞数据结构、原子变量类),而基础类是通过 CAS 和 volatile 来实现的。我们尽量使用顶层的类,避免使用基础类 CAS 和 volatile 来协调线程的执行。J.U.C 包其他的内容,在其他的篇章会有相应的讲解。 Future 是一种异步执行的设计模式,类似 ajax 异步请求,不需要同步等待返回结果,可继续执行代码。使 Runnable(无返回值不支持上报异常)或 Callable(有返回值支持上报异常)均可开启线程执行任务。但是如果需要异步获取线程的返回结果,就需要通过 Future 来实现了。 Future 是位于 java.util.concurrent 包下的一个接口,Future 接口封装了取消任务,获取任务结果的方法。 在 Java 中,一般是通过继承 Thread 类或者实现 Runnable 接口来创建多线程, Runnable 接口不能返回结果,JDK 1.5 之后,Java 提供了 Callable 接口来封装子任务,Callable 接口可以获取返回结果。我们使用线程池提交 Callable 接口任务,将返回 Future 接口添加进 ArrayList 数组,最后遍历 FutureList,实现异步获取返回值。 运行结果: 上面就是异步线程执行的调用过程,实际开发中用得更多的是使用现成的异步框架来实现异步编程,如 RxJava,有兴趣的可以继续去了解,通常异步框架都是结合远程 HTTP 调用 Retrofit 框架来使用的,两者结合起来用,可以避免调用远程接口时,花费过多的时间在等待接口返回上。 线程封闭是通过本地线程 ThreadLocal 来实现的,ThreadLocal 是线程局部变量(local vari able),它为每个线程都提供一个变量值的副本,每个线程对该变量副本的修改相互不影响。 在 JVM 虚拟机中,堆内存用于存储共享的数据(实例对象),也就是主内存。Thread Local .set()、ThreadLocal.get() 方法直接在本地内存(工作内存)中写和读共享变量的副本,而不需要同步数据,不用像 synchronized 那样保证数据可见性,修改主内存数据后还要同步更新到工作内存。 Myabatis、hibernate 是通过 threadlocal 来存储 session 的,每一个线程都维护着一个 session,对线程独享的资源操作很方便,也避免了线程阻塞。 ThreadLocal 类位于 Thread 线程类内部,我们分析下它的源码: ThreadLocal 和 Synchonized 都用于解决多线程并发访问的问题,访问多线程共享的资源时,Synchronized 同步机制采用了以时间换空间的方式,提供一份变量让多个线程排队访问,而 ThreadLocal 采用了以空间换时间的方式,提供每个线程一个变量,实现数据隔离。 ThreadLocal 可用于数据库连接 Connection 对象的隔离,使得每个请求线程都可以复用连接而又相互不影响。 在 Java 里面,存在强引用、弱引用、软引用、虚引用。我们主要来了解下强引用和弱引用: 上面 a、b 对实例 A、B 都是强引用 而上面这种情况就不一样了,即使 b 被置为 null,但是 c 仍然持有对 C 对象实例的引用,而间接的保持着对 b 的强引用,所以 GC 不会回收分配给 b 的空间,导致 b 无法回收也没有被使用,造成了内存泄漏。这时可以通过 c = null; 来使得 c 被回收,但也可以通过弱引用来达到同样目的: 从源码中可以看出 Entry 里的 key 对 ThreadLocal 实例是弱引用: Entry 里的 key 对 ThreadLocal 实例是弱引用,将 key 值置为 null,堆中的 ThreadLocal 实例是可以被垃圾收集器(GC)回收的。但是 value 却存在一条从 Current Thread 过来的强引用链,只有当当前线程 Current Thread 销毁时,value 才能被回收。在 threadLocal 被设为 null 以及线程结束之前,Entry 的键值对都不会被回收,出现内存泄漏。为了避免泄漏,在 ThreadLocalMap 中的 set/get Entry 方法里,会对 key 为 null 的情况进行判断,如果为 null 的话,就会对 value 置为 null。也可以通过 ThreadLocal 的 remove 方法(类似加锁和解锁,最后 remove 一下,解锁对象的引用)直接清除,释放内存空间。 总结来说,利用 ThreadLocal 来访问共享数据时,JVM 通过设置 ThreadLocalMap 的 Key 为弱引用,来避免内存泄露,同时通过调用 remove、get、set 方法的时候,回收弱引用(Key 为 null 的 Entry)。当使用 static ThreadLocal 的时候(如上面的 Spring 多数据源),static 变量在类未加载的时候,它就已经加载,当线程结束的时候,static 变量不一定会被回收,比起普通成员变量使用的时候才加载,static 的生命周期变长了,若没有及时回收,容易产生内存泄漏。 使用线程池,可以重用存在的线程,减少对象创建、消亡的开销,可控制最大并发线程数,避免资源竞争过度,还能实现线程定时执行、单线程执行、固定线程数执行等功能。 Java 把线程的调用封装成了一个 Executor 接口,Executor 接口中定义了一个 execute 方法,用来提交线程的执行。Executor 接口的子接口是 ExecutorService,负责管理线程的执行。通过 Executors 类的静态方法可以初始化 ExecutorService 线程池。Executors 类的静态方法可创建不同类型的线程池: 但是,不建议使用 Executors 去创建线程池,而是通过 ThreadPoolExecutor 的方式,明确给出线程池的参数去创建,规避资源耗尽的风险。 如果使用 Executors 去创建线程池: 最佳的实践是通过 ThreadPoolExecutor 手动地去创建线程池,选取合适的队列存储任务,并指定线程池线程大小。通过线程池实现类 ThreadPoolExecutor 可构造出线程池的,构造函数有下面几个重要的参数: 参数 1:corePoolSize 线程池核心线程数。 参数 2:workQueue 阻塞队列,用于保存执行任务的线程,有 4 种阻塞队列可选: 参数 3:maximunPoolSize 线程池最大线程数。如果阻塞队列满了(有界的阻塞队列),来了一个新的任务,若线程池当前线程数小于最大线程数,则创建新的线程执行任务,否则交给饱和策略处理。如果是无界队列就不存在这种情况,任务都在无界队列里存储着。 参数 4:RejectedExecutionHandler 拒绝策略,当队列满了,而且线程达到了最大线程数后,对新任务采取的处理策略。 有 4 种策略可选: 最后,还可以自定义处理策略。 参数 5:ThreadFactory 创建线程的工厂。 参数 6:keeyAliveTime 线程没有任务执行时最多保持多久时间终止。当线程池中的线程数大于 corePoolSize 时,线程池中所有线程中的某一个线程的空闲时间若达到 keepAliveTime,则会终止,直到线程池中的线程数不超过 corePoolSize。但如果调用了 allowCoreThread TimeOut(boolean value) 方法,线程池中的线程数就算不超过 corePoolSize,keepAlive Time 参数也会起作用,直到线程池中的线程数量变为 0。 参数 7:TimeUnit 配合第 6 个参数使用,表示存活时间的时间单位最佳的实践是通过 ThreadPoolExecutor 手动地去创建线程池,选取合适的队列存储任务,并指定线程池线程大小。 运行结果: 线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后,还会不断的去获取队列里的任务来执行。Worker 的加锁解锁机制是继承 AQS 实现的。 我们来看下 Worker 线程的运行过程: 总结来说,如果当前运行的线程数小于 corePoolSize 线程数,则获取全局锁,然后创建新的线程来执行任务如果运行的线程数大于等于 corePoolSize 线程数,则将任务加入阻塞队列 BlockingQueue 如果阻塞队列已满,无法将任务加入 BlockingQueue,则获取全局所,再创建新的线程来执行任务 如果新创建线程后使得线程数超过了 maximumPoolSize 线程数,则调用 Rejected ExecutionHandler.rejectedExecution() 方法根据对应的拒绝策略处理任务。 CPU 密集型任务,线程执行任务占用 CPU 时间会比较长,应该配置相对少的线程数,避免过度争抢资源,可配置 N 个 CPU+1 个线程的线程池;但 IO 密集型任务则由于需要等待 IO 操作,线程经常处于等待状态,应该配置相对多的线程如 2*N 个 CPU 个线程,A 线程阻塞后,B 线程能马上执行,线程多竞争激烈,能饱和的执行任务。线程提交 SQL 后等待数据库返回结果时间较长的情况,CPU 空闲会较多,线程数应设置大些,让更多线程争取 CPU 的调度。
2023-06-09 22:47:371

C语言 整型变量 括号

u200da+aa=a=aaaadsadsacfasdasdasdsadsadsadasdassssaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdasd
2023-06-09 22:47:455

java类内多个函数如何同步

楼上正解,试试吧
2023-06-09 22:48:107

并发编程 atomicboolean什么场景下适用

1.类AtomicBoolean  可以用原子方式更新的 boolean 值。有关原子变量属性的描述,请参阅 java.util.concurrent.atomic 包规范。AtomicBoolean 可用在应用程序中(如以原子方式更新的标志),但不能用于替换 Boolean。2.构造函数  1.AtomicBoolean()    使用初始值 false 创建新的 AtomicBoolean。  2.AtomicBoolean(boolean initialValue)    使用给定的初始值创建新的 AtomicBoolean。3.方法详解getpublic final boolean get()返回当前值。返回:
2023-06-09 22:48:421

初中化学原子公式。

在数理逻辑中,原子公式(Atomic formula)或原子是没有子公式的公式。把什么公式当作原子依赖于所使用的逻辑。例如在命题逻辑中,唯一的原子公式是命题变量。原子是在逻辑系统中"最小"的公式。在逻辑系统中的合式公式通常通过识别所有有效的原子公式,和给出从两个原子公式建立公式的规则而递归的定义。
2023-06-09 22:49:031

化学方程式的配平是怎么样的,怎么计算的,有那些是需要用到哪些的公式呢?

没有什么公式,就这边有几个那边也要有几个。
2023-06-09 22:49:214

想请教一下,多线程情况下BOOL变量的操作是原子性的吗

不是,跟一般变量一样,取值、改变、赋值
2023-06-09 22:49:281

变量用英语怎么说

  变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。变量可以通过变量名访问。在指令式语言中,变量通常是可变的;但在纯函数式语言(如Haskell)中,变量可能是不可变(immutable)的。那么你知道变量用英语怎么说吗?下面来学习一下吧。    变量英语说法1:   variable   变量英语说法2:   variate    变量英语例句:   为变量或符号确定一个值,或者确定此变量代表的内容。   Establishing a value for a variable or symbol or establishing what the variable represents.   应变量,函数一个与他量有关联的变量,这一量中的任何一值都能在他量中找到对应的固定值   A variable so related to another that for each value assumed by one there is a value determined for the other.   同时,我们还能对“哪些变量为外生变量”这样的问题,有更好的把握。   It is also possible to get a better idea of which variables are exogenous.   本质上,变量的操作性定义是如何或用什么办法测量变量的描述。   There may be more than one operational definition of a variable.   访问会话变量就和 其它 变量一样。   Session variables are accessible like any other variables.   您可以像输入普通变量那样输入伪变量。   You can enter a pseudovariable the same way you would enter a normal variable.   声明游标变量的名称,该变量接收游标输出。   Is the name of a declared cursor variable to receive the cursor output.   可以在脚本使用的变量称为脚本变量。   Variables that are used in scripts are called scripting variables.   为变量指定异常与为变量指定条件一样。   You assign exceptions to a variable just as you assign conditions to a variable.   用图表表示系统各个变量的变化情况   Diagramming the movement of a system"s variables   长期以来,科学 方法 一直认为,要想得到不同的结果,就要调整实验变量。   The scientific method has long held that to obtain a different outcome, one should change the experimental variables.   修改此变量对于确定在匹配单击时间签名时可以接受的可变性非常关键。   Modification of this variable is key to determining what amount of variability is acceptable during a click-time signature match.   使用db2set-g命令设置全局注册表变量。   Set the global registry variables using the db2set-g command.   对象有两个方面:成员变量和方法。   Objects have two aspects: member variables and methods.   您可以选择调用栈中的条目,这样可以显示变量被调用时的值。   Entries on the call stack can be selected, causing variables at the time of invocation to be displayed.   Scala没有静态的变量和方法,因此不能将它们直接关联到User类。   Scala does not have static variables and methods, so these cannot be associated directly to the User class.   可以将库添加到项目中,以提供特定于用户目标运行时环境的对象和变量集。   Libraries can be added to a project to provide object and variable sets specific to the users"target runtime environment.   持续性变量的字符串值。   aVariableValue, the string value of the persisted variable.   非阻塞算法简介:介绍如何使用原子变量而不是锁实现并发算法。   An introduction to nonblocking algorithms: Describes how concurrent algorithms can be implemented without locks, using atomic variables.   然后,JSP将会取得匹配变量名的值并显示在UI中。   After that, a JSP will get the values of matching variable names and display them on the UI.
2023-06-09 22:49:341

原子操作的实现原理

我们一起来聊一聊在Inter处理器和Java里是如何实现原子操作的。 32位IA-32处理器使用基于 对缓存加锁或总线加锁 的方式来实现多处理器之间的原子操作 首先处理器会自动保证基本的内存操作的原子性。 处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。 第一个机制是通过总线锁保证原子性。 如果多个处理器同时对共享变量进行读改写(i++就是经典的读改写操作)操作,那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一致,举个例子:如果i=1,我们进行两次i++操作,我们期望的结果是3,但是有可能结果是2。如下图 处理器使用总线锁就是来解决这个问题的。 所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。 “缓存锁定”指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不需要在总线上声言LOCK#信号,而是修改内部的内存地址,通过缓存一致性机制保证操作的原子性。 例外:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行,处理器会调用总线锁定。 在java中可以通过锁和循环CAS的方式来实现原子操作。 CAS ABA问题 循环时间长开销大 只能保证一个共享变量的原子操作 原子操作的实现原理 聊聊并发(五)原子操作的实现原理
2023-06-09 22:49:401

volatile为什么不能保证原子性

首先要了解的是,volatile可以保证可见性和顺序性,这些都很好理解,那么它为什么不能保证原子性呢? 可见性 可见性与Java的内存模型有关,模型采用缓存与主存的方式对变量进行操作,也就是说,每个线程都有自己的缓存空间,对变量的操作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量进行修改,那么这个线程拿到的值是主存中未被修改的值,这就是可见性的问题。 volatile很好的保证了变量的可见性,变量经过volatile修饰后,对此变量进行写操作时,汇编指令中会有一个LOCK前缀指令,这个不需要过多了解,但是加了这个指令后,会引发两件事情: 将当前处理器缓存行的数据写回到系统内存 这个写回内存的操作会使得在其他处理器缓存了该内存地址无效 什么意思呢?意思就是说当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,这就保证了可见性。 原子性 问题来了,既然它可以保证修改的值立即能更新到主存,其他线程也会捕捉到被修改后的值,那么为什么不能保证原子性呢? 首先需要了解的是,Java中只有对基本类型变量的赋值和读取是原子操作,如i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。 所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了。 举个栗子 一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作也就是i++,i++的过程可以分为三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。 线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也得到了i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。 问题来了,线程A已经读取到了i的值为100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存,所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。
2023-06-09 22:49:471

CAS 与原子操作

锁可以从不同的角度分类。其中,乐观锁和悲观锁是一种分类方式。 乐观锁: 乐观锁又称为“无锁”。乐观锁总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待。而一旦多个线程发生冲突,乐观锁通常是使用一种称为 CAS 的技术来保证线程执行的安全性。 由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说 乐观锁免疫死锁 。 乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;而悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。 悲观锁: 悲观锁就是我们常说的锁。对于悲观锁来说,它总是认为每次访问共享资源时会发生冲突,所以必须对每次数据操作加上锁,以保证临界区的程序同一时间只能有一个线程在执行。 在Java中可以通过锁和循环 CAS 的方式来实现原子操作。 CAS 的全称是:比较并交换(Compare And Swap)。在CAS中,有这样三个值: 比较并交换的过程如下: CAS 指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。 我们以一个简单的例子来解释这个过程: 在这个例子中,i就是V,5就是A,6就是B。 那有没有可能我在判断了 i 为5之后,正准备更新它的新值的时候,被其它线程更改了 i 的值呢? 不会的。因为CAS是一种原子操作,它是一种系统原语,是一条CPU的原子指令,从CPU层面保证它的原子性 当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。 CAS 的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。循环 CAS 就是在一个循环里不断的做 cas 操作,直到成功为止。 CAS 是怎么实现线程的安全呢? 我们将其交给硬件 — CPU 和内存,利用 CPU 的多处理能力,实现硬件层面的阻塞,再加上 volatile 变量的特性即可实现基于原子操作的线程安全。 CAS 是一种无锁算法,通过硬件层面上对先后操作内存的线程进行排队处理,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 CAS(比较并交换)是CPU指令级的操作, 只有一步原子操作,所以非常快 。而且避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了 1、ABA 问题 CAS 在操作的时候会检查变量的值是否被更改过,如果没有则更新值,但是带来一个问题,最开始的值是A,接着变成B,最后又变成了A。经过检查这个值确实没有修改过,因为最后的值还是A,但是实际上这个值确实已经被修改过了。为了解决这个问题,在每次进行操作的时候加上一个 版本号 ,每次操作的就是两个值,一个版本号和某个值,A——>B——>A问题就变成了1A——>2B——>3A。在 jdk 中提供了 AtomicStampedReference 类解决ABA问题,用Pair这个内部类实现,包含两个属性,分别代表版本号和引用。 这个类的 compareAndSet 方法的作用是首先检查当前引用是否等于预期引用,并且检查当前版本号标志是否等于预期版本号标志,如果二者都相等,才使用CAS设置为新的值和标志。 2、循环时间长开销大 自旋 CAS 如果长时间不成功,会占用大量的 CPU 资源,给 CPU 带来非常大的执行开销。 解决思路是让 JVM 支持处理器提供的 pause 指令 。 pause 指令 能让自旋失败时 cpu 睡眠一小段时间再继续自旋,从而使得读操作的频率低很多,为解决内存顺序冲突而导致的CPU流水线重排的代价也会小很多。 3、只能保证一个共享变量的原子操作 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。 还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比 如,有两个共享变量 i=2,j=a,合并一下 ij=2a,然后用 CAS 来操作 ij。从 Java 1.5 开始,JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行 CAS 操作。 解决方案: AtomicInteger 实例: 打印结果:12。 ai.compareAndSet(10, 12); 改为 ai.compareAndSet(11, 12);时,打印结果:10 AtomicIntegerArray 主要是提供原子的方式更新数组里的整型,其常用方法如下。 需要注意的是,数组 value 通过构造方法传递进去,然后 AtomicIntegerArray 会将当前数组复制一份,所以当 AtomicIntegerArray 对内部的数组元素进行修改时,不会影响传入的数组。 实例 打印结果: 原子更新基本类型的 AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic 包提供了以下 3 个类。 **AtomicReference ** 原子更新引用类型。 实例: 打印结果: AtomicStampedReference 利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在 ABA 问题了。这就是 AtomicStampedReference 的解决方案。AtomicMarkableReference 跟 AtomicStampedReference 差不多,AtomicStampedReference 是使用 pair 的 int stamp 作为计数器使用,AtomicMarkableReference 的 pair 使用的是 boolean mark。 AtomicStampedReference 可能关心的是动过几次, AtomicMarkableReference 关心的是有没有被人动过,方法都比较简单。 实例: 打印结果: AtomicMarkableReference 原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是 AtomicMarkableReference(V initialRef,boolean initialMark)。 如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类, Atomic 包提供了以下 3 个类进行原子字段更新。 要想原子地更新字段类需要两步。第一步,因为原子更新字段类都是抽象类, 每次使用的时候必须使用静态方法 newUpdater() 创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新类的字段(属性)必须使用 public volatile 修饰符。 AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。 AtomicLongFieldUpdater: 原子更新长整型字段的更新器。 AtomicReferenceFieldUpdater: 原子更新引用类型里的字段。
2023-06-09 22:49:581

对于双原子分子和三原子,其能量的变量有几个

因为双原子分子只能产生一条直线,且一条直线产生的平面只可以产生x、y两维坐标,所以有两个自由度。而多原子分子,最多组成的是立体坐标,即只比平面两维坐标多出一个z方向的自由度,所以只有三个自由度,即只能产生xyz的自由度。
2023-06-09 22:50:081

linux如何保护共享资源

主要有一下几种:中断屏蔽、 原子操作、自旋锁、信号量、环形缓冲区。在本文中对于这些机制的具体的实现函数,以及原理不再做任何的表述。本 一、原子变量 假设我们所需要保护的共享资源只是一个整数值,此时我们可以采用的机制有自旋锁,信号量,和原子变量,当然中断屏蔽也是可以的。但是如果选择最优的机制,我们应该选择原子变量。原因:1.对一个整数值的操作是很简单的,也就是说此对全局变量形成的临界区是很小的.如果我们采用其他的机制,例如锁的机制,信号量等就显得有些浪费.也就是说,你的锁机制的代码量可能比临界区的代码量还要多.2. 对一个缺乏经验的程序员来讲,由于思维缺乏逻辑,使用锁机制会存在很多潜在的风险.例如: 死锁.等问题;而采用原子变量的方法,可以避免锁机制产生的弊端. 二、自旋锁自旋锁机制和信号量机制都可以对资源进行互斥访问,但是从性能上讲,自旋锁的性能优于信号量。但是自旋锁机制本身在不停的自旋(也就是查询锁是否可用),导致此机制有一些缺点:1。假定我们的驱动程序获得了一个自旋锁,在临界区开始了他的工作期间,驱动程序丢掉了处理器。也许他调用了一个函数,这个函数使进程休眠。也许发生了内核的抢占。但是我们的代码拥有这个自旋锁,如果其他的进程想要获得此自旋锁,需要等很长时间,甚至造成死锁。所以我们应用自旋锁时应遵循:任何拥有自旋锁的代码必须是原子的。所以他不能休眠,所以不能调用能够引起休眠的函数。例如:copy_to_user,copy_from_user,kmalloc等。也不能因为任何原因放弃处理器,除了中断以外(有时中断也不行,可以采用禁止中断的自旋锁函数操作)。 三、信号量拥有信号量的进程是可以休眠的。这也正是他对于自旋锁的优势。 其他的暂不论述。
2023-06-09 22:50:321

linux内核中的atomic_read与atomic_set函数的功能是什么

1.atomic_read与atomic_set函数是原子变量的操作,就是原子读和原子设置的作用.2.原子操作,就是执行操作的时候,其数值不会被其它线程或者中断所影响3.原子操作是linux内核中一种同步的方式
2023-06-09 22:50:451

关于线程安全问题分析?

在学习java编程开发语言的过程中,我们掌握了线程与线程池等相关技术知识。今天,北大青鸟北京计算机学院就关于线程安全问题给大家做一个简单的说明和介绍,一起来了解一下吧。线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。什么时候考虑到线程安全:一个对象是否需要线程安全,取决于该对象是否被多线程访问。这指的是程序中访问对象的方式,而不是对象要实现的功能。要使得对象是线程安全的,要采用同步机制来协同对对象可变状态的访问。Java常用的同步机制是Synchronized,还包括volatile类型的变量,显示锁以及原子变量。在多个线程中,当它们同时访问同个类时,每次执行的结果和单线程结果一致,且变量值跟预期一致,这个类则是线程安全的。锁的特性锁机制的两种特性:互斥性:即同一时间只允许一个线程持有某个对象的锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的,否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。挂起、休眠、阻塞和非阻塞挂起:当线程被挂起时,其会失去CPU的使用时间,直到被其他线程(用户线程或调试线程)唤醒。休眠:同样是会失去CPU的使用时间,但是在过了指定的休眠时间之后,它会自动激活,无需唤醒(整个唤醒表面看是自动的,但实际上也得有守护线程去唤醒,只是不需编程者手动干预)。阻塞:在线程执行时,所需要的资源不能得到,则线程被挂起,直到满足可操作的条件。非阻塞:在线程执行时,所需要的资源不能得到,则线程不是被挂起等待,而是继续执行其余事情,等待条件满足了后,收到了通知(同样是守护线程去做)再执行。
2023-06-09 22:50:511

关于线程安全问题分析?

在学习java编程开发语言的过程中,我们掌握了线程与线程池等相关技术知识。今天,北大青鸟昌平计算机学院就关于线程安全问题给大家做一个简单的说明和介绍,一起来了解一下吧。线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。什么时候考虑到线程安全:一个对象是否需要线程安全,取决于该对象是否被多线程访问。这指的是程序中访问对象的方式,而不是对象要实现的功能。要使得对象是线程安全的,要采用同步机制来协同对对象可变状态的访问。Java常用的同步机制是Synchronized,还包括volatile类型的变量,显示锁以及原子变量。在多个线程中,当它们同时访问同个类时,每次执行的结果和单线程结果一致,且变量值跟预期一致,这个类则是线程安全的。锁的特性锁机制的两种特性:互斥性:即同一时间只允许一个线程持有某个对象的锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的,否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。挂起、休眠、阻塞和非阻塞挂起:当线程被挂起时,其会失去CPU的使用时间,直到被其他线程(用户线程或调试线程)唤醒。休眠:同样是会失去CPU的使用时间,但是在过了指定的休眠时间之后,它会自动激活,无需唤醒(整个唤醒表面看是自动的,但实际上也得有守护线程去唤醒,只是不需编程者手动干预)。阻塞:在线程执行时,所需要的资源不能得到,则线程被挂起,直到满足可操作的条件。非阻塞:在线程执行时,所需要的资源不能得到,则线程不是被挂起等待,而是继续执行其余事情,等待条件满足了后,收到了通知(同样是守护线程去做)再执行。
2023-06-09 22:50:581

关于线程安全问题分析?

在学习java编程开发语言的过程中,我们掌握了线程与线程池等相关技术知识。今天,北大青鸟昌平计算机学院就关于线程安全问题给大家做一个简单的说明和介绍,一起来了解一下吧。线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。什么时候考虑到线程安全:一个对象是否需要线程安全,取决于该对象是否被多线程访问。这指的是程序中访问对象的方式,而不是对象要实现的功能。要使得对象是线程安全的,要采用同步机制来协同对对象可变状态的访问。Java常用的同步机制是Synchronized,还包括volatile类型的变量,显示锁以及原子变量。在多个线程中,当它们同时访问同个类时,每次执行的结果和单线程结果一致,且变量值跟预期一致,这个类则是线程安全的。锁的特性锁机制的两种特性:互斥性:即同一时间只允许一个线程持有某个对象的锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的,否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。挂起、休眠、阻塞和非阻塞挂起:当线程被挂起时,其会失去CPU的使用时间,直到被其他线程(用户线程或调试线程)唤醒。休眠:同样是会失去CPU的使用时间,但是在过了指定的休眠时间之后,它会自动激活,无需唤醒(整个唤醒表面看是自动的,但实际上也得有守护线程去唤醒,只是不需编程者手动干预)。阻塞:在线程执行时,所需要的资源不能得到,则线程被挂起,直到满足可操作的条件。非阻塞:在线程执行时,所需要的资源不能得到,则线程不是被挂起等待,而是继续执行其余事情,等待条件满足了后,收到了通知(同样是守护线程去做)再执行。
2023-06-09 22:51:041

java一些能降低竞争锁的方法?

本文介绍一下提升并发可伸缩性的一些方式:减少锁的持有时间,降低锁的粒度,锁分段、避免热点域以及采用非独占的锁或非阻塞锁来代替独占锁。减少锁的持有时间降低发生竞争可能性的一种有效方式就是尽可能缩短锁的持有时间。例如,可以将一些与锁无关的代码移出同步代码块,尤其是那些开销较大的操作,以及可能被阻塞的操作,例如I/O操作。降低锁的粒度另一种减小锁的持有时间的方式是降低线程请求锁的频率(从而减小发生竞争的可能性)。这可以通过锁分解和锁分段等技术来实现,在这些技术中将采用多个相互独立的锁来保护独立的状态变量,从而改变这些变量在之前由单个锁来保护的情况。这些技术能减小锁操作的粒度,并能实现更高的可伸缩性,然而,使用的锁越多,那么发生死锁的风险也就越高。锁分段在某些情况下,可以将锁分解技术进一步扩展为对一组独立对象上的锁进行分解,这种情况被称为锁分段。例如,在ConcurrentHashMap的实现中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(Nmod16)个锁来保护。假设散列函数具有合理的分布性,并且关键字能够实现均匀分布,那么这大约能把对于锁的请求减少到原来的1/16。正是这项技术使得ConcurrentHashMap能够支持多达16个并发的写入器。(要使得拥有大量处理器的系统在高访问量的情况下实现更高的并发性,还可以进一步增加锁的数量,但仅当你能证明并发写入线程的竞争足够激烈并需要突破这个限制时,才能将锁分段的数量超过默认的16个。)避免热点域如果一个锁保护两个独立变量X和Y,并且线程A想要访问X,而线程B想要访问Y(这类似于在ServerStatus中,一个线程调用addUser,而另一个线程调用addQuery),那么这两个线程不会在任何数据上发生竞争,即使它们会在同一个锁上发生竞争。当每个操作都请求多个变量时,锁的粒度将很难降低。这是在性能与可伸缩性之间相互制衡的另一个方面,一些常见的优化措施,例如将一些反复计算的结果缓存起来,都会引入一些“热点域(HotField)”,而这些热点域往往会限制可伸缩性。当实现HashMap时,你需要考虑如何在size方法中计算Map中的元素数量。最简单的方法就是,在每次调用时都统计一次元素的数量。一种常见的优化措施是,在插入和移除元素时更新一个计数器,虽然这在put和remove等方法中略微增加了一些开销,以确保计数器是最新的值,但这将把size方法的开销从O(n)降低到O(l)。代替独占锁第三种降低竞争锁的影响的技术就是放弃使用独占锁,从而有助于使用一种友好并发的方式来管理共享状态。例如,使用并发容器、读-写锁、不可变对象以及原子变量。北京北大青鸟发现ReadWriteLock能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要加锁操作。
2023-06-09 22:51:111

linux c 什么时候用到 atomic

1.atomic_read与atomic_set函数是原子变量的操作,就是原子读和原子设置的作用. 2.原子操作,就是执行操作的时候,其数值不会被其它线程或者中断所影响 3.原子操作是linux内核中一种同步的方式
2023-06-09 22:51:201

原子公式是怎么样的啊??

物理定理、定律、公式表 物理定理、定律、公式表 http://zhidao.baidu.com/question/18473318.html
2023-06-09 22:51:291

Java 中对引用类型的变量赋值是原子操作吗

由一个简单的例子引出并发处理时容易被忽视的陷阱,用来作为面试问题应该很适合。某日,工作了 4 年多的 Java 程序员小 K 跳槽,面试时碰到这样一个题目....public class P1 { private long b = 0; public void set1() { b = 0; } public void set2() { b = -1; } public void check() { System.out.println(b); if (0 != b && -1 != b) { System.err.println("Error"); } }}问题调用 set1()、set2()、check(),会打印出 Error 么?小K 的推理“无论如何调用 set1()、set2() -> b 的值只可能是 0 或 -1 -> 在 check() 里面的判断条件(b 既不为 0 也不为 -1)永远不成立 -> 不打印 Error”小 K 觉得有坑:这题目应该不会这么简单,再考虑一下多线程环境。
2023-06-09 22:51:462

volatile为什么不能保证原子性

首先要了解的是,volatile可以保证可见性和顺序性,这些都很好理解,那么它为什么不能保证原子性呢?可见性可见性与Java的内存模型有关,模型采用缓存与主存的方式对变量进行操作,也就是说,每个线程都有自己的缓存空间,对变量的操作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量进行修改,那么这个线程拿到的值是主存中未被修改的值,这就是可见性的问题。volatile很好的保证了变量的可见性,变量经过volatile修饰后,对此变量进行写操作时,汇编指令中会有一个LOCK前缀指令,这个不需要过多了解,但是加了这个指令后,会引发两件事情:将当前处理器缓存行的数据写回到系统内存这个写回内存的操作会使得在其他处理器缓存了该内存地址无效什么意思呢?意思就是说当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,这就保证了可见性。原子性问题来了,既然它可以保证修改的值立即能更新到主存,其他线程也会捕捉到被修改后的值,那么为什么不能保证原子性呢?首先需要了解的是,Java中只有对基本类型变量的赋值和读取是原子操作,如i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了。举个栗子一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作也就是i++,i++的过程可以分为三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也得到了i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。问题来了,线程A已经读取到了i的值为100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存,所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。
2023-06-09 22:52:051

java中用volatile修饰count变量执行count++是不是原子性操作

不是原子操作,不能保证线程安全,volatile只能保证可见性和禁止指令重排
2023-06-09 22:52:191

命题逻辑证明的后缀

P。命题逻辑是指以逻辑运算符结合原子命题来构成代表“命题”的公式,以及允许某些公式建构成“定理”的一套形式“证明规则”。用符号P代表任意逻辑命题,它由几种不同语法形式组成:符号T和⊥分别代表两个逻辑常量“真和“假”;小写符号x代表一个原子命题变量;这三种语法形式都是基本的,因此可称它们为原子命题。
2023-06-09 22:52:261

c++的静态类型变量初始化是原子操作吗

静态变量初始化是编译时就完成,所以应该不存在多线程访问吧,你可以当成原子操作吧。
2023-06-09 22:52:331

cas为什么只能保证一个共享变量的原子操作

我是用c和c++的 至于synchronized 修饰的作用不太清楚,不过原子操作对所有语言来说都一样 所谓原子操作,就是cpu在一个时间片内可以完成的操作。 主要用在多线程编程上,2个线程对同一段内存地址进行读写操作,如果用的不是原子操作
2023-06-09 22:52:411

c++ 中如何声明一个 int 变量

原子类型的变量一般都不需要 纯声明式声明(楼主说的定义是 定义式声明)差别在于 声明(通知编译器,此资源存在,后续工作由连接器处理) 定义(通知编译器,此资源不存在,但是将会使用,请分配【不仅指分配内存,还包括在维护表中生成类体等】)。所以 C++ 中的声明int 变量差不多(其他方式恕我才疏学浅)只有一种,那就是非本文件的成员,或是说,声明部分前面已经有对该变量的引用。可以使用 extern 关键字声明一下。(作用只是告诉编译器,关于此变量不要报错)
2023-06-09 22:52:481

synchronize具有可见性吗

volatile是线程同步的轻量级实现,所以volatile的性能要比synchronize好;volatile只能用于修饰变量,synchronize可以用于修饰方法、代码块。随着jdk技术的发展,synchronize在执行效率上会得到较大提升,所以synchronize在项目过程中还是较为常见的;2.多线程访问volatile不会发生阻塞;而synchronize会发生阻塞;3.volatile能保证变量在私有内存和主内存间的同步,但不能保证变量的原子性;synchronize可以保证变量原子性;4.volatile是变量在多线程之间的可见性;synchronize是多线程之间访问资源的同步性;对于volatile修饰的变量,可以解决变量读时可见性问题,无法保证原子性。对于多线程访问同一个实例变量还是需要加锁同步。
2023-06-09 22:52:551