Java 并发编程

2023-11-13

目录

回顾线程 

并发编程

并发编程

Java 内存模型(JMM)

编程核心问题--可见性,原子性,有序性

可见性

有序性

原子性

valatile 关键字

CAS(Compare-And-Swap,比较并交换)

原子类

java中的锁

乐观锁/悲观锁

可重用锁(递归锁)

读写锁

分段锁

自旋锁

独占锁/共享锁

公平锁/非公平锁

偏向锁/轻量级锁/重量级锁

Synchronized锁实现

AQS(AbstractQueuedSynchronizer)抽象同步队列

AQS的锁模式:独占和共享 

ReentrantLock实现

JUC常用类

ConcurrentHashMap(并发安全的map)

CopyOnWriterArrayList

CopyOnWriterArraySet

辅助类 CountDownLatch(递减计数器)

​编辑 CyclicBarrier

线程池

线程池参数

线程池的执行

4种拒绝策略

关闭线程池

对象引用

ThreadLocal 线程变量

回顾线程 

线程与进程之间的关系

进程:是操作系统分配系统资源的基本单位。

线程:一个进程里面有多个线程,线程是进程内的执行单位,是进行系统调度的最小单位。

如何创建线程?

继承Thread类

实现Runnable接口:

实现Callable接口:可以抛出异常,需要借助FutureTask类,获取返回结果,支持泛型的返回值

常用方法

run();sleep();wait();start();stop();wait();join();notify();notifyAll();yield();

wait()和sleep()方法的区别

wait()是用于线程间通信的,而sleep()是用于短时间暂停当前线程。更加明显的一个区别在于,当一个线程调用wait()方法的时候,会释放它锁持有的对象的管程和锁,但是调用sleep()方法的时候,不会释放他所持有的管程。

线程状态

创建状态、就绪状态、运行状态、阻塞状态、销毁/死亡状态

线程由运行状态如何转换为阻塞状态?

IO阻塞、wait()、join()、等待同步锁、sleep();

多线程

在同一个进程中,有多个线程可以访问同一共享资源,java语言支持多线程

优点:提高程序的处理速度,提高CPU的利用率。

缺点:多个线程对同一共享资源数据进行访问

如何解决多线程访问同一共享资源?

加锁:

1. 使用synchronized关键字

静态方法:锁对象是类的Class对象

非静态方法:锁对象是this

修饰代码块:对某一段代码进行加锁控制,需要自己传入锁对象,并且要求锁对象是唯一的。

  • 隐式的自动的加锁和释放锁
  • 底层通过java虚拟机来进行控制实现

2. 使用Lock接口下的实现类

是类,只能通过修饰代码块,显式的加锁和释放锁,底层通过java代码赖进行控制实现,Lock(),unLock();只能手动的添加,手动的释放。

守护线程和用户线程

守护线程:任何一个守护线程都是整个JVM中所有非守护线程的保姆: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作; 只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。例如:垃圾回收器

用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

注意:设置线程为守护线程必须在启动线程之前,否则会跑出一个 IllegalThreadStateException异常。

线程死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁。

线程间的通信

指多个线程通过相互牵制,相互调度,即线程间的相互作用。

wait()、notify()和notifyAll() 必须在同步代码块中执行

  • wait();   线程阻塞,并同时释放同步锁
  • notify(); 唤醒被wait() 的一个线程,如果有多个线程被wait(); 会唤醒一个优先级高的线程。
  • notifyAll(); 唤醒所有被wait()的线程

生产者、消费者模式(消息队列组件 MQ)

并发编程

多线程访问共享数据,会出现安全问题

并发(concurrent)与并行 (Parallel)

并行:微观上同时执行,在同一时间点上,同时做多件事情。

并发:宏观上同时执行,多件事情在同一时间段内,交替执行。

例如:大家排队在一个咖啡机上接咖啡,交替执行,是并发;两台咖啡机上面接咖啡,
是并行。

并发编程

在很多线程对共享资源进行访问,需要通过控制,让多个线程并发的对共享数据访问。

多线程执行本质问题:

由于CPU、内存、硬盘三者之间的读写速度不一样。

