Java并发编程加锁导致的活跃性问题详解方案

目录
  • 死锁(Deadlock)
  • 死锁的解决和预防
    • 1.超时释放锁
    • 2.按顺序加锁
    • 3.死锁检测
  • 活锁(Livelock)
  • 避免活锁
  • 饥饿
  • 解决饥饿
  • 性能问题
  • 上下文切换
    • 什么是上下文切换?
    • 减少上下文切换的方法
  • 资源限制
    • 什么是资源限制
    • 资源限制引发的问题
    • 如何解决资源限制的问题

我们主要处理锁带来的问题.
首先就是最出名的死锁

死锁(Deadlock)

什么是死锁

死锁是当线程进入无限期等待状态时发生的情况,因为所请求的锁被另一个线程持有,而另一个线程又等待第一个线程持有的另一个锁 导致互相等待。总结:多个线程互相等待对方释放锁。

例如在现实中的十字路口,锁就像红绿灯指示器,一旦锁坏了,就会导致交通瘫痪。
那么该如何避免这个问题呢

死锁的解决和预防

1.超时释放锁

>顾名思义,这种避免死锁的方式是在尝试获取锁的时候加一个超时时间,这就意味着,如果一个线程在获取锁的门口等待太久这个线程就会放弃这次请求,退还并释放所有已经获得的锁,再在等待一段随机时间后再次尝试,这段时间其他的线程伙伴可以去尝试拿锁.

public interface Lock {
	//自定义异常类
    public static class TimeOutException extends Exception{
        public TimeOutException(String message){
            super(message);
        }
    }
	//无超时锁,可以被打断
    void lock() throws  InterruptedException;
    //超时锁,可以被打断
    void lock(long molls) throws InterruptedException,TimeOutException;
    //解锁
    void unlock();
    //获取当前等待的线程
    Collection<Thread> getBlockedThread();
    //获取当前阻塞的线程数目
    int getBlockSize();
}
public class BooleanLock  implements Lock{
    private boolean initValue;
    private Thread currenThread;
    public BooleanLock(){
        this.initValue = false;
    }
    private Collection<Thread> blockThreadCollection = new ArrayList<>();

    @Override
    public synchronized void lock() throws InterruptedException {
        while (initValue){
            blockThreadCollection.add(Thread.currentThread());
            this.wait();
        }
        //表明此时正在用,别人进来就要锁住
        this.initValue = true;
        currenThread = Thread.currentThread();
        blockThreadCollection.remove(Thread.currentThread());//从集合中删除
    }

    @Override
    public synchronized void lock(long mills) throws InterruptedException, TimeOutException {
        if (mills<=0){
            lock();
        }else {
            long hasRemain = mills;
            long endTime = System.currentTimeMillis()+mills;
            while (initValue){
                if (hasRemain<=0)
                    throw new TimeOutException("Time out");
                blockThreadCollection.add(Thread.currentThread());
                hasRemain = endTime-System.currentTimeMillis();
            }
            this.initValue = true;
            currenThread = Thread.currentThread();
        }

    }

    @Override
    public synchronized void unlock() {
        if (currenThread==Thread.currentThread()){
            this.initValue = false; //表明锁已经释放
            Optional.of(Thread.currentThread().getName()+ " release the lock monitor").ifPresent(System.out::println);
            this.notifyAll();
        }
    }

    @Override
    public Collection<Thread> getBlockedThread() {
        return Collections.unmodifiableCollection(blockThreadCollection);
    }

    @Override
    public int getBlockSize() {
        return blockThreadCollection.size();
    }
}
public class BlockTest {
    public static void main(String[] args) throws InterruptedException {
    final BooleanLock booleanLock = new BooleanLock();
    // 使用Stream流的方式创建四个线程
        Stream.of("T1","T2","T3","T4").forEach(name->{
            new Thread(()->{
                try {
                    booleanLock.lock(10);
                    Optional.of(Thread.currentThread().getName()+" have the lock Monitor").ifPresent(System.out::println);
                    work();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Lock.TimeOutException e) {
                    Optional.of(Thread.currentThread().getName()+" time out").ifPresent(System.out::println);
                } finally {
                    booleanLock.unlock();
                }
            },name).start();
        });
    }

  	//如果是需要一直等待就调用 lock(),如果是超时要退出来就调用超时lock(long millo)
    private static void work() throws InterruptedException{
        Optional.of(Thread.currentThread().getName()+" is 		working.....'").ifPresent(System.out::println);
        Thread.sleep(40_000);
    }
}

