精华内容
下载资源
问答
  • java多线程的应用
    千次阅读
    2021-06-11 19:59:58


    锁应用于java多线程中的同步机制,我们知道线程安全的问题大部分是由于多个线程并发的访问共享变量或共享数据。于是我们想到将并发访问变为串行访问,既一次只能有一个线程访问共享数据。这就是锁的思想
    如果你学过操作系统的多线程同步机制的话,相信你会对这部分有更好的理解。在操作系统中,多线程的同步是通过p,v原语,既waite(),signal()来解决线程同步问题,在访问临界区之前,所谓临界区就是p,v原语中间的代码,我们首先要对线程进行资源的申请操作p,访问结束后我们使用v归还资源。
    java多线程中的锁的概念和操作系统的锁的概念大同小异。对一个线程进行加锁,就是进行P操作向处理机请求分配资源并告诉其他线程当前线程正在使用临界区,你们没有锁这个钥匙不可访问。当这个线程访问结束后系统由JVM自动进行释放当前线程的锁的操作(相当于V)。锁具有排他性,一个锁一次只能被一个线程持有,因此这种锁被称为排他锁或互斥锁。
    锁的分类
    按照java虚拟机对锁的实现方式的划分,我们将锁分为内部锁和显示锁。其中,内部锁是通过synchronized 关键字来实现的。显示锁则是通过java.comcurrent.locks.Lock接口的实现类实现的。
    内部锁synchroniezd关键字
    java中任何一个对象都有一个内部锁。内部锁是一种排他锁。
    内部锁是通过synchonized关键字来实现的,synchronized关键字可以修饰方法或代码块,被修饰的方法或代码块被称为同步方法或同步代码块,同步方法的方法体就是一个临界区。在对共享数据的访问时,线程必须向处理器申请该同步代码块的锁,当线程执行完毕后在对其进行释放。整个过程中,锁的申请和释放都是有java虚拟机代为实施,所以这是synchronized被称为内部锁的原因。
    内部锁的调度
    回想我们在操作系统中学过的同步机制,当一个临界区正在被一个线程占用的时候,其他线程是无法申请资源也无法进入这个临界区的,那么这些线程去哪儿了呢?当前一线程申请的临界区资源被释放又是怎么调度其他曾经申请该临界区的线程的?我们知道,当线程访问的临界区正在被占用时,处理器会将当前想访问却无法访问临界区的线程阻塞,并放入一个阻塞队列的最末尾,如果有多个线程想访问当前临界区也是由阻塞队列代为保管线程。等到占用线程被释放时,处理器会调用阻塞队列中的一个线程变为就绪状态申请资源进入临界区。
    内部锁的调入也是这样。不同的是,我们将内部锁的阻塞队列称之为入口集,用于记录等待获得相应内部锁的线程。在java中对入口集中锁的调动支持的是非公平调用,具体如何调用应当参照当前java虚拟机所实现的调用算法。
    显示锁 Lock接口
    显示锁是java.util.concurrent.locks.Lock的接口实例,该接口对显示锁进行了抽象。方法如图所示

    voidlock()获取锁
    voidlockInterruptibly 如果当前线程为被中断,则获取锁
    ConditionnewCondition() 返回绑定到此Lock实例的新condition实例
    booleantryLock() 仅在调用时锁为空闲状态才获取该锁
    voidunLock() 释放锁
    private final Lock lock = ... ;//创建一个Lock接口实例
    ......
    lock.lock() ; //申请该锁
    try{
    //共享区域
    ....
    }finally{
    //总是在finally中释放锁,避免**锁泄漏
    lock.unlock(); //释放Lock
    }
    

    这里简单解释一下锁泄漏
    所谓锁泄漏指的是一个线程获取某个锁之后,由于程序的错误,导致该锁一直无法进行释放而导致其他线程无法获取该锁的现象。锁泄漏可能导致死锁,锁死等故障。
    显示锁与内部锁的比较
    内部锁是基于代码块或方法体的锁,显示锁是基于对象的锁。显示锁可以充分发挥面向对象编程的灵活性。内部锁的申请与释放只能在一个方法内执行,而显示锁支持在一个方法中申请所,另一个方法中释放锁。内部锁中不存在锁泄漏问题,显示锁中要警惕锁泄漏。

    更多相关内容
  • 主要介绍了Java多线程锁机制相关原理实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Java多线程锁机制

    千次阅读 2019-04-01 16:12:25
    1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动...

    一、并发与并行

    提到多线程,首先要理解两个概念,并发与并行。
    并发,指的是两个或多个事件在同一时间段内发生。在操作系统的多道程序环境下,并发性是指在同一时间,宏观上有很多程序在同时执行,然而在单CPU系统中,每一时刻仅能有一道程序执行,所以微观上,这些程序只是在交替执行。
    并行,指的是两个多个事件在同一时刻点发生。计算机系统中若有多个CPU,则可以将这些可并发执行的程序分配到多个处理器上,实现多任务并行执行。
    对于线程来说,宏观上这些具有并发性的任务是并行执行的,但微观上它们是串行执行的,即一个接一个去运行。当系统只有一个CPU时,线程会根据某种顺序被执行,我们将其称作线程调度。
    在这里插入图片描述

    二、进程和线程

    进程:
    进程指的是一个内存中运行的应用程序,是程序的执行过程。每个进程都有其独立的一块内存空间。一个应用程序可以同时启动多个进程。如在Windows系统中,一个运行的exe文件就是一个进程。
    线程:
    线程是进程中的一个执行任务(控制单元),一个进程可以同时并发运行多个线程。一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个执行任务,即多线程。

    **多进程:**操作系统中同时运行的多个程序
    **多线程:**在同一个进程中同时执行的多个任务

    线程和进程的区别:
    内存角度:进程有独立的内存空间,进程中的数据存放空间(堆和栈)是独立的,进程中至少有一个线程;线程实际上没有其独立的内存空间,堆空间是共享的,栈空间是独立的。
    资源角度:进程是资源分配的基本单位,线程是资源调度的基本单位,实际上不拥有资源,消耗的资源也比进程小,线程相互之间可以影响,又称为轻型进程或进程元。

    因为一个进程中的多个线程是并发执行的,从微观角度上考虑是有先来后到之分,所以线程执行顺序完全取决于CPU调度器,程序员控制不了。
    我们可以把多线程并发性看做多个线程在瞬间抢占CPU资源,谁抢到资源就运行(抢占式任务调度)。这也造就了多线程的随机性。
    Java程序的进程里至少包含主线程和垃圾回收线程(后台线程)。

    三、创建和启动线程

    方式1:继承Thread类

    (1)定义一个类A,继承于java.lang.Thread
    (2)在A类中覆盖Thread中的run()方法
    (3)在run()方法中编写需要执行的操作(线程执行体)
    (4)在main中创建线程对象,并启动线程
    注意:启动线程要调用对象的start方法,不要调用run方法,
    若调用run方法则是对象调用方法,仍然只有一个线程,并未开启新的线程。
    

    方式2:实现Runnable接口

    (1)定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类
    (2)在A类中覆盖Runnable接口中的run方法
    (3)在run()方法中编写需要执行的操作(线程执行体)
    (4)在main中创建线程对象,并启动线程
    

    两种方式的比较:
    (1)设计角度
    Java中类是单继承的,如果继承了Thread类,该类就不能再有其他的直接父类;而Java中类可以实现多个接口,此时该类还可以继承其他类,并且还可以实现其他接口,从设计角度看更为优雅。
    (2)操作角度
    继承方式较为简单,获取线程名字也简单;实现方式稍微复杂一些,获取线程名字要使用Thread.currentThread()来获取当前线程的引用。
    (3)资源角度
    继承方式不能做到多线程共享同一个资源;实现方式可以实现多线程共享同一个资源。

    四、线程锁

    在多线程同步机制中,若想实现在资源互斥下进行线程调度,常会用到线程锁技术。常用的方式有Lock接口synchronized关键字

    Lock和synchronized有以下几点不同:
    1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
    2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
    3)Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
    4)通过Lock可以知道有没有成功获取锁(调用tryLock方法),而synchronized却无法办到。
    5)Lock可以提高多个线程进行读操作的效率。

    在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
      
    举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。

    但是采用synchronized关键字来实现同步的话,就会导致一个问题:
      如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
      因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
      另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的

    ps:线程执行start()方法后转换为就绪态
    执行start()方法,将线程调入就绪队列,等待CPU分配时间片(资源调度)后即可运行,同时线程会自动调用其run()方法。

    展开全文
  • 主要介绍了Java编程中的互斥,信号量和多线程等待机制实例详解,简单介绍了互斥和信号量的区别,需要的朋友可以了解下。
  • Java多线程 -

    千次阅读 2022-03-28 15:31:49
    Java多线程 - 三性 可见性 指的是线程之间的可见性,一个线程对状态的修改,对其他线程是可见的。在 Java中 volatile、synchronized 和 final 实现可见性。 原子性 如果一个操作是不可分割的,我们则称之为...

    Java多线程 - 锁

    三性

    • 可见性

      指的是线程之间的可见性,一个线程对状态的修改,对其他线程是可见的。在 Javavolatilesynchronizedfinal 实现可见性。

    • 原子性

      如果一个操作是不可分割的,我们则称之为原子操作,也就是有原子性。比如i++,就不是原子操作。在Javasynchronized和在lockunlock 中操作保证原子性

    • 有序性

      一系列操作是按照规定的顺序发生的。如果在本线程之内观察,所有的操作都是有序的,如果在其他线程观察,所有的操作都是无序的;前半句指“线程内表现为串行语义”后半句指“指令重排序”和“工作内存和主存同步延迟”。Java语言提供了volatilesynchronized 两个关键字来保证线程之间操作的有序性。volatile是因为其本身包含“禁止指令重排序”的语义,
      synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

    Volatile

    作用

    • 保证了可见性。
    • 防止指令重排序(有序性)。
    • 半个原子性: 对任意单个volatile变量的读/写具有原子性

    读-写时内存语义

    • 写的内存语义

    • 当写一个volatile变量时,JMM会把该线程对应的本地内存中共享变量值刷新会共享内存

    • 读的内存语义

      当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。

    Volatile的内存屏障实现

    是否可以重排序第二个操作第二个操作第二个操作
    第一个操作普通读/写volatile读volatile写
    普通读/写YESYESNO
    volatile读NONONO
    volatile写YESNONO

    编译器生成字节码文件时会在指令序列中插入内存屏障来禁止特定类型的处理器排序。但是,在实际执行过程中,只要不改变volatile的内存语义,
    编译器可以根据实际情况省略部分不必要的内存屏障。

    放置的内存屏障

    在每个volatile写操作前面插入StoreStore屏障
    在每个volatile写操作后面插入StoreLoad屏障
    在每个volatile读操作后面插入LoadLoad屏障
    在每个volatile读操作后面插入LoadStore屏障

    什么是指令重排序

    执行任务的时候,为了提高编译器和处理器的执行性能,编译器和处理器(包括内存系统,内存在行为没有重排但是存储的时候是有变化的)会对指令重排序。编译器优化的重排序是在编译时期完成的,指令重排序和内存重排序是处理器重排序

    编译器优化的重排序

    在不改变单线程语义的情况下重新安排语句的执行顺序。比如,在第10行创建了临时变量,在使用前才进行初始化。或者,下面的情况。

    int a = 3;
    a = 5;
    
    int a = 5
    

    指令级并行重排序

    处理器的指令级并行技术将多条指令重叠执行,如果不存在数据的依赖性,为了使 CPU 内部的运算单元能够尽量被充分利用,处理器可能会对输入的字节码指令进行重排序处理,也就是处理器优化。

    现在的CPU一般采用流水线来执行指令。一个指令的执行被分成:取址、译码、访存、执行、写回、等若干个阶段。然后,多条指令可以同时存在于流水线中,同时被执行。指令流水线并不是串行的,多个指令可以同时处于同一个阶段,只要CPU内部相应的处理部件未被占满即可。

    比如说CPU有一个加法器和一个除法器,那么一条加法指令和一条除法指令就可能同时处于“执行”阶段, 而两条加法指令在“执行”阶段就只能串行工作。

    a++; 
    b=f(a);  //会阻塞直到a++有结果
    c--; //没有因果关系,所以,可以先计算。但是,这个先计算是发生在CPU执行时
    

    像这样有依赖关系的指令如果挨得很近,后一条指令必定会因为等待前一条执行的结果,而在流水线中阻塞很久,占用流水线的资源。而编译器的乱序,作为编译优化的一种手段,则试图通过指令重排将这样的两条指令拉开距离, 以至于后一条指令进入CPU的时候,前一条指令结果已经得到了,那么也就不再需要阻塞等待了。比如将指令重排为:

    a++; c--; b=f(a);
    

    相比于CPU的乱序,编译器的乱序才是真正对指令顺序做了调整。但是编译器的乱序也必须保证程序上下文的因果关系不发生改变。

    内存系统的重排序

    因为使用了读写缓存区,使得看起来并不是顺序执行的。

    内存重排序实际上并不是真的相关操作被排序了,而是因为CPU引入高速缓存还没来得及刷新导致;

    寄存器是什么和高速缓存什么区别:

    CPU要取数据,处理数据,都要放到寄存器处理。一般寄存器不用太大,它只要存放指令一次操作的数据就够了。

    高速缓存是内存的部分拷贝,因为高速缓存速度快,把常用的数据放这里可以提高速度。

    每个CPU都有自己的缓存,为了提高共享变量的写操作,CPU把整个操作变成异步的了,如果写入操作还没来的及同步到其它CPU,就有可能发生其它CPU读取到的是旧的值,因此看起来这条指令还没执行一样。

    多CPU架构中,多个CPU高速缓冲区数据同步,依赖于缓存一致性协议

    缓存一致性协议

    在多处理器的情况下,每个处理器总是嗅探总线上传播的数据来检查自己的缓存是否过期,当处理器发现自己对应的缓存对应的地址被修改,
    就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行操作的时候,会重新从系统中把数据督导处理器的缓存里。这个协议被称之为缓存一致性协议。

    CPU对于Volatile的支持

    在有volatile修饰的共享变量进行写操作的时候会多出一条带有lock前缀的汇编代码,而这个lock操作会做两件事:

    • 将当前处理器的缓存行的数据同步到系统内存。lock信号确保存在该信号期间CPU可以独占共享内存。在之前通过锁总线的方式,现在采用锁缓存的方式。
    • 这个写回操作会使其他处理器的缓存中缓存了该地址的缓存行无效。在下一次这些CPU需要使用这些地址的值时,强制要求去共享内存中读取。

    总结

    volatile提供了一种轻量级同步机制来完成同步,它可以保操作的可见性、有序性以及对于单个volatile变量的读/写具有原子性,对于符合操作等非原子操作不具有原子性。

    volatile通过添加内存屏障(特殊的字节码)及缓存一致性协议(lock指令)来完成对可见性的保证。

    Synchronized

    三种应用方式

    • 修饰实例方法,作用于当前实例(this)加锁,进入同步代码前要获得当前实例的锁

    • 修饰静态方法,作用于当前类对象(XX.class)加锁,进入同步代码前要获得当前类对象的锁

    • 修饰代码块,指定加锁对象,对给定对象(object)加锁,进入同步代码库前要获得给定对象的锁。

    Synchronized 锁升级

    在Java SE1.6里锁一共有四种状态,无锁状态偏向锁状态轻量级锁状态重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

    升级过程

    1. 一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。
    2. 一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了。检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程。如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的),此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁;如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
    3. 轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

    其中,偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。

    Synchronized底层原理

    Java对象结构

    Java对象结构

    • 实例变量:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
    • 填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。(相当于C语言的内存对其)
    • 对象头:jvm中采用2个字来存储对象头(如果对象是数组则会分配3个字,多出来的1个字记录的是数组长度),其主要结构是由Mark Word 和 Class Metadata Address 组成。

    Java对象头结构

    虚拟机位数头对象结构说明
    32/64bitMark Word存储对象的hashCode、锁信息或分代年龄或GC标志等信息
    32/64bitClass Metadata Address类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。

    Mark Word在默认情况下存储着对象的HashCode、分代年龄、锁标记位等以下是32位JVM的Mark Word默认存储结构

    锁状态25bit4bit1bit是否是偏向锁2bit 锁标志位
    无锁状态对象HashCode对象分代年龄001

    由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据:
    Mark Word特殊结构

    加上最初的默认状态,一共有5种变化。

    重量级锁

    Monitor对象

    Synchronized为重量级锁时,指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其monitor之间的关系有存在多种实现方式。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的。

    JVM: 即Java Virtual Machine,JAVA编译后生成的JAVA字节码是在JVM上跑,须要由JVM把字节码翻译成机器指令。主流的JVM包括Hotspot、Jikes RVM等。JVM运行的码文件是.jar

    DVM:即Dalvik Virtual Machine,是安卓中使用的虚拟机。每一个进程相应着一个Dalvik虚拟机实例。Dalvik虚拟机运行的是Dalvik字节码。Dalvik使用即时编译(JIT)。因为JVM是Oracle公司(原SUN公司)的产品,Google担心版权的问题,既然Java是开源的,索性就研究了JVM,写出了DVM。Android执行的码文件是.dex

    ART:Android最新的虚拟机,使用AOT技术。

    JIT:JIT会在运行时分析应用程序的代码,识别哪些方法可以归类为热方法,这些方法会被JIT编译器编译成对应的汇编代码,然后存储到代码缓存中,以后调用这些方法时就不用解释执行了,可以直接使用代码缓存中已编译好的汇编代码。

    AOT:预编译。在运行之前,就对其包含的Dex字节码进行翻译,得到对应的本地机器指令,于是就可以在运行时直接执行了。

    ObjectMonitor的主要数据结构如下:

    ObjectMonitor() {
        _header       = NULL;
        _count        = 0; //记录个数
        _waiters      = 0,
        _recursions   = 0;
        _object       = NULL;
        _owner        = NULL; //指向持有ObjectMonitor对象的线程
        _WaitSet      = NULL; //保存ObjectWaiter对象列表.处于wait状态的线程,会被加入到_WaitSet
        _WaitSetLock  = 0 ;
        _Responsible  = NULL ;
        _succ         = NULL ;
        _cxq          = NULL ;
        FreeNext      = NULL ;
        _EntryList    = NULL ; //保存ObjectWaiter对象列表.处于等待锁block状态的线程,会被加入到该列表
        _SpinFreq     = 0 ;
        _SpinClock    = 0 ;
        OwnerIsThread = 0 ;
      }
    

    每个等待锁的线程都会被封装成ObjectWaiter对象。

    当多个线程同时访问一段同步代码时:

    1. 首先会进入 _EntryList 集合,当线程获取到对象的monitor后,进入 _Owner区域并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count加1;
    2. 若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒;
    3. 若当前线程执行完毕,也将释放monitor(锁)并复位count的值,以便其他线程进入获取monitor(锁);

    总结:monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时也是notify/notifyAll/wait等方法存在于顶级对象Object中的原因。

    Synchronized重量级锁底层原理

    对于Synchronized代码块,在字节码中使用monitorentermonitorexit实现。

    3: monitorenter  //进入同步代码
    //..........省略其他  
    15: monitorexit   //退出同步代码
    16: goto          24 //正常退出,继续执行
    //省略其他.......
    21: monitorexit //异常结束时被执行的释放monitor 的指令
    

    偏向锁

    HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁是为了在只有一个线程执行同步块时提高性能。

    优势

    当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。

    流程
    1. 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态。
    2. 如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)
    3. 如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。
    4. 如果CAS获取偏向锁失败,则表示有竞争(CAS获取偏向锁失败说明至少有过其他线程曾经获得过偏向锁,因为线程不会主动去释放偏向锁)。当到达全局安全点(safepoint)时,会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着(因为可能持有偏向锁的线程已经执行完毕,但是该线程并不会主动去释放偏向锁),如果线程不处于活动状态,则将对象头设置成无锁状态(标志位为“01”),然后重新偏向新的线程;如果线程仍然活着,撤销偏向锁后升级到轻量级锁状态(标志位为“00”),此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。
    5. 执行同步代码
    总结

    偏向锁只适用于一个线程竞争获得锁的情况,如果出现另一个线程竞争,立即升级为轻量级锁

    轻量级锁

    轻量级锁是为了在线程近乎交替执行同步块时提高性能。

    流程
    1. 在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如下图所示。
      锁记录
    2. 拷贝对象头中的Mark Word复制到锁记录中。
    3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(3),否则执行步骤(4)。
    4. 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如下图所示。
      轻量级锁定状态内存图
    5. 如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,若当前只有一个等待线程,则可通过自旋稍微等待一下,可能另一个线程很快就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

    Synchronized锁类型

    • synchronized 锁是非公平锁。

    • synchronized是可重入锁,所以不会自己把自己锁死

    Java锁

    CAS操作

    使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。

    CAS操作(又称为无锁操作)是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。

    因此,线程就不会出现阻塞停顿的状态。那么,如果出现冲突了怎么办?无锁操作是使用**CAS(compare and swap)**又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。

    操作过程

    CAS比较交换的过程可以通俗的理解为CAS(V,O,N),包含三个值分别为:

    • V 内存地址存放的实际值
    • O 预期的值(旧值)
    • N 更新的新值。

    更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

    举例:

    1.在内存地址V当中,存储着值为10的变量。

    2.此时线程1想把变量的值增加1.对线程1来说,旧的预期值A=10,要修改的新值B=11。

    3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

    4.线程1开始提交更新,首先进行A和地址V的实际值比较,发现A不等于V的实际值,提交失败。

    5.线程1 重新获取内存地址V的当前值,并重新计算想要修改的值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。

    6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行比较,发现A和地址V的实际值是相等的。

    7.线程1进行交换,把地址V的值替换为B,也就是12.

    Java锁的种类

    公平锁/非公平锁

    • 公平锁:加锁前先查看是否有排队等待的线程,有的话优先处理排在前面的线程,先来先得。

    • 非公平锁:线程加锁时直接尝试获取锁,获取不到就自动到队尾等待。

    可重入锁/不可重入锁

    • 可重入锁:当线程获取某个锁后,还可以继续获取它,可以递归调用,而不会发生死锁

    Java的可重入锁有:reentrantLock(显式的可重入锁)、synchronized(隐式的可重入锁)

    • 不可重入锁:与可重入相反,获取锁后不能重复获取,否则会死锁(自己锁自己)。

    独享锁/共享锁

    • 独占锁:指该锁一次只能被一个线程所持有。

    对ReentrantLock和Synchronized而言都是独占锁

    • 共享锁:指该锁可以被多个线程锁持有。

    对ReentrantReadWriteLock其读锁是共享,其写锁是独占写的时候只能一个人写,但是读的时候,可以多个人同时读

    互斥锁/读写锁

    互斥锁

    在访问共享资源之前对其进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。

    如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源。举个形象的例子:多个人抢一个马桶。

    读写锁

    读写锁既是互斥锁,又是共享锁,read模式是共享,write是互斥(排它锁)的。

    读写锁有三种状态 :读加锁状态、写加锁状态和不加锁状态

    乐观锁/悲观锁

    乐观锁

    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐 观锁适用于多读的应用类型,这样可以提高吞吐量 。

    悲观锁

    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁( 共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程 )。

    分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    并发容器类的加锁机制是基于粒度更小的分段锁,分段锁也是提升多并发程序性能的重要手段之一。

    容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    在ConcurrentHashMap中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设使用合理的散列算法使关键字能够均匀的分部,那么这大约能使对锁的请求减少到越来的1/16。也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。

    自旋锁

    自旋锁,就是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

    自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。自旋等待不能替代阻塞,虽然它可以避免线程切换带来的开销,但是它占用了CPU处理器的时间

    如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,它不会做任何有意义的工作,这样反而会带来性能上的浪费。

    展开全文
  • Java多线程|同步与锁机制

    千次阅读 2016-06-03 20:06:43
    总结 Java与同步」机制

    目录

    1 同步的含义

    2 保证线程安全的本质

    3 死锁发生的四个条件

    4 JVM发生死锁如何恢复

    5 乐观锁与悲观锁

    (1)乐观锁

    (2)悲观锁

    6 活锁与死锁

    (1)活锁

    (2)死锁

    7 锁的形式与种类

    (1)内置锁(独占锁的形式)

    (2)显式锁

    (3)显式锁-ReentrantLock

    (4)读写锁-ReadWriteLock(读写锁接口)

    8 可重入锁机制

    9 锁优化手段

    (1)锁分解与锁分段技术(阿姆达尔定律应用)

    (2)替代独占锁

    (3)非阻塞同步机制

    (4)原子变量

    (5)CAS(Compare And Swap)机制

    (6)CAS之ABA问题

    (7)偏向锁


    ==========================【导读】[开始]========================== 

            工作中实践到了多线程与高并发应用,也踩了一些沉重的坑。
    万丈高楼起于垒土,学习与总结+工作实践不可相离。分三部分总结这块知识。
    知识体系详见第一张思维导图。本篇主题“同步与锁机制”。

    ==========================【导读】[结束]========================== 

    1 同步的含义

    1 原子性
    2 内存可见性(一个线程修改对象的状态后,其他线程可看到状态的改变)

    2 保证线程安全的本质

    保证线程安全的手段
    (1)不在线程之间共享该状态变量。
    (2)将状态变量修改为不可变的变量。
    (3)在访问状态变量时使用同步。

    3 死锁发生的四个条件

    互斥,持有,不可剥夺,环形等待

    4 JVM发生死锁如何恢复

    JVM 在解决死锁,只能重启恢复程序。

    5 乐观锁与悲观锁

    (1)乐观锁

    对于细粒度的操作,有一种更高效的方法,既乐观锁机制。通过这种方法可以在不发生干扰的情况下完成更新操作。
    这种方法需要借助冲突检查机制来判断在更新过程中是否存在来自其他线程的干扰,如果存在,这个操作将失败,
    并且可以重试(也可以不重试)。

    (2)悲观锁

    独占锁是一项悲观锁技术一它假设最坏的情况(如果你不锁门,那么搞蛋鬼就会闯人并搞得一团糟),
    并且只有在确保其他线程不会造成干扰(通过获取正确的锁)的情况下才能执行下去。

    6 活锁与死锁

    (1)活锁

        活锁(Livelock)是另一种形式的话跃性问题,该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,
    而且总会失败。活锁通常发生在处理事务消息的应用程序中:如果不能成功地处理某个消息,那么消息处理机制将回滚整个事务,并
    将它重新放到队列的开头,线程将不断重复执行相同的操作,而且总会失败。
        当多个相互协作的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程都无法继续执行时,就发生了活锁。这就像两个
    过于礼貌的人在半路上面对面地相遇:他们彼此都让出对方的路,然而又在另一条路上相遇了。因此他们就这样反复地避让下去。
        要解决这种活锁问题,需要在重试机制中引人随机性。

    (2)死锁

    <2.1>致命拥抱死锁

    	在单线程的Executor中,如果一个任务将另一个任务提交到同一个 Excutor,并且等待这个
    被提交任务的结果,那么通常会引发死锁。第二个任务停留在工作队列中,并等待第一个任务完
    成,而第一个任务又无法完成,因为它在等待第二个任务的完成。资源循环等待死锁。

    <2.1>线程饥饿死锁

    在线程池中的任务需要无限期地等待一些必须由池中其他任务才能提供的资源或条件,例如某个
    任务等待另一个任务的返回值或执行结果,那么除非线程池足够大,否则将发生线程饥饿死锁。

    7 锁的形式与种类

    (1)内置锁(独占锁的形式)

         任何Java对象都可以用作同步锁,为了便于区分,将其称为内置锁。当然这个“内置锁”是
    要与synchronized 配合使用的。所以内置锁指的是“synchronized”与synchronized配合使用的
    任何Java对象。
        Object中对内置锁进行操作的一些方法。
    Wait系列:使当前已经获得该对象锁的线程进入等待状态,并释放该对象锁。
    Notify系列:唤醒那些正在等待该对象锁的线程,使其继续运行。

    (2)显式锁

        Lock接口中定义了一组抽象的加锁操作。与内置加锁机制不同的是,Lock提供了一种无条件的、
    可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。在Lock的实现中
    必须提供与内部锁相同的内存可见性语义,但在加锁语义、调度算法、顺序保证以及性能特性等方
    面可以有所不同。
        ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。
    ReentrantLock还提供了可重人的加锁语义。与synchronized相比提供了更高的灵话性。

    (3)显式锁-ReentrantLock

    <3.1>使用形式

    //可指定公平锁,非公平锁(默认)模式
    //ReentrantLock(boolean fair)
    Lock lock = new ReentrantLock();
    lock.lock();
    //定时的与可轮询的锁获取模式tryLock
    //boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    try{
    	//更新对象的操作
    	//捕获异常并处理
    }
    finally {
    	lock.unlock();
    }

    <3.2>为什么要创建一种与内置锁如此相似的新加锁机制?

        内置锁无法中断一个正在等待获取锁的线程,或者无法在请求获取一个锁时无限地等待下去。内置锁必须在获取该锁
    的代码块中释放,这就简化了编码工作,并且与异常处理操作实现了很好的交互,但却无法实现非阻塞结构的加锁规则。
        ReentrantLock使用确更加灵活。ReentrantLock不能完全替代synchronized的原因:它更加“危险”,当程序的执行控
    制离开被保护的代码块时,不会自动情除锁。需要在finally 块中释放锁。

    <3.3>ReentrantLock公平性

        在ReentrantLock的构造函数中提供了两种公平性选择:创建一个非公平的锁(默认)或者一个公平的锁。
    在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许“插队”。非公平锁
    的性能要高于公平锁的性能。

    <3.4>ReentrantLock定时锁与轮询机制

    支持定时与轮询锁机制
    可以检测死锁和从死锁中恢复过来,即显式使用Lock类中的定时tryLock 功能来代替内置锁(Synchronized)机制。
    当使用内置锁时,只要没有获得锁,就会永远等待下去,而显式锁则可以指定一个超时时限( Timeout),  在等待超过
    该时间后tryLock会返回一个失败信息。如果超时时限比获取锁的时间要长很多,那么就可以在发生某个意外情况后重
    新获得控制权。

    (4)读写锁-ReadWriteLock(读写锁接口)

        ReentrantReadWriteLock实现了一种在多个读取操作以及单个写人操作情况下的加锁规则:
    如果多个读取操作都不会修改共享资源,那么这些读取操作可以同时访问该共享资源,但在执行
    写人操作时必须以独占方式来获取锁。对于读取操作占多数的数据结构,ReadWriteLock能提供比
    独占锁更高的并发性。

    8 可重入锁机制

        当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重人的,
    因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的
    操作的粒度是“线程”,而不是“调用。重人的一种实现方法是,为每个锁关联一个获取计数值和一个所有
    者线程。当计数值为0时,这个锁就被认为是没有被任何线程持有。  当线程请求一个未被持有的锁时,JVM
    将记下锁的持有者,并且将获取计数值置为1.  如果同一个线程再次获取这个锁,计数值将递增,而当线程
    退出同步代码块时,计数器会相应地递减。当计数值为0时,这个锁将被释放。重入避免了死锁的发生。
        内置锁,显式锁ReentrantLock都是可重入锁。

    9 锁优化手段

    (1)锁分解与锁分段技术(阿姆达尔定律应用)

    阿姆达尔定律应用(降低锁的粒度)
        大多数井发程序都与农业耕作有着许多相似之处,它们都是由一系列的并行工作和串行工作组成的。
    Amdahl定律描述的是: 在增加计算资源的情况下,程序在理论上能够实现最高加速比,这个值取决于程
    序中可并行组件与串行组件所占的比重。假定F是必须被串行执行的部分,那么根据Amdahl定律,在包含
    N个处理器的机器中,最高的加速比为:
        S<=1/(F+(1-F)/N)
        注:F为串行执行占比。
    该定律的应用:降低锁的粒度。
        具体包括:锁分解(一个锁分解为两个)
        锁分段(一个锁分解为多个,效率提升更加有效)
        ConcurrentHashMap的实现中使用了一个包含16个锁的数组,每个锁保护所有散列桶的116,其中
    第N个散列桶由第(N mod 16) 个锁来保护。假设散列函数具有合理的分布性,并且关键字能够实现
    均匀分布,那么这大约能把对于锁的请求减少到原来的1/16. 

    (2)替代独占锁

    内置属于独占锁机制。可用显式锁,读写锁机制替代独占锁使用。
    内置锁,在java6中得到了优化,和显式锁性能几乎相当,ReentrantLock,性能略微高于内置锁。

    (3)非阻塞同步机制

        非阻塞算法被广泛地用于在操作系统和JVM中实现线程/进程调度机制、垃圾回收机制以及销和其他并发数据结构。
    非阻塞算法是基于底层原子机器指令(如比较交换指令CAS)代替锁确保数据并发访问的一致性。非阻塞同步机制在多
    个线程在竟争相同的数据时不会发生阻塞;不存在死销和其他活跃性问题,效率更高。

    (4)原子变量

        原子变量类提供了在整数或者对象引用上的细粒度原子操作,并使用了现代处理器中提供的底层原语(如CAS)。
    共有12个原子变量类,可分为4组:标量类(Scalar). 更新器类、数组类以及复合变量类。最常用的原子变量就是标量
    类: AtomicInteger, AtomicLong、AtomicBoolcan以及AtomicReference,所有这些类都支持CAS,此外,AtomicInteger
    和AtomicLong还支持算术运算。
        在原子变量类中同样没有重新定义hashCode或equals方法,每个实例都是不同的。与其他可变对象相同,它们也
    不宜用做基于散列的容器中的键值。

    (5)CAS(Compare And Swap)机制

        CAS(比较并交换)包含了3个操作数一需要读写的内存位置V、进行比较的值A和拟写人的新值B。
    当且仅当V的值等于A时,CAS才会通过原子方式用新值B来更新V的值,否则不会执行任何操作。无论
    位置V的值是否等于A,都将返回V原有的值。这种变化形式被称为比较并设置,无论操作是否成功都
    会返回.CAS的含义是:“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的
    值实际为多少”。CAS是一项乐观的技术,它希望能成功地执行更新操作,并且如果有另一个线程
    在最近一次检查后更新了该变量,那么CAS能检测到这个错误。
        当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线
    程都将失败。然而,失败的线程并不会被挂起,而是被告知在这次竞争中失败,并可以再次尝试。由
    于一个线程在竞争CAS时失败不会阻塞,因此它可以决定是否重新尝试,或者执行一些恢复操作,也
    或者不执行任何操作。这种灵话性就大大减少了与锁相关的话跃性风险。

    (6)CAS之ABA问题

        ABA问题是一种异常现象: 如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令时就可能
    出现这种问题(主要在没有垃圾回收机制的环境中)。在CAS操作中将判断“V的值是否仍然为A?",并且如果是的
    话就继续执行更新操作。 在某些算法中,如果V的值首先由A变成B,再由B变成A,那么仍然被认为是发生了变化,
    并需要重新执行算法中的某些步骤。
        如果在算法中采用自己的方式来管理节点对象的内存,那么可能出现ABA问题。在这种情况下,有一个相对
    简单的解决方案: 不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号。即使这个值由A变为B,
    然后又变为A,版本号也将是不同的。

    (7)偏向锁

        偏向锁也是JDK 1.6 中引人的一项锁优化,它的目的是消除数据在无竞争情况下
    的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用
    CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消
    除掉,连CAS操作都不做了。

     

    展开全文
  • Java多线程的理解与使用

    万次阅读 多人点赞 2017-10-14 18:45:51
    作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等 )
  • Java中的锁机制

    千次阅读 2021-06-07 20:57:14
    锁机制无处不在,锁机制是实现线程同步的基础,锁机制并不是Java锁独有的,其他各种计算机语言中也有着锁机制相关的实现,数据库中也有锁的相关内容。这篇文章就是从Java入手,深入学习、理解Java中的锁机制,提升...
  • Java多线程机制

    千次阅读 2018-12-25 13:37:57
    线程序机制:每个java程序都有一个主线程,当jvm加载代码,发现main方法,此时就会启动主线程,在mian方法执行过程中再创建的线程就是其他线程线程的四种状态: 新建状态:Thread类被创建 运行状态:执行run...
  • Java提供了多种多线程锁机制的实现方式,常见的有: synchronized ReentrantLock Semaphore AtomicInteger等 每种机制都有优缺点与各自的适用场景,必须熟练掌握他们的特点才能在Java多线程应用开发时得心应手。...
  • JUC多线程:synchronized锁机制原理 与 Lock锁机制

    万次阅读 多人点赞 2021-08-26 08:53:31
    synchronized 通过当前线程持有对象锁,从而拥有访问权限,而其他没有持有当前对象锁的线程无法拥有访问权限,保证在同一...synchronized 锁机制Java 虚拟机中的同步是基于进入和退出监视器锁对象 monitor 实现的
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...
  • JAVA多线程并发

    千次阅读 2022-02-17 14:41:51
    JAVA线程实现/创建方式 1.继承Thread类 Thread类本质上时实现了Runnable接口的一个实例,代表一个现成的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个...
  • volatile保证可见性与部分有序性,但是不保证原子性,保证原子性需要借助synchronized这样的锁机制 volatile(最底层:lock add) ●保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这...
  • JAVA-线程安全与锁机制详解

    千次阅读 2018-10-06 16:00:09
    JAVA中操作共享数据按照线程安全程度大致分为5类: 不可变,绝对线程安全,相对线程安全,线程兼容和线程对立 不可变 只要一个不可变的对象被正确的构建出来,没有发生this引用逃逸,那其外部的可见状态永远...
  • Java多线程安全问题和

    千次阅读 2020-02-28 17:32:26
    多线程安全问题和 文章目录多线程安全问题和锁线程在jvm中的特点的出现synchronized 关键字-监视器monitor lock死锁的产生和避免 什么是线程安全问题? 当多个线程同时操作同一个数据是,可能会出现数据不一样...
  • java 多线程 面试题整理(更新......)

    千次阅读 2021-11-30 16:23:06
    3、什么是同步执行和异步执行4、Java中实现多线程有几种方法?(较难)(1)继承Thread类(2)实现runable接口(3)实现Callable接口(创建FutureTask(Callable)对象)5、Future接口,Callable接口,FutureTask实现类的...
  • Java 多线程 并发 Java线程面试题

    千次阅读 2018-05-12 00:02:12
    1) 什么是线程?线程是操作系统能够进行运算调度的最小单位,它被...Java在语言层面对多线程提供了卓越的支持,它也是一个很好的卖点。2) 线程和进程有什么区别?线程是进程的子集,一个进程可以有很多线程,每条线...
  • Java多线程-6】synchronized同步

    千次阅读 2020-03-31 15:35:49
    前文描述了Java多线程编程,多线程的方式提高了系统资源利用和程序效率,但多个线程同时处理共享的数据时,就将面临线程安全的问题。 例如,下面模拟这样一个场景:一个售票处有3个售票员,出售20张票。 public ...
  • 10第章Java多线程机制Java程序设计10.1线程基本概念ONTENTS10.2线程的创建方法目录10.3线程状态及转换10.4线程调度10.5线程常用方法10.6线程同步与锁机制 10.7线程的交互10.8小结CC10.1线程基本概念2. 并行计算...
  • java 多线程面试题及答案

    千次阅读 2021-09-18 09:20:33
    并发操作系统会根据任务调度系统给线程分配线程的 CPU 执行时间,线程的执行会进行切换。 线程和进程的区别? 1、进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)一个程序至少有一个...
  • Java锁机制详解.pdf

    2017-11-02 17:05:48
    Java锁机制详解.pdf java线程 java多线程 Java锁机制详解.pdf java线程 java多线程
  • [Java多线程 五]---JAVA有哪些种类

    万次阅读 多人点赞 2017-09-02 12:09:25
    转载自: ...上一篇既然提到了,这一篇来详细介绍JAVA中的,也为之后JUC下的做一个铺垫 ... 自旋 ,自旋的其他种类,阻塞,可重入 ,读写 ,互斥 ,悲观 ,乐观 ,公平 ,偏向, 对象线程
  • java多线程创建创建线程的方式Runnable和Callable的区别Thread类中的start()和run()方法有什么区别?什么导致线程阻塞?3. 多线程同步和怎么检测一个线程是否持有对象监视器Condition?4. 线程池CyclicBarrier和...
  • Java多线程面试题(面试必备)

    万次阅读 多人点赞 2020-05-26 01:15:38
    文章目录一、多线程基础基础知识1. 并发编程1.1 并发编程的优缺点1.2 并发编程的三要素1.3 并发和并行有和区别1.4 什么是多线程多线程的优劣?2. 线程与进程2.1 什么是线程与进程2.2 线程与进程的区别2.3 用户线程...
  • Java 多线程(超详细)

    千次阅读 2021-01-12 21:14:38
    多线程学习思路:为什么学习线程?为了解决CPU利用率问题,提高CPU利用率。 =》 什么是进程?什么是线程? =》 怎么创建线程?有哪几种方式?有什么特点? =》 分别怎么启动线程? =》 多线程带来了数据安全问题,该...
  •   synchronized是JAVA中解决并发编程中最常用的方法。   synchronized的作用如下:   1、确保线程互斥访问同步代码;   2、保证共享变量的修改能够及时可见;   3、有效解决指令重排序问题。   ...
  • 讲述了线程同步问题,同步,死锁以及加锁的缺点。主要是分享认知上的普及,框架一定要简单,不要炫技,可以出错,但不要让错误向下传递。
  • Java多线程学习(吐血超详细总结)

    万次阅读 多人点赞 2015-03-14 13:13:17
    本文主要讲了java多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
  • 线程概述 线程的创建 1. 继承Thread类 2. 实现Runnable接口 3. 实现Callable接口 ...3. 方法三:Lock(): 4. 对比三种方法 5. 典型例题 6. 线程死锁问题 线程通信 两个方法 sleep()方法和wait() 典型例题

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 229,401
精华内容 91,760
关键字:

java多线程锁机制

java 订阅