速度排序:CPU > 内存 > I/O 设备
  • 多核CPU,每个内核中都会有一个高速缓存,每一个高速缓存数据不可见。(可见性
  • 线程中有IO操作,耗时比较长,操作系统需要切换线程执行。(原子性
  • 操作系统对指令进行优化,对指令进行重排序。(有序性

Java 内存模型(JMM)

先复制一份数据到 CPU Cache 中,当 CPU 需要用到的时候就可以直接从 CPU Cache 中读取数据,当运算完成后,再将运算得到的数据写回主内存中。但是,这样存在 内存缓存不一致性的问题 !比如我执行一个 i++ 操作的话,如果两个线程同时执行的话,假设两个线程从 CPU Cache 中读取的 i=1,两个线程做了 1++ 运算完之后再写回 Main Memory 之后 i=2,而正确结果应该是 i=3。

        Java 内存模型(Java Memory Model,JMM)规范了 Java 虚拟机与计算机内存是如何协同工作的。Java 虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——又称为 Java 内存模型。
因为Java语言是跨平台的,它需要自己提供一套内存模型以屏蔽系统差异。因为不同的操作系统内存模型不同,直接使用的话会导致内存模型不可用,我们使用JMM内存模型就是这个原因,目的是为了简化多线程编程,增强程序可移植性的(从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范)。

编程核心问题--可见性,原子性,有序性

可见性

        一个线程对共享变量的修改,另一个变量也会立即会看到,这种情况我们称为可见性。如今的多核处理器,每个 CPU 内核都有自己的缓存,而缓存仅仅对它所在的处理器内核可见,CPU 缓存与内存的数据不容易保证一致。

有序性

        有序性指的是程序按照代码的先后顺序执行。 为了优化性能,有时候会改变程序中语句的先后顺序。 cpu 的读等待同时指令执行是 cpu 乱序执行的根源。 读指令的同时可以同时执行不影响的其他指令。

原子性

        一个或多个操作在 CPU 执行的过程中不被中断的特性,我们称为原子性。 原子性是拒绝多线程交叉操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。 CPU 能保证的原子操作是 CPU 指令级别的,而不是高级语言的操作符。线程切换导致了原子性问题。

如 count++,至少需要三条 CPU指令
  • 指令 1:首先,需要把变量 count 从内存加载到工作内存;
  • 指令 2:之后,在工作内存执行 +1 操作;
  • 指令 3:最后,将结果写入内存;

  两个线程 A 和 B 同时执行 count++, 即便 count 使用 volatile 修辞,我们预期的结果值是 2,但实际可能是 1。

  缓存(CPU中)导致的可见性问题,编译优化带来的有序化问题,线程切换带来的原子性问题。

volatile 关键字

修饰的变量  可以保证可见性(一个内存更改的数据)

                   禁止指令重排序

不能保证原子性?

解决:原子性(CAS),加锁

volatile 底层是如何实现的?

      在对volatile修饰的变量读写操作前,可以添加内存屏障指令,禁止在该条指令执行前插入其他指令,在工作内存修改后,结合缓存一致性协议,将工作内存数据更新到主内存,其他内存读取更新。

CAS(Compare-And-Swap,比较并交换)

CAS是乐观锁(没有采用加锁的方式)的一种实现方式,使用自旋锁(一遍一遍的获取)思想去比较。

内部三个值:
内存值 V:操作前先将内存值读到工作内存。

预期值 A:在工作内存修改了变量后,将要将修改后的值向主内存写入的时候,再次读取的内存数据。

更新值 B:内部操作后的变量值。当向主内存写入数据时,必须满足V==A,就将V=B,否则就再次读入内存值。

优点:没有加锁,效率高于锁

缺点:CAS是无锁的,采用自旋的方法,线程不会阻塞,如果有大量的线程进行尝试,那么CPU就消耗较大(适合低并发量较小的情况)

ABA问题

就是内存中某个线程将内存值由 A 改为了 B,再由 B 改为了 A。当另外一个线程使用预期值去判断时,预期值与内存值相同,当前线程的 CAS 操作无法分辨,当前 V 值是否发生过变化。

解决:加入版本号,每次修改都要更新版本号。

解决 ABA 问题的主要方式,通过使用类添加版本号,来避免 ABA 问题。

如原先的内存值为(A,1),线程将(A,1)修改为了(B,2),再由(B,2)修改为(A,3)。此时另一个线程使用预期值(A,1)与内存值(A,3)进行比较, 只需要比较版本号 1 和 3,即可发现该内存中的数据被更新过了。

原子类

原子类原理(AtomicInteger 为例)
原子类的原子性是通过 volatile + CAS 实现原子操作的。 AtomicInteger 类中的 value 是有 volatile 关键字修饰的,这就保证了 value 的内存可见性,这为后续的 CAS 实现提供了基础。
低并发情况下:使用 AtomicInteger。

java中的锁

不全是指锁,有的指的是锁的特性,有的是锁的状态,有的是锁的设计

乐观锁/悲观锁

乐观锁:采用CAS机制,乐观认为不加锁是没有问题的。(适用于读操作,例:原子类)

悲观锁:采用加锁的方式实现,悲观的认为不加锁是会有问题的。(适用于写操作)

可重用锁(递归锁)

       指当一个线程进入外层方式获取锁后,如果内存调用另一个需要获取该锁的方法时,会自动的获取该锁修饰的方法,那么线程是可以进入的。

优点:可以一定程度上避免死锁。

读写锁

里面维护两个锁的实现,一个是读锁,一个是写锁如果使用的写锁,一次只能有一个线程进入;如果使用的是读锁,可以允许多个线程同时存在;写锁的优先级高于读锁

分段锁

  不是具体的所,将锁的粒度分的更小,以提高并发效率

自旋锁

  不断尝试去尝试获得锁,不会让线程进入阻塞状态,提高效率,但是耗CPU

独占锁/共享锁

独占锁

      ReentrantLock、synchronized,读写锁中的写锁或者互斥锁,一个只允许一个线程获取锁。

共享锁

 读写锁中读锁是共享的,可以有多个线程同时获取锁

公平锁/非公平锁

公平锁

可以按照请求顺序分配锁。ReentrantLock中有公平锁实现,里面维护一个队列,按顺序排队获取锁。

非公平锁

不按照请求顺序分配锁。synchronized非公平锁和ReentrantLock默认为非公平锁(可变为公平锁)

偏向锁/轻量级锁/重量级锁

偏向锁:只有一个线程访问一段同步代码,此时会将线程的id存入到对象头中,下次该线程来获取锁的时候,直接分配即可。

轻量级锁:当锁的状态为偏向锁时,又有线程访问,那么锁状态升级为轻量级锁,不会让线程进入阻塞状态,而是自旋尝试获得锁,以提高效率。

重量级锁:当锁的状态为轻量级锁,如果线程数量的太多,线程的自旋次数达到一定数量,锁的状态变为重量级锁,线程进入阻塞状态,等待操作系统调度分配。

在synchronized中的锁有三种状态,为了优化synchronized,在jdk 6之后提出的,针对不同的状态进行不同处理。

Synchronized锁实现

synchronized是关键字,可以修饰方法、代码块、一次只允许一个线程进入

synchronized如果修饰方法

     在编译后的指令中添加ACC_SYNCHRNIZED表示次方法是同步方法,有线程进入后其他线程不能进入,在对象头中锁标志+1,方法运行结束或出现异常,锁标志 -1。

synchronized如果修饰代码块

    在进入代码之前加入monitorenter指令,对象锁标志+1,同步代码块运行结束或出现异常,执行monitorexit指令,锁标志-1。

AQS(AbstractQueuedSynchronizer)抽象同步队列

JUC java.util.concurrent  java并发包     FIFO 先进先出队列

实现原理

       AQS是JUC中实现线程安全的核心组件,是从java代码级别实现内部维护锁的状态,用volatile关键字修饰state,内部维护一个FIFO队列,保存未获取到锁的线程。多个线程来访问,如果有一个线程访问到了state,就将其改为1,其他线程获取失败后,就会添加到队列中,Node(Thread)。

        就是说,多个线程来访问,如果有一个线程访问到了volatile关键字修饰state(标志位),就将其改为1,这时,其他的线程获取失败后,就会添加到FIFO队列中,等待该线程结束后,将state改为0,然后等待下一个线程访问state队列由 Node 对象组成,Node 是 AQS 中的内部类。

 它还维护一些获取锁,添加线程到队列,释放锁的一些方法。

AbstractQueuedSynchronizer 成员
private transient volatile Node head ;
private transient volatile Node tail ;
使用变量 state 表示锁状态,0-锁未被使用,大于 0 锁已被使用
共享变量 state,使用 volatile 修饰保证线程可见性
private volatile int state ;

 状态信息通过 getState , setState , compareAndSetState 进行操作.

获得锁状态
protected final int getState () {
        return state ;
}
设置锁状态
protected final void setState ( int newState) {
        state = newState;
}
使用 CAS 机制设置状态
protected final boolean compareAndSetState ( int expect, int update) {
        return unsafe .compareAndSwapInt( this , stateOffset , expect, update);
}

获取锁的方式有两种 

AQS 操作重点方法

acquire: 表示一定能获取锁

public final void acquire ( int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter( Node . EXCLUSIVE ), arg))
                selfInterrupt();
}

 tryAcquire:尝试获取锁,如果tryAcquire获取锁成功,那么!tryAcquire(arg)false,说明已经获取了锁,不用向后执行,直接返回。

addWaiter:尝试获取锁失败后,将当前线程封装到一个Node对象中,添加到队尾,并返回Node节点。

acquireQueued:将线程添加到队列中,以自旋的方式去获取锁。

release 释放锁

tryRelease:释放锁,将state值进行修改为0

unparkSuccessor:唤醒节点的后继者(如果存在)

AQS的锁模式:独占和共享 

独占锁:每次只能有一个线程持有锁,比如 ReentrantLock 就是以独占方式实现的互斥锁

共享锁:允许多个线程同时获取锁,并发访问共享资源,比如ReentrantReadWriteLock。

ReentrantLock实现

ReentrantLock 是向外界提供的所实现的类,内部包含三个内部类(Sync,FairSync,NonfairSync)。

  • Sync   extends  AQS
  • FairSync   extends  Sync   公平实现
  • NonFairSync   extends Sync   非公平实现
构造方法
无参方法
默认为非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
有参方法
fair == true 为公平锁,fair == false 为非公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
NonFairSync   extends Sync   非公平实现
static final class NonfairSync extends Sync {
加锁
        final void lock () {
        //若通过 CAS 设置变量 state 成功,就是获取锁成功,则将当前线程设置为独占线程。
        //若通过 CAS 设置变量 state 失败,就是获取锁失败,则进入 acquire 方法进行后续处理。
                if (compareAndSetState( 0 , 1 )) // 没有排队,直接尝试去获取锁
                        setExclusiveOwnerThread( Thread . currentThread ());
                else
                        acquire( 1 );// 获取锁,表示一定能获取锁,获取不到就继续
        }
         //尝试获取锁,无论是否获得都立即返回
        protected final boolean tryAcquire ( int acquires) {
                return nonfairTryAcquire(acquires);
        }
}

Lock():尝试获取锁,肯定是能获取到锁的,直到有线程获取到锁,lock()方法就运行结束了。

unlock():释放锁

JUC常用类

JUC java.util.concurrent  java并发包 

ConcurrentHashMap(并发安全的map)

HashMap:线程不安全的,使用于单线程情况,键值可为空。

HashTable:是线程安全的,支持多线程并发访问(),但是锁是加在put()方法上的,效率低,一次只能有一个线程进入到put()方法中进行操作,键值都不能为空。

ConcurrentHashMap

       多线程并发访问安全的,将锁不在put()方法上添加,而是将每个位置看作是独立的空间,添加元素,通过Hash值计算它的位置,如果位置上还没有任何元素,采用CAS机制判断,添加元素到第一个位置,如果有元素,则使用头结点作为锁标记对象

优点:实现了锁粒度变小,提高了并发效率

为什么HashMap可以有null值,HashTable不能有空值?

 Hashtable:

Hashtable不支持存储 key == null 或 value == null的值,则抛出空指针异常。

 HashMap:


 当key == null ,存储在哈希值为0的位置;

ConcurrentHashMap 不支持存储 null 键和 null 值

从底层源码我们可以看出,当键或值为null值的时候,我们会抛出空指针异常。 

原因:ConcurrentHashMap用于多线程,为了消除歧义,无法分辨key是没有找到的null还是key值为null,当get(key)获取对应的value时,如果获取到的是null时你无法判断它是put(K,V)的时候value是空的,还是这个key没有对应的值(无映射)。

CopyOnWriterArrayList

Vector

 

     由源码我们可以看出来,Vector线程安全的 但是对读(get())和写(add())操作也加了锁, 读和写用的是同一把锁。

       添加、查询的方法都添加了同步锁,在写操作时,其他线程是不能读的,效率很低。操作时经常读操作是比较多的,但是读不会改变数据。一次只允许一个线程读数据

CopyOnWriterArrayList

       CopyOnWriterArrayList读(get())和写(add())进行分离,读操作完全不用加锁,读不影响数据,写操作加锁,写操作时不影响读操作,只有两个线程同时添加时会互斥。

给add(),set()会影响数据的操作方法添加锁,保证修改数据是安全的,写入数据时,会先创建新的数组,将新添加的元素写入到新的数组中,写完之后在将新数组赋给底层原来数组的引用。而读没有做任何的控制。

CopyOnWriterArraySet

CopyOnWriteArraySet 的实现基于 CopyOnWriteArrayList,不能存储重复数据。添加元素时会判
断是否有重复元素。

辅助类 CountDownLatch(递减计数器

        使一个线程,等待其他线程执行结束后再执行,相当于一个线程计数器,是一个递减的计数器。先指定一个数量,当有一个线程执行结束后就减一 直到为0,关闭计数器,这样线程就可以执行了。


public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {

        CountDownLatch downLatch = new CountDownLatch(6);//计数
        for (int i = 0; i <6 ; i++) {
            new Thread(
                ()->{
                    System.out.println(Thread.currentThread().getName());
                    downLatch.countDown();//计数器减一操作
                }
            ).start();
        }
        downLatch.await();//关闭计数
        System.out.println("main线程执行");
    }
}

执行结果:

 CyclicBarrier

     让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,是一个加法计数器,当线程数量到达指定数量时,开门放行。

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier c = new CyclicBarrier(5, ()->{
            System.out.println("大家都到齐了 该我执行了");
        });

        for (int i = 0; i < 5; i++) {
            new Thread(
                    ()->{
                        System.out.println(Thread.currentThread().getName());
                        try {
                            c.await();//加一计数器
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (BrokenBarrierException e) {
                            e.printStackTrace();
                        }
                    }
            ).start();
        }
    }
}

