一个同步的介绍

电脑杂谈  发布时间:2020-07-29 17:01:32  来源:网络整理

java线程安全的集合_java多线程用哪些实现_实现线程的集中方法

一个同步的介绍

并发时,多个线程需要操作同一资源,这很容易导致错误数据的生成. 为了解决此问题,当有多个线程在操作共享数据时,必须确保只有一个线程在同时操作共享数据. 线程必须等到线程处理完数据后才能继续.

java中的sychronized关键字可以确保只有一个线程可以同时执行某个方法或代码块,而sychronized可以确保一个线程的更改可以被其他线程看到(以确保可见性).

两个同步的应用方法

1. 根据实例方法,当前实例被锁定,进入同步代码块时需要获取当前实例的锁定;

2. 在使用静态方法时,当前类被锁定,在输入同步代码之前需要获取当前类的锁定;

3. 作用于代码块,这需要指定锁定对象,锁定给定的指定对象,并在进入同步代码块之前获得指定对象的锁;

java虚拟机中的同步是基于进入和退出监视对象实现的. 在Java中,同步可以修改同步方法. 同步方法不是由monitorenter和monitorexit指令实现的,而是由方法调用指令隐式调用的,该方法调用指令读取运行时常量池中的ACC_SYNCHRONIZED批注.

Java对象标头和监视器

在Java中,对象在内存中的布局分为三个区域,即对象标头,实例数据和填充数据.

java对象

1. 对象标头

HotSpot虚拟机的对象标头包括markword和klass,数组长度;

markword用于存储对象本身的运行时数据,例如哈希码(HashCode),GC生成年龄,锁状态标志,线程所持有的锁,有偏差的线程ID,有偏差的时间戳等. 虚拟机中数据的长度分别为32位和64位,长度为32位和64位(不带压缩指针),正式称为MarkWord.

对象标头的另一部分是klass类型指针,即对象指向其类元数据的指针. 虚拟机使用此指针来确定对象是哪个类实例.

数组的长度. 如果对象是数组,则对象标头中必须有一段数据来记录数组的长度.

2,实例数据

实例数据部分是对象实际存储的有效信息,也是程序代码中定义的各种字段的内容. 无论是从父类继承还是在子类中定义,都需要对其进行记录.

3. 对象填充

对齐填充不一定存在,也没有任何特殊含义. 它仅用作占位符. 因为HotSpot VM的自动内存管理系统要求对象的起始地址必须是8个字节的整数倍,换句话说,对象的大小必须是8个字节的整数倍. 对象头部分恰好是8个字节的倍数(1倍或2倍). 因此,当对象实例数据部分未对齐时,需要通过对齐填充来完成.

hotspot虚拟机中的Markword结构

重量级锁是同步的对象锁. 锁定标识符为10,指针指向监视对象的起始地址. 每个对象都有一个与其关联的监视器,并且该对象与其监视器之间的关系具有多种实现. 例如,监视器可以与对象一起创建和销毁,也可以程尝试获取对象锁时自动生成,但当监视器为“线程持有它之后,它将被锁定”时.

在Java虚拟机(hotSpot)中,监视器由ObjectMonitor实现;

ObjectMonitor对象

java多线程用哪些实现_实现线程的集中方法_java线程安全的集合

ObjectMonitor中有两个队列,_WaitSet和_EntryList,用于存储ObjectWaiter列表. 每个等待锁定的线程都将封装到一个ObjectWaiter对象中. _owner指向保存ObjectMonitor对象的线程. 当多个线程同时访问同一段时,同步代码时,它将首先进入_EntryList集合. 当线程获取对象的监视器时,它将进入_Owner区域并将监视器中的_owner变量设置为当前线程. 同时,如果将监视器的计数器_count加1,则如果先写入并调用等待,则()方法将释放当前保留的监视器,_owner变量将还原为null,计数减1,然后线程进入_waitSet集合以等待被唤醒. 如果当前线程完成执行,则将释放监视器,并重置变量的值,以便其他线程可以获得监视器锁.

显示器

监视对象存储在每个java对象的对象标头中(指向存储的指针的指针). 同步锁用于以这种方式获取锁. 这就是为什么Java中的任何对象都可以用作锁的原因. 同时,这也是对象方法中notify / notifyAll / wait方法存在的原因.

同步方法

同步代码块

同步代码块是使用monitorenter和moniterexist指令实现的,它们将通过同步块区域中的侦听器对象获取并释放锁.

同步方法和静态同步方法由方法修改器ACC_SYNCHRONIZED实现. 调用该方法时,调用指令将检查是否设置了该方法的ACC_SYNCHRONIZED访问标志. 如果设置了它,则将在执行时首先获取监视器,并且获取成功,仅足以执行方法主体,并在执行方法后释放监视器. 在执行该方法期间,没有其他线程可以获取相同的监视对象.

1. 双重检查锁定的单例模式

单人模式

必须对单例进行可修改的修改,易失性可以防止指令重新排列,单例= new Singleton();实际上分为三个步骤:

