Java并发编程艺术学习笔记(二)

java并发编程的艺术 《JAVA并发编程艺术》 阅读详情

Java并发编程艺术学习笔记(二)

Java内存模型

在《深入理解JVM》中已经学习了一些关于java内存模型的知识,在并发编程中关于java内存模型写的更详细,再做次总结有助于理解深刻。

一.Java内存模型基础

Ⅰ.并发编程模型的两个关键问题

两个关键问题是线程之间如何通信以及线程之间如何同步。
在共享内存的并发模型中,线程之间共享程序的公共状态,通过修改公共状态来隐式通信。
在消息传递的并发模型中,线程之间没有公共状态,线程之间通过发送消息来显式地进行通信。
同步是控制不同线程间操作发生相对顺序的机制,共享内存模型中同步是显式进行的,必须显式制定某个方法或者某段代码需要在线程之间互斥执行。在消息传递的并发模型中,由于消息发送必须在消息的接收之前,因此同步是隐形的。
Java并发采用的是共享内存的并发模型。

Ⅱ.Java内存模型的抽象结构

JVM中栈帧是线程独有的,不会有内存可见性问题,而Java堆是线程共享的,因此会存在线程可见性问题。
JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,而每个线程都有私有的本地内存,其中存储着线程读/写共享变量的副本。
线程A与线程B之间要通信的话,需要经历下面2个步骤:
①线程A把本地内存A中更新过的共享变量更新到主内存中。
②线程B到主内存中读取线程A之前已经更新过的共享变量。
JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。

Ⅲ.从源代码到指令序列的重排序

在执行程序时,为了提高性能,编译器和处理器会对指令做重排序,分成以下三种:
①编译器优化的重排序。
②指令级并行的重排序。现在处理器采用指令级并行技术将多条指令重叠执行,如果不存在数据依赖性,处理器可以改变语句对应的机器指令的执行顺序。
③内存系统的重排序。处理器使用缓存和读/写缓冲区,使得加载和存储看起来可能是乱序执行。
2和3都属于处理器的重排序。这些重排序都会造成多线程程序出现内存可见性问题,对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序,对于处理器重排序,JMM会要求Java编译器在生成指令序列时,插入特定类型的内存屏障,通过内存屏障可以禁止特定类型的处理器重排序。

Ⅳ.并发编程模型的分类

处理器一般需要写缓冲区来保证指令流水线可以持续运行,但需要注意的是每个处理器的写缓冲区只可以对这个处理器可见,这会产生严重的后果:处理器对于内存的读/写操作顺序不一定与内存实际发生的读/写操作一致。处理器写操作认为写入缓冲区就完成了,但是对于内存来说只有从缓冲区写入内存中才算完成。
由于现代的处理器都会使用写缓冲区,因此现代的处理器都会允许对于写-读操作进行重排序。
为了保证内存可见性,Java编译器会在合适的位置插入内存屏障来禁止默写特定类型的处理器重排序。
JMM把内存屏障分成四类,如下图:
在这里插入图片描述
写读屏障是个“全能型”屏障,但是开销很大。

Ⅴ.happens-before简介

JDK5以后,JSR-133内存模型被采用,采用了全新的happens-before来阐述内存可见性,如果一个操作执行的结果需要对另一个操作可见,那么两个操作必须存在happens-before关系。
与程序员密切相关的一些happens-before规则:
①程序顺序规则:一个线程的每个操作before于后面的任何操作。
②监视器锁规则:对一个锁的解锁一定before与随后对这个锁的加锁。
③volatile变量规则:对一个volatile的写一定before后面任何对于volatile的读。
④传递性:AbeforeB,BbeforeC,那么A一定beforeC。
**happens-before不意味着前一个操作必须在后一个操作前运行,而仅仅要求前一个的结果对后一个操作可见。
happens-before的这四个定义意味着java程序员不需要为了这四个基础规则再去学习重排序规则,JMM实现默认了这四条基本规则,但是如果还要再加规则,就需要程序员额外添加。

二.重排序

重排序是指编译器和处理器为了优化性能而对指令序列重新排序的一种手段。

Ⅰ.数据依赖性

数据依赖性仅仅考虑了单个处理器执行的指令序列和单个线程执行的操作。而线程间的是不被考虑的。
主要有三种:写后读,写后写,读后写