执行结果:

线程池

为什么要用池?

    每次连接数据库创建连接对象,用完销毁,比较费时,增大了时空开销。而使用池,可以事先创建出一些连接对象放入池中,每次使用都从池子获取,用完还回到池子里面。

线程池:jdk5之后提供java内置版本,提供ThreadPoolExecutor类实现创建线程(推荐使用)

优点:

  • 可以重复使用线程,降低线程创建和销毁的资源消耗,即降低时空开销
  • 统一管理,线程的创建和销毁都由线程池统一管理
  • 提高响应速度,线程已存在,直接调用空闲的线程

线程池参数

线程中也有一些参数对线程池进行设置

  • coreOPoolSize:核心池的大小 。默认为0,当有任务到来后,创建线程去执行,执行完后,线程不销毁,直到创建出与核心池子大小相等的数量线程,当到达corePoolSize大小时,会将其放入缓存队列,除非调用了prestartAllCoreThreads() 或prestartCoreThread();
  • maximumPoolSize:线程池最大的线程数量。 它表示线程池最多能创建多少个线程
  • keepAliveTime:非核心线程中,表示线程没有任务执行时最多保持多久时间会终止
  • unit:参数keepAliveTime的时间单位,在TimeUnit类中一共有7个。
  • workQueue:一个阻塞队列,用来存储等待队列的任务,当核心线程池到达corePoolSize时,用来存储等待执行的任务。
  • threadFactory:线程工厂,主要用来创建线程;
  • handler:表示当拒绝处理任务时的策略