运行:

T1 have the lock Monitor
T1 is working.....
T2 time out
T4 time out
T3 time out

2.按顺序加锁

>按照顺序加锁是一种有效防止死锁的机制,但是这种方式,你需要先知道所有可能用到锁的位置,并对这些锁安排一个顺序

3.死锁检测

>死锁检测是一个更好的死锁预防机制,主要用于超时锁和按顺序加锁不可用的场景每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph 等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。

如果检测出死锁,有两种处理手段:

  • 释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁,原因同超时类似,不能从根本上减轻竞争.
  • 一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。

活锁(Livelock)

什么是活锁

死锁是一直死等,活锁他不死等,它会一直执行,但是线程就是不能继续,因为它不断重试相同的操作。换句话说,就是信息处理线程并没有发生阻塞,但是永远都不会前进了,当他们为了彼此间的响应而相互礼让,使得没有一个线程能够继续前进,那么就发生了活锁

避免活锁

​ 解决“活锁”的方案很简单,谦让时,尝试等待一个随机的时间就可以了。由于等待的时间是随机的,所以同时相撞后再次相撞的概率就很低了。“等待一个随机时间”的方案虽然很简单,却非常有效,Raft 这样知名的分布式一致性算法中也用到了它。

饥饿

什么是饥饿

高优先级线程吞噬所有的低优先级线程的 CPU 时间。线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。线程在等待一个本身(在其上调用 wait())也处于永久等待完成的对象,因为其他线程总是被持续地获得唤醒。

饥饿问题最经典的例子就是哲学家问题。如图所示:有五个哲学家用餐,每个人要活得两把叉子才可以就餐。当 2、4 就餐时,1、3、5 永远无法就餐,只能看着盘中的美食饥饿的等待着。

解决饥饿

Java 不可能实现 100% 的公平性,我们依然可以通过同步结构在线程间实现公平性的提高。

有三种方案:

保证资源充足公平地分配资源避免持有锁的线程长时间执行

这三个方案中,方案一和方案三的适用场景比较有限,因为很多场景下,资源的稀缺性是没办法解决的,持有锁的线程执行的时间也很难缩短。倒是方案二的适用场景相对来说更多一些。
那如何公平地分配资源呢?在并发编程里,主要是使用公平锁。所谓公平锁,是一种先来后到的方案,线程的等待是有顺序的,排在等待队列前面的线程会优先获得资源。

性能问题

​ 并发执行一定比串行执行快吗?线程越多执行越快吗?

​ 答案是:并发不一定比串行快。因为有创建线程和线程上下文切换的开销。

上下文切换

什么是上下文切换?

​ 当 CPU 从执行一个线程切换到执行另一个线程时,CPU 需要保存当前线程的本地数据,程序指针等状态,并加载下一个要执行的线程的本地数据,程序指针等。这个开关被称为“上下文切换”。

减少上下文切换的方法

无锁并发编程 - 多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的 ID 按照 Hash 算法取模分段,不同的线程处理不同段的数据。CAS 算法 - Java 的 Atomic 包使用 CAS 算法来更新数据,而不需要加锁。使用最少线程 - 避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。使用协程 - 在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

资源限制

什么是资源限制

​ 资源限制是指在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源。

资源限制引发的问题

​ 在并发编程中,将代码执行速度加快的原则是将代码中串行执行的部分变成并发执行,但是如果将某段串行的代码并发执行,因为受限于资源,仍然在串行执行,这时候程序不仅不会加快执行,反而会更慢,因为增加了上下文切换和资源调度的时间。

如何解决资源限制的问题

​ 在资源限制情况下进行并发编程,根据不同的资源限制调整程序的并发度。

对于硬件资源限制,可以考虑使用集群并行执行程序。对于软件资源限制,可以考虑使用资源池将资源复用。