1. 为单身人士分配空间; 2.初始化单例; 3.将单例指向分配的内存地址;

由于jvm具有指令重排的特性,执行顺序可能变为1-> 3-> 2. 指令重排在单线程中可能不是问题,但是它可能导致获取线程并且在多线程环境中不进行初始化. 实例.

2. 在JDK1.6之后,同步关键字的底部做了哪些优化?

引入了偏置锁,轻型锁,自旋锁,自适应自旋锁,锁消除,锁粗化等技术,以减少操作开销.

主要有四种锁定状态: 无锁定状态,偏置锁定状态,轻量级锁定状态和重量级锁定状态. 锁可以升级,但不能降级. 这种策略是为了提高获取和释放锁的效率.

3,同步和ReenTrantLock之间的区别

这两个都是可重入锁: 您可以再次获取自己的内部锁,这时,锁计数器的计数将增加1,并且通常会释放该锁,直到锁计数器降至0.

同步化取决于基础的jvm,而ReenTrantLock取决于API.

ReenTrantLock增加了一些比同步更高级的功能: 可以中断等待,可以实现公平锁定,可以实现选择性通知:

(1)ReenTrantLock使用lock.lockInterruptible()实现了等待中断功能,这意味着等待线程可以选择放弃等待;

(2)ReenTrantLock可以指定公平锁或不公平锁,而同步化只能是不公平锁. 公平锁意味着首先等待的线程获得该锁;该实现采用ReentrantLock(布尔公平)构造方法来确定其是否公平.

(3)同步和wait(),notify(),notifyAll()方法的组合可以实现等待通知机制,还可以实现ReenTrantLock类,这需要使用Condition接口和newCondition( ) 方法. 条件具有很好的灵活性. 您可以在Lock对象中创建多个Condition实例(对象监视器). 可以在指定的条件册线程对象,以便您可以有选择地执行线程通知,并且可以灵活地调度线程. 使用notify()/ notifyAll()方法进行通知时,JVM选择了被通知的线程,并且ReenTrantLock类与Condition实例结合使用可以实现选择性通知. sychronized关键字仅等效于整个Lock对象中的一个Condition,并且在其上注册了许多线程. 如果执行notifyAll(),它将通知所有处于等待状态的线程,条件的singalAll()方法将仅唤醒在Condition实例中注册的所有等待对象.

性能不再是一种选择标准: 在高并发性下,ReenTrantLock的效率高于同步的;

实现线程的集中方法_java线程安全的集合_java多线程用哪些实现

3,易失性关键字

在当前的Java模型下,线程可以将变量保存在本地内存(例如寄存器)中,而不是直接在主内存中进行读写,这可能导致线程修改主内存Value中的变量,而另一个线程继续在寄存器中使用其变量的副本,从而导致数据不一致.

可以通过使用volatile关键字将变量声明为volatile来解决此问题,这表明JVM变量是不稳定的,并且每次使用时都必须从主内存中读取它.

volatile关键字用于确保变量的可见性并防止指令重新排序.

4,同步和易失性之间的区别

sychronized关键字可以修改方法,变量和代码块,而volatile只能修改变量;

Volatile是同步的轻量级实现,因此性能要好于同步;但是在JDK1.6之后,锁已进行了优化,并且同步性能得到了改善. 在实际任务中更常用.

将不会阻止对volatile变量的多线程访问,但是将阻止对syncchronized关键字的访问.

volatile关键字可以保证数据的可见性,但不能保证数据的原子性. 同步确保可见性和原子性.

Volatile主要确保线程之间变量的可见性,而syncchronized主要确保多个线程之间的数据同步.

5. 线程池

线程池提供了一种限制和管理资源的方法. 每个线程池还维护一些基本统计信息,例如线程数.

线程池的优点:

通过重用已创建的线程,减少资源消耗,减少线程创建和销毁的开销.

提高响应速度: 当任务到达时,可以立即执行该任务,而无需等待线程的创建.

改善线程的可管理性. 线程是稀缺资源. 如果无限创建它们,则它们不仅会消耗系统资源,还会降低系统的稳定性. 线程池的使用允许统一分配,调整和监视.

6. Runnable和Callable接口的实现之间的区别

如果希望线程池执行任务,则需要实现Runnable或Callable接口. 这两个接口都可以由ThreadPoolExcutor和ScheduledPoolExcutor执行. 但是Runnable接口不会返回结果,而Callable接口可以返回结果.

工具类执行程序可以实现Runnable对象和Callable对象之间的相互转换. (Executors.callable(可运行任务)或Executors.callable(可运行任务,对象处理). )

7. execute()方法和Submit()方法之间的区别

execute()方法用于提交不需要返回值的任务,因此Lunch确定线程池是否成功执行了该任务;

The