线程池的执行

线程队列:

  • SynchronousQueue:同步队列是一个容量只有 1 的队列,这个队列比较特殊, 它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务,每个 put 必须等待一个 take.
  • ArrayBlockingQueue:有界队列,是一个用数组实现的有界阻塞队列,按 FIFO 排序量。
  • LinkedBlockingQueue:可设置容量队列,基于链表结构的阻塞队列,按 FIFO 排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度Integer.MAX_VALUE;

4种拒绝策略

  • AbortPolicy:报错(会抛出异常,阻止程序运行)
  • CallerRunsPolicy:只要线程池未关闭,由当前调用的线程执行(例如:任务是在main线程中,就由main线程执行)
  • DiscardOleddestPolicy丢弃等待时间最长的(等待队列)
  • DiscardPolicy线程池满后,直接丢弃。

execute 和 submit 区别

相同点:都用来执行任务

不同点:excecute是没有返回值的,而submit是有返回值的

关闭线程池

关闭线程池可以调用 shutdownNow 和 shutdown 两个方法来实现。
  • shutdownNow:对正在执行的任务全部发出 interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。
  • shutdown:当我们调用 shutdown 后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。

对象引用

概述

除了垃圾回收标记垃圾对象之外,还需要对垃圾对象进行撞他分类管理。