Ⅱ.as-if-serial语义

这种语义把单线程程序保护了起来,保证了结果的准确性。

Ⅲ.程序顺序规则

A happens-before B,如果操作A的执行结果并不需要对于B可见,而且重排序之后的执行结果跟之前一样,那么JMM会认为这种重排序并不非法。

Ⅳ.重排序对于多线程的影响

重排序可能改变程序的执行结果。

三.顺序一致性

顺序一致性内存模型是一个理论参考模型,设计的时候,处理器的内存模型和编程语言内存模型都会以顺序一致性内存模型作为参考。

Ⅰ.数据竞争与顺序一致性

程序未正确同步时,可能会存在线程竞争。如果程序是正确同步的,程序的执行将具有顺序一致性----即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。

Ⅱ.顺序一致性内存模型

模型是一个被理想化的模型,给程序员提供了极强的内存可见性保证,顺序一致性内存模型有两大特征:
(1)一个线程中的所有操作必须按程序的顺序来执行。
(2)(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序,在顺序一致性内存模型中,每个操作都必须原子执行并且立刻对所有线程可见。
未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有线程都只能看到一个一致的整体执行顺序。但是在JMM中未同步不但整体是无序的,所有线程自己也是无序的。

Ⅲ.同步程序的顺序一致性效果

同步程序可以保证线程间的顺序关系,但是仍然不能保证临界区中代码的顺序执行,这给了编译器和处理器的优化提供了便利的条件。

Ⅳ.未同步程序的执行特性

对于未同步的程序,JMM只提供了最小的安全性:线程执行时读取到的值要么是之前某个线程写入的值,要么是默认值。未同步程序在JMM执行时,整体上是无序的,执行结果无法预知,未同步程序在两个模型执行特性有如下几个差异:
(1)顺序一致性模型保证了单个线程中是顺序执行的,而JMM中不保证。
(2)顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM中不保证。
(3)JMM中不保证对于64位long和double变量的写操作具有原子性,而顺序一致性模型保证了。
从JSR-133内存模型(JDK5)开始,仅仅把一个long/double写操作拆分成两个32位的写操作来执行,任意的读操作必须具有原子性。

四.volatile的内存语义

Ⅰ.volatile的特性

volatile的特性可以理解成对于volatile变量的读写看做是同一个锁对于这些单个读写操作进行了同步。
1.可见性
2.原子性,单个变量具有原子性,但是对于v++这种复合变量没有原子性。

Ⅱ.volatile写-读建立的happens-before关系

从JSR-133开始,volatile的写读可以实现线程之间的通信。从内存语义来看,volatile的写读与锁的释放-获取有同样的效果。
所以volatile变量规则相当于就是监听器锁规则。

Ⅲ.volatile写-读的内存语义

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量更新回主内存。
当读一个volatile变量时,JMM会把该线程对应的本地内存设为无效,线程接下来将从主内存中读取共享变量。
**个人理解:volatile读=工作内存更新+普通读
volatile写=普通写+主内存更新

Ⅳ.volatile内存语义的实现

JMM针对编译器制定了volatile重排序规则表:
在这里插入图片描述
(1)第二个操作是volatile写时,无论第一个操作是什么,都不能重排序。
(2)当第一个操作是volatile读时,无论第二个操作是什么,都不能重排序。
(3)第一个操作是volatile写时,第二个操作是volatile读时也不能重排序。
基于保守策略JMM内存屏障插入策略:
(1)每个volatile写操作前插入一个StoreStore屏障。
(2)每个volatile写操作后插入一个StoreLoad屏障。
(3)每个volatile读操作后插入一个LoadStore屏障。
(4)每个volatile读操作后插入一个LoadLoad屏障。
编译器在生成字节码的时候可能会做些优化,省去一些不必要的内存屏障。

Ⅴ.JSR-133为什么要增强volatile的内存语义

因为JSR-133之前并没有禁止volatile变量和普通变量的重排序,因此会出现上述(1)(2)的问题。
volatile只能保证单个volatile变量的读/写原子性,而锁能保证整个临界区的原子性,因此锁更强大,但是消耗也更大。

五.锁的内存含义

Ⅰ.锁的释放-获取建立的happens-before关系

锁除了可以让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。
锁用到的happens-before关系是上述的监听器锁关系,及上一个锁的释放happens-before下一次锁的加锁。

Ⅱ.锁的释放和获取的内存语义

线程获取锁时,JMM会把线程对应的本地内存设为无效,必须从主内存中读取共享变量。
线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
获取锁和释放锁的内存语义跟volatile相同。

Ⅲ.锁内存语义的实现

借助ReentrantLock源代码来分析内存语义的实现机制。
ReentrantLock分为公平锁和非公平锁,公平锁加锁lock():
(1)ReentrantLock:lock();
(2)FairSync:lock();
(3)AbstractQueuedSynchronizer:acquire(int arg);
(4)ReentrantLock:tryAcquire(int acquires)。
第四步才是真正的加锁,源码是:

        final boolean TryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

加锁的过程首先会获取state值,如果是0说明没有现成获得锁就会执行加锁操作,如果>0说明已经有线程占据锁,那么根据重入锁的规则,如果加锁的线程就是当前线程,就会状态值加1,如果不是就会阻塞。
解锁方法unlock()调用轨迹如下:
(1)ReentrantLock:unlock()
(2)AbstractQueuedSynchronize:release(int args)
(3)Sync:tryRelease(int releases)
真正解锁的是第三步,源代码是:

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

首先获取当前锁的state值,与解锁次数相减得到局部变量c,如果发送解锁指令的线程不是当前拥有的锁的线程就会抛出错误,如果c=0,意味着解锁操作后该对象将没有锁,这时候将拥有该锁的对象设为null,如果c!=0,就把state设为c。
需要注意的时候加锁解锁的过程中,都是通过state变量来判断当前对象是否加锁,而state使用volatile修饰的保证了内存的可见性。
非公平锁和公平锁解锁过程的内存语义完全一样,而非公平锁的获取过程有所不同,加锁方法lock()调用轨迹:
(1)ReentrantLock:lock()。
(2)NonfairSync:lock()。
(3)AbstractQueuedSynchronizer:compareAndSetState(int expect,int update)。
第三步是真正加锁过程,源码是:

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

该方法用原子操作CAS来更新state变量,先从编译器和处理器的角度来分析下CAS如何同时具有volatile读和volatile写的内存语义。
编译器和处理器不会对volatile读以后做任何重排序,也不会volatile写前面的做任何重排序,那么CAS就需要不能对CAS前面以及CAS后面做任何的重排序。
程序会根据当前处理器的类型来判断是否为cmpxchg指令添加lock前缀(Lock Cmpxchg)。如果是多处理器运行的,就需要加上lock前缀,单处理器就不需要lock前缀。
lock的几点说明:
(1)确保了对内存读-改-写都具有原子执行。在之前主要通过总线锁定,现在有内存锁定来减少执行开销。
(2)禁止该指令与前后指令重排序。
(3)把写缓冲区的所有数据同步到内存中。
第二三点具有的内存屏障效果足够同时实现volatile读和volatile写的内存语义。
公平锁和非公平锁的内存语义总结:
(1)公平锁和非公平锁释放都需要写一个volatile变量state。
(2)公平锁获得时,首先会去读volatile变量。
(3)非公平锁获得时,首先会用CAS去更新volatile变量。
锁的获得与释放实现至少有两种方式:
(1)volatile变量的写和读的内存语义。
(2)CAS附带的volatile读和volatile写的内存语义。

Ⅳ.concurrent包的实现

java线程之间的通信有了下面四种方式:
(1)A写volatile变量,B读volatile变量。
(2)A写volatile变量,BCAS这个变量。
(3)ACAS变量,BCAS变量。
(4)ACAS变量,B读volatile变量。
concurrent包中有一种通用的实现方法:
(1)声明共享变量为volatile。
(2)CAS更新来实现线程之间的同步。
(3)配合使用volatile的特性和CAS的特性来实现线程之间的通信。

六.final域的内存语义

final域的读和写更像是普通变量的访问。

Ⅰ.final域的重排序规则

final域中编译器和处理器需要遵守两个重排序规则:
(1)构造函数中对于final域的写入和随后被构造对象的引用赋值给一个引用变量,这两个操作不能重排序。
(2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作不能重排序。

Ⅱ.写final域的重排序规则

写final域的重排序规则禁止把final域的写重排序到构造函数之外,实现包括两个方面:
(1)JMM禁止编译器把final域的写重排序到构造函数之外。
(2)编译器会在final域的写之后,构造函数return之前,插入一个storestore屏障,这个屏障禁止处理器把final域的写重排序到构造函数之外。
写final域的重排序规则保证了在对象引用为任意线程可见前,对象的final已经被正确初始化过了。

Ⅲ.读final域的重排序规则

读final域的重排序规则是在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止了处理器重排序这两个操作。
编译器会在读final域操作的前面插入一个LoadLoad屏障。
读final域的重排序规则可以保证:在读一个对象的final域之前,一定先读包含这个final域的对象的引用。

Ⅳ.final域为引用类型

对于引用类型,写final域的重排序规则堆编译器和处理器加了如下的约束:
在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作不能重排序。

Ⅴ.为什么final引用不能从构造函数中“溢出”

在构造函数内部,不能让这个被构造对象的引用为其他线程所见。即如果想要final引用一定是在对象被引用时已经构造完成,就不能在构造函数中使用obj=this这样的指令使得其溢出。

Ⅵ.final语义在处理器中的实现

由于有些处理器并不会对一些操作进行重排,例如X86不会对写-写操作进行重排,也不会对存在间接依赖关系的操作重排序,因此final域在X86中的读写实际上不需要插入任何内存屏障。

Ⅶ.JSR-133为什么要增强final的语义

因为旧的java内存模型中严重缺陷就是可能看到final修饰的值会改变。增强后确保了只要对象是安全构建的并且其中没有对象溢出,那么可以不用锁也可以保证final在构造函数中已经初始化。

七.happens-before

对于程序员来说,理解happens-before是理解JMM的关键。

Ⅰ.JMM的设计

设计JMM时候,需要考虑的两个关键因素:
(1)程序员对于内存模型的使用。程序员希望内存模型易于理解、编程,甚至希望一个强内存模型来编写代码。
(2)编译器和处理器对内存模型的实现。编译器和处理器希望内存模型的约束越少越好,可以增加更多的优化。
JMM对于happens-before要求禁止的重排序分成了下面两类:
(1)会改变程序执行结果的重排序。(JMM要求编译器和处理器必须禁止这种重排序)
(2)不会改变程序执行结果的重排序。(JMM允许这种重排序)
也可以得出两点结论:
(1)JMM向程序员提出的happens-before规则简单易于,确保了足够强的内存可见性。但是有些内存可见性的保证并不一定存在。
(2)JMM对编译器和处理器的束缚已经尽可能少。

Ⅱ.happens-before的定义

JSR-133使用happens-before的概念来指定两个操作之间的执行顺序。给程序员提供了跨线程的内存可见性保证。具体定义如下:
(1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
(2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须按照关系指定的顺序进行,如果重排序后的执行结果不改变,那么也是允许的。
(1)是对程序员的承诺,(2)是JMM对编译器和处理器重排序的约束原则。
因此happens-before关系本质上和as-if-serial语义是一回事。
(1)as-if-serial保证了单线程结果不被改变,而happens-before保证了正确同步的多线程程序执行结果不被改变。
(2)as-if-serial给程序员带来了幻境:单线程都是按照程序的顺序来执行的;happens-before给程序员带来了幻境:正确同步的多线程都是按照happens-before顺序来完成的。

Ⅲ.happens-before规则

前面已经讲到了4个规则,这里再总结一下:
(1)程序顺序规则
(2)监视器锁规则
(3)volatile变量规则
(4)传递性
(5)start()规则:如果线程A执行操作ThreadB.start(),那么线程A的ThreadB.start()操作happens-before线程B中的任意操作。
(6)join()规则:如果线程A执行操作ThreadB.join()并且成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

八.双重检查锁定与延迟初始化

java多线程程序当中,需要采用延迟初始化来降低初始化类和创建对象的开销。双重锁定检查是常见的延迟初始化技术,但是这是一个错误的用法。接下来分析双重检查锁定为什么错误,以及两种线程安全的延迟初始化方案。

Ⅰ.双重检查锁定的由来
public class DoubleCheckedLocking {
    private static Instance instance;
    public static Instance getInstance(){
        if (instance==null){
            synchronized (DoubleCheckedLocking.class){
                if (instance==null){
                    instance=new Instance();
                }
            }
        }
        return instance;
    }
}

表面上看起来很完美:多线程状态时,在对象还没有创始前,多个线程会通过第一个检查,在第二次检查的时候只要给第一个线程创建新对象,剩下的线程都会通不过第二个线程而返回。
但是问题出在代码读取第一次检查instance!=null时,instance引用的对象可能还没有完成初始化。

Ⅱ.问题的根源

问题的根源出在instance=new Instance()中:在编译器中这会转成3个伪代码
(1)memory=allocate();/分配对象的内存空间
(2)ctorInstance(memory);/初始化对象
(3)instance=memory;/设置instance指向刚分配的内存地址
而2和3可能被重排序,因此可能出现instance!=null而对象却没有初始化。
因此可以由两个办法来实现线程安全的延迟初始化:
(1)不允许2,3重排序。
(2)允许2,3重排序,但是不允许其他线程看到这个重排序。

Ⅲ.基于volatile的解决方案

可以将instance声明成volatile类型,就可以实现线程安全的进行类初始化。
本质上就是禁止了2和3之间的重排序。

Ⅳ.基于类初始化的解决方案

JVM在类的初始化阶段(Class被加载,在被线程使用之前),会执行类的初始化,而类的初始化阶段JVM会自动给与一个锁,这个锁可以同步多个线程对于一个类的初始化。
因此可以通过以下代码来实现:

public class InstanceFactory {
    private static class InstanceHolder{
        public static Instance instance=new Instance();
    }
    private static Instance getInstance(){
        return InstanceHolder.instance;
    }
}

这个方案的本质是:允许2,3重排序,但是其他线程看不到。
在JVM规范中,只要以下的任意一种情况发生,那么这个类或者接口类型T将被立刻初始化:
(1)T是一个类,而且T类型的实例被创建。
(2)T是一个类,并且T中声明的一个静态方法被调用。
(3)T中声明的一个静态字段被赋值。
(4)T中声明的一个静态字段被使用,并且这个字段不是一个常量字段。
(5)T是一个顶级类,而且一个断言语句嵌套在T内部被执行。
因此首先执行getInstance()方法的线程将导致InstanceHolder类被初始化。
java语言规范规定,对于每一个类或者接口C,都有一个唯一的初始化锁LC与之对应。

九.Java内存模型综述

Ⅰ.处理器的内存模型

顺序一致性模型是一个理论参考模型,JMM和处理器的内存模型都会以顺序一致性模型作为参考,但是与顺序一致性模型完全一致,那么很多编译器和处理器优化都会被放弃,对性能影响很大。
根据不同类型的读/写操作组合的执行顺序的放松,可以将常见处理器的内存模型划分为以下几种类型:
(1)放松写-读操作的顺序,由此产生了Total Store Ordering内存模型(TSO)。
(2)放松写-写操作的顺序,由此产生了Partial Store Order内存模型(PSO)。
(3)在上述两条基础上继续放松程序中的读-写以及读-读操作,由此产生了Relaxed Memory Order内存模型(RMO)和PowerPC内存模型。
这里处理器对于读/写操作的放松,是以两个操作之间不存在数据依赖性为前提的。
JMM屏蔽了不同处理器内存模型的差异,他在不同的处理器平台之上为JAVA程序员呈现了一个一致的内存模型。

Ⅱ.各种内存模型之间的关系

JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。
可以得知处理器内存模型一般都比语言内存模型要弱。

Ⅲ.JMM的内存可见性保证

Java程序的内存可见性保证可以分为下列3类:
(1)单线程程序,单线程程序肯定不会出现内存可见性问题。
(2)正确同步的多线程程序。JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性的保证。
(3)未同步/未正确同步的多线程程序。JMM为它们提供了最小的安全性保障:线程执行时读到的要么是某个线程写入的值,要么是默认值。
最小安全性保障和64位数据的非原子性写并不矛盾。可以理解为就算读到的是一半的无效值,那也是某个线程写入的值。

Ⅳ.JSR-133对旧内存模型的修补

JDK5之后做出的修补主要有两个:
(1)增强volatile内存语义,严格限制了volatile变量与普通变量之间的重排序。
(2)增强final的内存语义。保证了只要final引用不从构造函数中逸出,final就有了初始化安全性。

Java并发编程的艺术(一)——并发编程需要注意的问题 并发是为了提升程序的执行速度,但并不是多线程一定比单线程高效,而且并发编程容易出错。若要实现正确且高效的并发,就要在开发过程中时刻注意以下三个问题: 上下文切换 死锁 资源限制 接下来会逐一分析这三个问题,并给出相应的解决方案。问题一:上下文切换会带来额外的开销线程的运行机制 一个CPU每个时刻只能执行一条线程; 操作系统给每条线程分配不同长度的时间片; 操作系统会从一堆线程中随机选取一条来执行; 阅读详情

相关推荐

《Java并发编程艺术》源码解析与实战

htmltable {th, td {th {pre {简介:《Java并发编程艺术》是Java开发者的宝贵资源,本书配套源码包含了书中所有实例和示例,用于加深对并发编程概念的理解与实践。源码包中包含详细的章节源代码、测试用例以及相关材料。

weixin_31688273的博客 914

理解Monitor监视器锁原理

理解Monitor监视器锁原理 任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和MonitorExit指令来实现。 • monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monito

qq_43783527的博客 7362

深入理解Java并发编程艺术

在Java中,除了使用现有的线程安全集合外,还可以通过扩展现有的类或使用同步工具类来创建自定义的线程安全集合。例如,可以利用方法来同步一个普通的ArrayList,但这通常不如直接使用方便和高效。在自定义集合时,需要特别注意同步的粒度和锁的使用,以确保并发安全同时避免过度的性能损失。通过本章节的介绍,你已经了解到如何在Java中选择和使用线程安全的数据结构,并且知道了它们的内部原理和各自的使用场景。下一章我们将讨论如何在并发编程中应用最佳实践与技巧。

weixin_32047493的博客 748

synchronized底层monitor原理

synchronized底层使用monitor来控制锁的活动。了解monitor中的各个属性值的含义,锁的竞争流程。

qq_63815371的博客 1168

Linux Wireless之80211(nl80211, cfg80211, mac80211)

Linux Wireless之80211(nl80211, cfg80211, mac80211)前言在Linux无线子系统中,cfg80211、mac80211和nl80211是三个关键的组件,它们共同工作以实现对802.11设备的配置和管理。cfg80211负责内核空间的通用API,mac80211负责具体的MAC层实现,而nl80211则作为用户空间与内核空间之间的桥梁,用于配置管理和通信。Netlink 是一个 Linux 中的 socket 类型,用于在内核与用户空间之间传递事件。

DD'Notes 1481

Python爬虫实战:用BeautifulSoup和Pandas轻松抓取去哪儿网景点数据(附完整代码)

本文详细介绍了如何使用Python的BeautifulSoup和Pandas库构建一个高效的旅游数据爬虫,从去哪儿网抓取景点数据。内容涵盖环境准备、网页结构分析、核心代码实现、反爬策略以及数据清洗与存储,提供完整代码示例,帮助开发者快速掌握数据采集技术。

river的博客 357

synchronized关键字-监视器锁(monitor lock)

这就是我们上一篇中代码提到的加锁的主要方式,

asdssadddd的博客 1504

MPC模型预测控制实战:从理论到代码实现(Python示例)

本文详细介绍了MPC模型预测控制的实战应用,从理论到Python代码实现。通过构建系统模型、预测方程、优化问题及约束处理,展示了如何实现一个完整的MPC控制器。文章还提供了水箱液位控制的应用案例,帮助读者掌握MPC在工业控制中的实际应用。

weixin_42653691的博客 461

JMM内存模型之happens-before阐述

如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照 happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法。

qq_39939541的博客 530

添加odbc数据源时的一些问题和解决办法

pl/sql

Mr_Syh的博客 9644

Python+ArcGIS实战:5步搞定土壤类型预测(附随机森林模型配置技巧)

本文详细介绍了如何结合Python与ArcGIS,利用随机森林模型进行土壤类型预测。通过5个核心步骤——数据预处理、样本创建、模型构建、空间预测与精度验证,构建了一套自动化、高精度的空间预测工作流,并提供了关键的模型配置与调参技巧,助力GIS开发者高效完成土壤制图任务。

weixin_29227585的博客 396

synchronized 的底层原理及优化机制

原子性指一个操作或一组操作,要么全部执行且执行过程不被打断,要么全部不执行(不可分割)。比如 i++ 本质是「读取 i → 加 1 → 写入 i」三个步骤,若不加同步,多线程环境下可能被其他线程打断,导致结果错误。可见性指一个线程修改了共享变量的值后,其他线程能立刻感知到这个修改。若没有可见性保障,线程 A 修改的变量可能只存在于自己的工作内存中,未同步到主内存,线程 B 读取的仍是主内存中旧值。有序性指程序执行的顺序与代码编写的顺序一致,避免因「指令重排序」导致的多线程执行混乱。

stone_tmp的博客 757

基于Codex与Skills的学术研究自动化全流程实战指南

还在为海量文献下载、整理、阅读和综述写作而头疼吗?面对堆积如山的PDF,你是否感到无从下手?从选题、文献检索、到写作、绘图、润色,再到应对AI检测和审稿意见,学术研究的每一步都充满挑战。本文将为你带来一套堪称“王炸组合”的解决方案: Codex + Skills 。这不是简单的工具介绍,而是一套从零开始,覆盖学术研究全流程的保姆级实战教程。我们将手把手教你如何安装、配置,并利用强大的Skills(技能)自动化处理文献下载管理、高效撰写综述、生成专业图表、智能润色降重,直至应对审稿。无论你是科研新手还是希望提

weixin_29041195的博客 765

Java并发编程的艺术,学习笔记(二)锁

锁Lock接口lock 比 synchronized 的好处队列同步器重入锁获取锁过程(code)公平和非公平的区别读写锁(ReentrantReadWriteLock)LockSupport工具Condition接口condition的实现 Lock接口 java 中提供了两种方式来给代码加锁,一种是隐式加锁synchronized,当然他的解锁也是隐式的。 另一种就是显示加锁 Lock ,他的...

忘记过去的人,必将重蹈覆辙 244

mindie部署qwen3-8b

2. 确认目录文件权限是否如下所示,若存在不匹配项,则参考以下命令修改权限。否则要stop容器之后重新start,重新从《以下是在容器中的操作》开始操作。ctrl +d 让命令停止之后,需要等待一段事件让其释放,否则还会有占用。1. 若安装路径为默认路径,执行如下命令,进入MindIE安装目录。# 配置CANN环境,默认安装在/usr/local目录下。3. 运行以下命令初始化各组件环境变量,并开启日志打印。如果报错,可能有些线程没法去除,删除容器重新操作。# 配置模型仓环境变量。以下是在容器中的操作。

andeyeluguo的博客 1036

YOLOv8改进策略【Conv和Transformer】| CVPR-2023 BiFormer 稀疏自注意力,减少内存占用

本文记录的是利用双级路由注意力机制优化的目标检测网络模型。传统的复杂度高,随着输入空间分辨率增加,计算量呈平方增长,导致严重的可扩展性问题。而本文所使用的在获取全局信息的同时,通过区域到区域路由和令牌到令牌注意力的合理设计,减少了不必要的计算量。BiFormer: Vision Transformer with Bi-Level Routing Attention采用四阶段金字塔结构,与一些先进的视觉Transformer类似。在每个阶段,首先使用重叠的补丁嵌入(在第一阶段)或补丁合并模块(在第二到第四阶段

Limiiiing的博客 614

Java并发编程的艺术学习笔记(二)

Java并发机制的底层实现原理(volatile) 1. volatile 1.1 volatile的作用 volatile是轻量级的synchronize,它在多处理器 开发中保证了共享变量的“可见性”。所谓可见性是指当一个线程修改共享变量时,其它线程能立即读取到该已被修改的变量值。volatile的开销更低,且不会引起线程上下文的切换。 1.2 volatile的定义: 如果一个变量被声明为v...

weixin_33696822的博客 141

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Java并发编程的艺术,学习笔记(五)线程池(二)

ExecutorExecutor两级调度模型Executor框架结构Executor的成员FixedThreadPoolSingleThreadPoolCacheThreadPool Executor两级调度模型 两级调度在于,一在于,我们将任务提交给Executor框架,然后Executor创建线程来执行任务。二在于,线程对应于操作系统的线程,所以线程池中的线程再由操作系统内核给他们分配cpu...

忘记过去的人,必将重蹈覆辙 235

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

【免费下载】 探索Java并发编程的奥秘:《JAVA并发编程艺术》高清PDF下载

探索Java并发编程的奥秘:《JAVA并发编程艺术》高清PDF下载 【下载地址】JAVA并发编程艺术高清PDF下载 本仓库提供了一份名为《JAVA并发编程艺术》的高清PDF文件下载。该资源详细介绍了Java并发编程的各个方面,涵盖了从基础概念到高级技术的全面内容 ...

gitblog_06678的博客 1184

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

《Java并发编程的艺术》之 Java并发编程基础

《Java并发编程的艺术》之 Java并发编程基础 文章目录《Java并发编程的艺术》之 Java并发编程基础线程简介什么是线程代码清单4-1 MultiThread.java解析为什么要使用多线程更多的处理器核心更快的响应时间线程优先级代码清单4-2 Priority.java结果注意参数解析线程的状态注意Daemon线程注意注意启动和终止线程构造线程代码清单4-6 Thread.java启动线程注意理解中断代码清单4-7 Interrupted.java过期的suspend()、resume()和sto

weixin_44019182的博客 2496

# Java 并发编程的艺术(二)

操作系统调度的最小单元是线程,也叫轻量级进程(),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。

不积跬步,无以至千里;不积小流,无以成江海 1171

《java并发编程的艺术》阅读笔记总结

第1章 并发编程的挑战 并发编程的目的是为了让程序运行得更快,但是不是更多的线程就能让程序最大限度的并发执行。比如上下文切换、死锁的问题,以及受限于软件和硬件的资源限制问题。 软件资源限制:有数据库的链接数和socket连接数等 硬件的资源限制有带宽的上传、下载速度、硬盘读写速度和CPU处理速度。 减少上下文切换的方法 无锁并发编程 CAS算法 使用最少线程 使用协程 避免死锁 避免...

weixin_36152448的博客 3982

Java 并发编程的艺术

内容简介 书中采用循序渐进的讲解方式,从并发编程的底层实现机制入手,逐步介绍了在设计 Java 并发程序时各种重要的技术、设计模式与应用,同时辅以丰富的示例代码,使得开发人员能够更快地领悟 Java 并发编程的要领,围绕着 Java 平台的基础并发功能快速地构建大规模的并发应用程序。 第1章介绍 Java 并发编程的挑战,会向读者说明可能会遇到哪些问题,以及如何解决。 第2章 Java 并发编程...

GitChat 2693

《Java并发编程的艺术》第 2 版来了,新增太内容太强了。。

好好看完,文末有重磅福利。Java 从诞生开始就明智地选择了内置对多线程的支持,这使得Java 语言相比同一时期的其他语言具有明显的优势。多个线程同时执行,将显著提升程序性能,在多核环境中表现得更加明显。并发编程相比 Java 中其他知识点学习门槛较高,从而导致很多人望而却步。但无论是职场面试,还是高并发/高流量的系统的实现,却都离不开并发编程,于是能够真正掌握并发编程的人成为了市场迫切需求的人才...

Java技术栈,分享最主流的Java技术 1593

【亲测免费】 JAVA并发编程艺术 高清PDF 下载

JAVA并发编程艺术 高清PDF 下载 【下载地址】JAVA并发编程艺术高清PDF下载 本仓库提供了一份名为《JAVA并发编程艺术》的高清PDF文件下载。该资源详细介绍了Java并发编程的各个方面,涵盖了从基础概念到高级技术的全面内容 ...

gitblog_06664的博客 822

Java并发编程的艺术

掌握Java并发编程的艺术,可以帮助我们更好地利用多核CPU的性能,提高程序的性能。线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。线程池是一种用于管理和复用线程的池化技术。CyclicBarrier是一个同步辅助类,用于允许一组线程相互等待,直到所有线程都达到某个屏障点。CyclicBarrier可以重复使用,当所有线程都达到屏障点后,屏障将被重置,可以再次使用。它通过计数器实现,每次调用countDown()方法,计数器减1,当计数器为0时,等待的线程将被唤醒。

m0_66404702的博客 518
上一篇: Java并发编程艺术学习笔记(一)
下一篇: Java并发编程艺术学习笔记(三)
jacobbbbbbbb
博客等级 码龄7年 6粉丝 66原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值