submit()方法用于提交需要返回值的任务. 线程池将返回一个将来的类型对象,并使用该对象来确定任务是否成功执行. 并且可以通过将来的get()方法获得返回值. get()方法将阻塞当前线程,直到任务完成为止,而使用get(长时间超时,TimeUnit单位)方法将阻塞当前线程一段时间,并立即返回. 目前,该任务可能尚未完成.

8,创建线程池

通常,建议不要使用Excutor来创建,而要使用ThreadPoolExcutor. 这种处理方式可以更清楚地阐明线程池的运行规则,并避免资源耗尽的风险.

实现线程的集中方法_java线程安全的集合_java多线程用哪些实现

ThreadPoolExcutorsAPI描述

通常,可以通过构造方法来创建

创建施工方法

使用Excutor返回线程池对象存在问题:

FixThreadPool和SingleThreadExutor: 允许的请求队列长度为Integer.MAX_VALUE,这可能会积累太多请求,从而导致OOM,

CachedThreadPool和ScheduledThreadPool: 允许创建的站点数是Integer.MAX_VALUE,这可能会导致创建大量线程,从而导致OOM.

由执行器的工具类“执行者”执行:

使用工具类创建

9,原子原子类

原子表示不能中断操作. 一旦启动,它将不会受到其他线程的干扰. 原子原子类是具有原子/原子操作特性的类.

并发包java.util.concurrent的原子类存储在java.util.concurrent.atomic下:

原子类

主要分为四类:

基本类型: AtomicBoolean,AtomicInteger,AtomicLong

数组类型: AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

引用类型: AtomicReference,AtomicStampedRerence,AtomicMarkableReference

对象属性修改类型: AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicLongFieldUpdater

AtomicInteger的方法:

AtomicInteger API

AtomicInteger类主要使用CAS + volatile和本机方法来确保原子操作,从而避免了同步的高开销,并大大提高了执行效率.

java线程安全的集合_实现线程的集中方法_java多线程用哪些实现

atomicInteger源代码

CAS的原理是将期望值与原始值进行比较,如果相同则更新为新值. UnSafe类的objectFieldOffset()方法是本地方法. 此方法用于获取“原始值”的内存地址,返回值为valueOffset. 另外,value是一个可变变量,在内存中可见,因此JVM可以保证任何线程始终可以随时获取该变量的最新值.

10. 使用CAS实现单例模式

Cas实现单例模式

此实现中存在一个循环,该循环基于忙等待算法并依赖于基础硬件的实现,这等效于在不进行线程切换的情况下将其锁定并阻止其他消耗,并且可以在很大程度上支持并行性.

CAS的一个重要缺点是,如果未能成功执行繁忙的等待,则会给CPU带来更大的执行开销. 如果多个线程同时执行到singleton = new Singleton(),将创建大量的Singleton对象,这可能会导致内存溢出,因此不建议这样做.

11,AQS

AQS是用于构建锁和同步器的框架. AQS可用于轻松高效地构造大量被广泛使用的同步器,例如ReentrantLock,Semaphore和其他诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask. 等等都是基于AQS的. 当然,我们也可以使用AQS轻松地构造满足我们自己需求的同步器.

12java多线程用哪些实现,先于规则后发生

某些指令可以重新排列,而某些指令则不能重新排列. 规则如下:

程序顺序规则: 保证线程中的语义序列化. 例如,如果第二条语句取决于第一条语句的执行结果java多线程用哪些实现,则第二条语句不能在第一条语句之前执行.

易失性原理: 易失性防止指令重排. 在读取之前先写易失变量,以确保易变变量的可见性.

锁定规则: 先解锁,然后再锁定后续步骤. 不能将锁定重新安排为解锁之前的状态,因此无法获得太多的锁定行为.

传递性: A优先于B,B优先于C,然后A优先于C.

线程的start()在其每个动作之前.

线程的所有操作都程终止之前.

线程中断先于中断线程的代码.

对象的构造函数的执行和终止在finalize()方法之前.

13,ReadWritLock

(1)Java并发库中的ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入功能

(2)ReetrantReadWriteLock读写锁的效率明显高于同步关键字

(3)在实现ReetrantReadWriteLock读写锁时,读锁使用共享模式;写锁使用互斥模式. 换句话说,没有写锁时,多个线程可以同时保持读锁. 写锁是互斥的

(4)在实现ReetrantReadWriteLock读写锁时,应注意,当存在读取锁时,无法获得该写入锁;当存在写锁时,除了获得写锁的线程外,还可以获取读锁. 其他线程无法获取读取锁

14. 线程池处理流程

(1)确定线程池中的核心线程是否都在执行任务. 如果有空闲或尚未创建的核心线程,请创建一个新的工作线程来执行任务;如果所有核心线程都在执行任务,则进入下一个过程.

(2)线程判断工作队列是否已满,如果工作队列未满,它将在此工作队列中存储新提交的任务. 如果工作队列已满,请进入下一个过程.

(3)确定线程池中的线程是否都处于工作状态,如果不是,则创建一个新的工作线程来执行任务,如果线程已满,则移交饱和策略来处理任务.


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/ruanjian/article-288988-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...