强引用:有引用指向的对象,不是垃圾对象。

例:Object object = new Object();

软引用(内存不足即回收):使用SoftReference来管理对象。已经是垃圾了,如果内存够用,不回收,否则,将软引用管理的对象进行回收。

弱引用(发现即回收):使用WeakReference来管理对象,只能存活到下一次垃圾回收

虚引用(对象回收跟踪机制):使用PhantomReference来管理对象,需要提供一个队列维护,随时可以被回收,主要就是记录跟踪对象是否被回收。

ThreadLocal 线程变量

创建一个ThreadLocal对象,复制用来为每个线程会存一份变量,实现线程封闭

底层实现原理

ThreadLocal内部维护了一个Map,ThreadLocal实现了一个叫做 ThreadLocalMap 的静态内部类。

      当有一个线程进入,在每个线程中,都会创建一个ThreadLocalMap对象,将ThreadLocalMap放入线程中,第一次创建的时候,在set()方法中,会将当前线程ThreadLocal作为键(this),将value做为值,存入到ThreadLocalMap中。

 在调用get方法时,会调用当前线程对象的来在ThreadLocalMap里面进行查找对应的值。

有什么问题?
    存在内存泄露的问题

如何解决内存泄露问题?

原因:ThreadLocal作为key,被弱引用(WeakReference)进行管理(存活到下一次垃圾回收),而值value是强引用的,与ThreadLocalMap和Thread强关联。key回收而value去还存在,造成大量无用的对象存在,但却又回收不掉,造成内存泄露。

