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

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的效率高于同步的;

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. 这种处理方式可以更清楚地阐明线程池的运行规则,并避免资源耗尽的风险.



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和本机方法来确保原子操作,从而避免了同步的高开销,并大大提高了执行效率.


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
楼上丢脸
美国佬既然自己送来了