到此这篇关于Java并发编程加锁导致的活跃性问题详解方案的文章就介绍到这了,更多相关Java 并发编程加锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java并发编程如何降低锁粒度并实现性能优化

    在高负载多线程应用中性能是非常重要的.为了达到更好的性能,开发者必须意识到并发的重要性.当我们需要使用并发时, 常常有一个资源必须被两个或多个线程共享. 在这种情况下,就存在一个竞争条件,也就是其中一个线程可以得到锁(锁与特定资源绑定),其他想要得到锁的线程会被阻塞.这个同步机制的实现是有代价的,为了向你提供一个好用的同步模型,JVM和操作系统都要消耗资源.有三个最重要的因素使并发的实现会消耗大量资源,它们是: 上下文切换 内存同步 阻塞 为了写出针对同步的优化代码,你必须认识到这三个因素以及如

  • Java 高并发编程之最实用的任务执行架构设计建议收藏

    目录 前言 1.业务架构 2.技术架构 3.物理架构 高并发任务执行架构 需求场景 业务架构设计 技术架构设计 初始设计 演化阶段一 演化阶段二 演化阶段三 代码设计 总结 前言 随着互联网与软件的发展,除了程序员,架构师也是越来越火的职业.他们伴随着项目的整个生命过程,他们更像是传统工业的设计师,将项目当做生命一般细心雕琢. 目前对于项目架构而言,基本都会需要设计的几个架构. 1.业务架构 项目或者产品的市场定位.需求范围.作用场景都是需要在项目启动初期进行系统性分析的.在设计业务架构中,架构

  • 详解Java并发编程之内置锁(synchronized)

    简介 synchronized在JDK5.0的早期版本中是重量级锁,效率很低,但从JDK6.0开始,JDK在关键字synchronized上做了大量的优化,如偏向锁.轻量级锁等,使它的效率有了很大的提升. synchronized的作用是实现线程间的同步,当多个线程都需要访问共享代码区域时,对共享代码区域进行加锁,使得每一次只能有一个线程访问共享代码区域,从而保证线程间的安全性. 因为没有显式的加锁和解锁过程,所以称之为隐式锁,也叫作内置锁.监视器锁. 如下实例,在没有使用synchronize

  • Java并发编程之阻塞队列深入详解

    目录 1. 什么是阻塞队列 2. 阻塞队列的代码使用 3. 生产者消费者模型 (1)应用一:解耦合 (2)应用二:削峰填谷 (3)相关代码 4.阻塞队列和生产者消费者模型功能的实现 1. 什么是阻塞队列 阻塞队列是一种特殊的队列,和数据结构中普通的队列一样,也遵守先进先出的原则同时,阻塞队列是一种能保证线程安全的数据结构,并且具有以下两种特性:当队列满的时候,继续向队列中插入元素就会让队列阻塞,直到有其他线程从队列中取走元素:当队列为空的时候,继续出队列也会让队列阻塞,直到有其他线程往队列中插入

  • Java并发编程之死锁相关知识整理

    一.什么是死锁 所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进 二.死锁产生的条件 以下将介绍死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁 互斥条件 进程要求对所分配的资源(如打印机〉进行排他性控制,即在一段时间内某资源仅为一个进程所占有.此时若有其他进程请求该资源,则请求进程只能等待 不可剥夺条件 进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能

  • Java多线程并发编程和锁原理解析

    这篇文章主要介绍了Java多线程并发编程和锁原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等方案后,问题得到解决. 加锁方案见下文. 二.乐观锁 & 悲观锁 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁

  • Java并发编程面试之线程池

    目录 什么是线程池 线程池好处 线程池的执行流程 怎么用线程池 corePoolSize maximumPoolSize keepAliveTime unit workQueue threadFactory ejectedExecutionHandler 线程池参数如何设置? 监控线程池 总结 什么是线程池 是一种基于池化思想管理线程的工具.池化技术:池化技术简单点来说,就是提前保存大量的资源,以备不时之需.比如我们的对象池,数据库连接池等. 线程池好处 我们为什么要使用线程池,直接new th

  • Java并发编程加锁导致的活跃性问题详解方案

    目录 死锁(Deadlock) 死锁的解决和预防 1.超时释放锁 2.按顺序加锁 3.死锁检测 活锁(Livelock) 避免活锁 饥饿 解决饥饿 性能问题 上下文切换 什么是上下文切换? 减少上下文切换的方法 资源限制 什么是资源限制 资源限制引发的问题 如何解决资源限制的问题 我们主要处理锁带来的问题. 首先就是最出名的死锁 死锁(Deadlock) 什么是死锁 死锁是当线程进入无限期等待状态时发生的情况,因为所请求的锁被另一个线程持有,而另一个线程又等待第一个线程持有的另一个锁 导致互相等

  • java并发编程_线程池的使用方法(详解)

    一.任务和执行策略之间的隐性耦合 Executor可以将任务的提交和任务的执行策略解耦 只有任务是同类型的且执行时间差别不大,才能发挥最大性能,否则,如将一些耗时长的任务和耗时短的任务放在一个线程池,除非线程池很大,否则会造成死锁等问题 1.线程饥饿死锁 类似于:将两个任务提交给一个单线程池,且两个任务之间相互依赖,一个任务等待另一个任务,则会发生死锁:表现为池不够 定义:某个任务必须等待池中其他任务的运行结果,有可能发生饥饿死锁 2.线程池大小 注意:线程池的大小还受其他的限制,如其他资源池:

  • JAVA并发中VOLATILE关键字的神奇之处详解

    并发编程中的三个概念: 1.原子性 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行. 2.可见性 对于可见性,Java提供了volatile关键字来保证可见性. 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值. 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保

  • GO语言并发编程之互斥锁、读写锁详解

    在本节,我们对Go语言所提供的与锁有关的API进行说明.这包括了互斥锁和读写锁.我们在第6章描述过互斥锁,但却没有提到过读写锁.这两种锁对于传统的并发程序来说都是非常常用和重要的. 一.互斥锁 互斥锁是传统的并发程序对共享资源进行访问控制的主要手段.它由标准库代码包sync中的Mutex结构体类型代表.sync.Mutex类型(确切地说,是*sync.Mutex类型)只有两个公开方法--Lock和Unlock.顾名思义,前者被用于锁定当前的互斥量,而后者则被用来对当前的互斥量进行解锁. 类型sy

  • Java面向对象编程中final关键字的使用方法详解

    在Java中通过final关键字来声明对象具有不变性(immutable),这里的对象包括变量,方法,类,与C++中的const关键字效果类似. immutable指对象在创建之后,状态无法被改变 可以从三个角度考虑使用final关键字: 代码本身:不希望final描述的对象所表现的含义被改变 安全:final对象具有只读属性,是线程安全的 效率:无法修改final对象本身,对其引用的操作更为高效 final 变量 定义final Object a,则a只能被初始化一次,一旦初始化,a的数据无法

  • Java核心编程之文件随机读写类RandomAccessFile详解

    本文实例为大家分享了Android九宫格图片展示的具体代码,供大家参考,具体内容如下 1.RandomAccessFile RandomAccessFile主要用于文件内容的读写访问 2.访问模式 "r":只读方式. "rw":打开以便读取和访问,如果文件不存在则创建文件. "rws": 除了'rw'功能以外,文件内容或者元数据更新时一同写入. "rwd":除了'rw'功能以外,文件内容更新时一同写入. 3.使用案例 pack

  • Java线程编程中isAlive()和join()的使用详解

    一个线程如何知道另一线程已经结束?Thread类提供了回答此问题的方法. 有两种方法可以判定一个线程是否结束.第一,可以在线程中调用isAlive().这种方法由Thread定义,它的通常形式如下: final boolean isAlive( ) 如果所调用线程仍在运行,isAlive()方法返回true,如果不是则返回false.但isAlive()很少用到,等待线程结束的更常用的方法是调用join(),描述如下: final void join( ) throws InterruptedE

  • java并发编程Lock锁可重入性与公平性分析

    目录 一.相似之处:Lock锁 vs Synchronized 代码块 二.Lock接口中的方法 三.不同点:Lock锁 vs Synchronized 代码块 四.锁的可重入性 4.1. synchronized锁的可重入性 4.2.ReentrantLock可重入锁 五.Lock锁的公平性 一.相似之处:Lock锁 vs Synchronized 代码块 Lock锁是一种类似于synchronized 同步代码块的线程同步机制.从Java 5开始java.util.concurrent.lo

  • Java 并发编程学习笔记之核心理论基础

    并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密,这样才能写出高效.安全.可靠的多线程并发程序.本系列会从线程间协调的方式(wait.notify.notifyAll).Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制.在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式.实现源码及其背后的原理.本

  • Java并发编程之线程安全性

    目录 1.什么是线程安全性 2.原子性 2.1 竞争条件 2.2 复合操作 3.加锁机制 3.1 内置锁 3.2 重入 4.用锁保护状态 5.活跃性与性能 1.什么是线程安全性 当多个线程访问某个类时,不管运行时环境采用何种调用方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 无状态的对象一定是线程安全的,比如:Servlet. 2.原子性 2.1 竞争条件 由于不恰当的执行时序而出现不正确的结果的情况,就是竞争

随机推荐