解决:用完变量之后,调用THreadLocal中的remove()删除键值对象(用完即删)。

多线程最主要的额外开销:锁竞争

在Java中多线程额外开销最主要的原因就是锁竞争,因为获取锁会导致串行操作同时获取锁失败会导致线程挂起出现上下文竞争。所以减少锁竞争是降低多线程开销最主要的手段

而在并发程序中,对可伸缩性的最主要威胁就是独占方式的资源锁。而影响锁竞争的因素:锁请求频率,每次持有锁的时间,如果两个的乘积很小,说明获取锁和持有锁的总时间就很少,那么大多数获取锁的操作都不会发生竞争。

所以减少锁竞争的主要方法是:减少锁的只有时间降低锁的请求频次、或者使用带有协调机制的独占锁替代,因为这些机制允许更高的并发性。

减少锁竞争手段

第一个是缩小锁的范围:将与锁无关代码移除同步代码块,尤其是那些可能发生阻塞的操作比如I/O;

第二个是减少锁的粒度:使用多个相互独立锁管理独立的状态变量,改变某个变量只用获取对应变量锁,而不用获取整体锁,其他线程仍然能使用其他变量。但是使用锁越多,那么发生死锁的风险也就越高。

第三个是锁分段:比如ConcurrentHashMap底层的链表数组,对数组中每一个数组元素进行加锁,数组长度是多少就有多少个锁,也就最大支持多少并发。不过在对数组扩张的时候就会更加复杂;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 并发编程 的相关文章

  • 按键时关闭 ModalWindow

    我希望能够在用户按下某个键 在我的例子中是 ESC 时关闭 ModalWindow 我有一个用于按键的 Javascript 侦听器 它调用取消按钮 ID 的单击事件 jQuery modalWindowInfo closeButtonId
  • Java中有没有一种方法可以通过名称实例化一个类?

    我正在寻找问题 从字符串名称实例化一个类 https stackoverflow com questions 9854900 instantiate an class from its string name它描述了如何在有名称的情况下实例
  • 在 Java 中克隆对象 [3 个问题]

    这样做会调用Asub的clone方法吗 或者Asub深度克隆是否正确 如果没有的话 有没有办法通过这种方法对Asub进行深度克隆呢 abstract class Top extends TopMost protected Object cl
  • Mockito:如何通过模拟测试我的服务?

    我是模拟测试新手 我想测试我的服务方法CorrectionService correctPerson Long personId 实现尚未编写 但这就是它将执行的操作 CorrectionService将调用一个方法AddressDAO这将
  • Java 枚举与创建位掩码和检查权限的混淆

    我想将此 c 权限模块移植到 java 但是当我无法将数值保存在数据库中然后将其转换为枚举表示形式时 我很困惑如何执行此操作 在 C 中 我创建一个如下所示的枚举 public enum ArticlePermission CanRead
  • 使用 LinkedList 实现下一个和上一个按钮

    这可能是一个愚蠢的问题 但我很难思考清楚 我编写了一个使用 LinkedList 来移动加载的 MIDI 乐器的方法 我想制作一个下一个和一个上一个按钮 以便每次单击该按钮时都会遍历 LinkedList 如果我硬编码itr next or
  • 过滤两次 Lambda Java

    我有一个清单如下 1 2 3 4 5 6 7 和 预期结果必须是 1 2 3 4 5 6 7 我知道怎么做才能到7点 我的结果 1 2 3 4 5 6 我也想知道如何输入 7 我添加了i gt i objList size 1到我的过滤器
  • Pig Udf 显示结果

    我是 Pig 的新手 我用 Java 编写了一个 udf 并且包含了一个 System out println 其中的声明 我必须知道在 Pig 中运行时该语句在哪里打印 假设你的UDF 扩展了 EvalFunc 您可以使用从返回的 Log
  • 如何在 Spring 中禁用使用 @Component 注释创建 bean?

    我的项目中有一些用于重构逻辑的通用接口 它看起来大约是这样的 public interface RefactorAwareEntryPoint default boolean doRefactor if EventLogService wa
  • java.lang.IllegalStateException:提交响应后无法调用 sendRedirect()

    这两天我一直在尝试找出问题所在 我在这里读到我应该在代码中添加一个返回 我做到了 但我仍然得到 java lang IllegalStateException Cannot call sendRedirect after the respo
  • 无法创建请求的服务[org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]-MySQL

    我是 Hibernate 的新手 我目前正在使用 Spring boot 框架并尝试通过 hibernate 创建数据库表 我知道以前也问过同样的问题 但我似乎无法根据我的环境找出如何修复错误 休眠配置文件
  • Hibernate 的 PersistentSet 不使用 hashCode/equals 的自定义实现

    所以我有一本实体书 public class Book private String id private String name private String description private Image coverImage pr
  • volatile、final 和synchronized 安全发布的区别

    给定一个带有变量 x 的 A 类 变量 x 在类构造函数中设置 A x 77 我们想将 x 发布到其他线程 考虑以下 3 种变量 x 线程安全 发布的情况 1 x is final 2 x is volatile 3 x 设定为同步块 sy
  • logcat 中 mSecurityInputMethodService 为 null

    我写了一点android应显示智能手机当前位置 最后已知位置 的应用程序 尽管我复制了示例代码 并尝试了其他几种解决方案 但似乎每次都有相同的错误 我的应用程序由一个按钮组成 按下按钮应该log经度和纬度 但仅对数 mSecurityInp
  • 专门针对 JSP 的测试驱动开发

    在理解 TDD 到底是什么之前 我就已经开始编写测试驱动的代码了 在没有实现的情况下调用函数和类可以帮助我以更快 更有效的方式理解和构建我的应用程序 所以我非常习惯编写代码 gt 编译它 gt 看到它失败 gt 通过构建其实现来修复它的过程
  • 最新的 Hibernate 和 Derby:无法建立 JDBC 连接

    我正在尝试创建一个使用 Hibernate 连接到 Derby 数据库的准系统项目 我正在使用 Hibernate 和 Derby 的最新版本 但我得到的是通用的Unable to make JDBC Connection error 这是
  • 我如何在java中读取二进制数据文件

    因此 我正在为学校做一个项目 我需要读取二进制数据文件并使用它来生成角色的统计数据 例如力量和智慧 它的设置是让前 8 位组成一个统计数据 我想知道执行此操作的实际语法是什么 是不是就像读文本文件一样 这样 File file new Fi
  • 干净构建 Java 命令行

    我正在使用命令行编译使用 eclipse 编写的项目 如下所示 javac file java 然后运行 java file args here 我将如何运行干净的构建或编译 每当我重新编译时 除非删除所有内容 否则更改不会受到影响 cla
  • 长轮询会冻结浏览器并阻止其他 ajax 请求

    我正在尝试在我的中实现长轮询Spring MVC Web 应用程序 http static springsource org spring docs 2 0 x reference mvc html但在 4 5 个连续 AJAX 请求后它会
  • 如何将双精度/浮点四舍五入为二进制精度?

    我正在编写对浮点数执行计算的代码的测试 不出所料 结果很少是准确的 我想在计算结果和预期结果之间设置一个容差 我已经证实 在实践中 使用双精度 在对最后两位有效小数进行四舍五入后 结果始终是正确的 但是usually四舍五入最后一位小数后

随机推荐

  • java 静态类的实例_java中类的静态成员和实例成员

    一 基本介绍 java中的静态成员包括静态方法和静态成员变量 静态成员都是由static修饰的 java中的实例成员包括实例方法和实例成员变量 实例成员都没有被static修饰 二 使用语法 关于使用静态成员和实例成员的具体语法规则如下 p
  • 关于指针运算的一道题

    目录 刚看到这道题的时候我也和大多数小白一样感到无从下手 但是在我写这篇博客的前几分钟开始我对这道题有了一点点的理解 所以我就想着趁热打铁 写一篇博客来记录一下我的想法 题目如下 画图 逐一解答 题一 cpp cpp 1 c 2 POINT
  • 苹果手机10秒解除锁屏_忘记苹果锁屏密码10秒解决 音量键选择wipedata/

    导读 谈到苹果 大家应该都不陌生 有人问忘记手机密码了怎么办 另外 还有朋友想问oppo忘记图案解锁怎么办 这到底怎么回事呢 其实锁屏密码是四位数密码呢 下面是小编精心为你们整理的忘记苹果锁屏密码10秒解决 欢迎大家一起来阅读 忘记苹果锁屏
  • Android 获取当前应用的版本号和当前系统的版本号

    1 获取当前程序版本名 我们可以在AndroidManifest xml中设置程序的版本号等 如android versionName 1 0 那如果想在代码中获取这个版本号呢 可以用如下方法 这些修改版本号时只需要修改AndroidMan
  • 离线安装mariadb

    离线安装mariadb 文章目录 离线安装mariadb 一 下载Rpm包 二 按顺序安装依赖 galera安装 安装mariadb相关 三 安全配置 四 配置权限 五 通过navicat测试 一 下载Rpm包 前往MariaDB官网选择所
  • 如何写好一篇高质量的IEEE/ACM Transaction级别的计算机科学论文?

    http www zhihu com question 22790506 answer 81787300 f3fb8ead20 ea27429f8cbe31fd9183a68ccb41caa7 from timeline isappinst
  • uni——传参出现问题[object Object],[object Object]

    案例说明 后台需要的参数样式 goods lists good id 5 num 11 good id 9 num 3 good id 10 num 34 按照此格式传参发现 解决办法 出现 object Object 的原因通常是因为在将
  • linux 端口转发 udp,Linux Socat TCP/UDP端口转发及使用

    socat是不支持端口段转发 只适用于单端口或者少量端口 如果需要大量端口考虑使用 iptables 或 haproxy haproxy 只能转发TCP Socat安装 Centos 系统 yum install y socat Debia
  • 2023新能源汽车行业薪酬报告

    导读 数据表明 虽然受疫情封控等多重不利因素影响 但新能源汽车产业依然活力强劲 2022年 新能源汽车产销数据分别为 705 8 万辆和 688 7 万辆 同比增长分别为 96 9 和 93 4 连续 8 年保持全球第一 此外 新能源汽车市
  • C语言考试题目(一)

    一 单项选择题 本大题共25小题 每题2分 共50分 1 C语言的源程序通常的扩展名是 A cpp B obj C exe D c 2 下列选项中 属于多行注释 A B C D 3 以下不合法的字符常量是 A ab B 2 C A D n
  • bootstrap 框架学习笔记

    2019独角兽企业重金招聘Python工程师标准 gt gt gt http getbootstrap com 在这个上面下载bootstrap 为什么使用 Bootstrap 移动设备优先 自 Bootstrap 3 起 框架包含了贯穿于
  • Linux下SUDO出现Unable to resolve host XXX解决方法

    转载 Ubuntu环境 假设这台机器名字叫abc 机器的hostname 每次执行sudo 就出现这个警告讯息 sudo unable to resolve host abc 虽然sudo 还是可以正常执行 但是警告讯息每次都出来 而这只是
  • SAP调用HTTP和HTTPS

    HTTPS https archive sap com discussions thread 482084 Note 510007 SAP Kernel版本为721 SAP ECC 6 0 SAP BASIS700 调用公司的https还是
  • 常见hash加密及判定

    常见hash加密及判定 unix系系统 ES Unix 例子 IvS7aeT4NzQPM 说明 Linux或者其他linux内核系统中 长度 13 个字符 描述 第1 2位为salt 例子中的 Iv 位salt 后面的为hash值 系统 M
  • Xshell 执行python脚本

    XShell支持使用VB JS Python脚本去启动自动化任务 这里介绍如何写Xshell的Python脚本 首先要在脚本中定义一个Main 函数 Xshell会调用这个函数 也就是程序的入口 然后 通过官方提供的API去完成脚本的书写
  • 无线通信与编码_MATLAB实现OFDM载波频偏估计_含仿真代码

    为了解决频率选择性衰落信道引起的失真 OFDM系统在正交子载波上并行传输消息数据 然而 只有正交性得到保持时 OFDM才能够发挥其优势 在正交性得不到保持的情况下 系统会因为ISI和ICI而下降 总的来说 与载波信号相关的畸变有两种 一种是
  • 三个线程循环打印ABC

    思路 1 定义两个信号量 A的默认个数为1 B的默认值为0 一个用于打印A 一个用于打印B 2 A线程获取到信号量A后打印 A 打印完后释放一个信号量B 让B可以打印 3 B线程获取到信号量B后打印 B 打印完后释放一个信号量A 让A可以打
  • OpenCV教程——形态学操作。膨胀,腐蚀,开操作,闭操作,形态学梯度,顶帽,黑帽

    1 形态学操作 图像形态学操作 基于形状的一系列图像处理操作的合集 主要是基于集合论基础上的形态学数学 形态学有四个基本操作 膨胀 腐蚀 开 闭 2 膨胀与腐蚀 2 1 膨胀 跟卷积操作类似 假设有图像A和结构元素B 结构元素B在A上面移动
  • 微信小程序app.js onLaunch异步,首页onLoad先执行

    本来按照事件顺序 小程序初始化时触发App里的onLaunch 后面再执行页面Page里的onLoad 但是在onLaunch里请求授权信息本就为异步执行 等待返回值的时候Page里的onLoad事件就已经执行了 app js 代码 app
  • Java 并发编程

    目录 回顾线程 并发编程 并发编程 Java 内存模型 JMM 编程核心问题 可见性 原子性 有序性 可见性 有序性 原子性 valatile 关键字 CAS Compare And Swap 比较并交换 原子类 java中的锁 乐观锁 悲