可以点进去看源码
同步的,可重入(计数器),非公平
用在方法和代码块上是对象锁。用在静态方法上是类锁(类的字节码文件对象,Class锁)
获取了类锁的线程和获取了对象锁的线程是不冲突的!(作用于线程)
每个对象内部都有一把锁
,只有抢到那把锁的线程
,才被允许进入对应的代码块执行相应的代码。
随便使⽤⼀个对象作为锁)在书上称之为–>客户端锁,这是不建议使⽤的。 书上想要实现的功能是:给ArrayList添加⼀个 putIfAbsent() ,这需要是线程安全的
话说谁知道你这是哪本书啊艹 为什么不建议啊话说,哦是因为,,艹辣鸡java3y,什么装饰器模式
话说操作系统的锁是锁代码区域的,,这个对象锁呢?
被锁的方法/代码块执行完会释放锁,若是出现异常也会释放(不会因异常导致死锁)
JVM规范:代码块同步是使用monitorenter 和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有 详细说明。但是,方法的同步同样可以使用这两个指令来实现。
任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁
相当于划分临界区?
monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到结束处和异常处,
历史:1.6之前被称为重量级锁,之后为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁
1.5.之后出现,有很多子类。
Condition
条件对象 允许多个读线程同时访问共享资源更加灵活(有许多Synchronized没有的特性,如重入、读写锁)但1.6之后Synchronized有了很多优化
优化操作:适应⾃旋锁,锁消除,锁粗化,轻量级锁,偏向锁
现在两者性能差不多,但Lock还要手动释放。
(Synchronized直接代码块画的范围)
所以用哪个都行,该利用特性的时候记得Lock就行
通过看注释:
依赖于先进先出的等待队列,依靠单个原子int值表示状态(获取中or释放)
子类一般使用内部类来实现需要同步的工作
提供两种模式:独占,共享
在LOCK包中的相关锁(常⽤的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建
⼀般我们叫AQS为同步器
通过CAS原子操作修改state的值,用volatile实现可见性。
好家伙还自己实现队列,,
acquire方法使用了模板设计模式
过程:acquire(int)尝试获取资源,如果获取失败,将线程插⼊等待队列。插⼊等待队列后, acquire(int)并没有放弃获取资源,⽽是根据前置节点状态状态判断是否应该继续获取资源,如果 前置节点是头结点,继续尝试获取资源,如果前置节点是SIGNAL状态,就中断当前线程,否则继 续尝试获取资源。直到当前线程被park()或者获取到资源,acquire(int)结束
关键字:阻塞队列的实现原理,独占模式
互斥,构造方式支持用参数设置为“相对公平”,比sy灵活,使⽤时最标准⽤法是在try之前调⽤lock⽅法,在finally代码块释放锁
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
三个内部类
都是AQS的子类(AQS相当于框架)
非公平:尝试获取锁,失败了就用acquire。acquire()里的tryAcquire之前说子类实现的,应该就是这里?
在ReentrantLock锁上使⽤的是state来表示同步状态(也可以表示重⼊的次数),
除了上面例子中的使用lock.lock()
方法外,还有
lock.lockInterruptibly()
####ReentrantReadWriteLock
默认非公平
也可以公平,等待时间最长的进程将获取读写锁
这公不公平说法谁提出来的,,OS怎么不提这个
不是互斥锁而是读写锁:
实现了ReadWriteLock接口
该接口维护一对锁(读锁和写锁)
理论上并发性更好(毕竟可以同时读了),但是修改并发增加的化,有吧开销花在互斥上了
以一个集合为例,如果初始化后常查询,少修改,那么适合
当尝试公平获取写锁时,应该处于无锁状态
写锁可以获取读锁,读锁不能获取写锁(什么意思?线程获取?写到一半想读了?)
写锁可以降级成读锁,读锁不能升级为写锁
两锁都支持在获取时中断
好家伙,这和中断和os是类似道理不?那个好像是硬件中断指令让cpu住手,,不过OS好像没讲主动让出?只讲了正常退出?
ReentrantReadWriteLock⽐ReentrantLock锁多了两个内部类(都是Lock实现)来维护读锁和写锁,但是 主体还是使⽤Syn:
高16位表示读,低16位表示写
调⽤syn的 acquire(1) 获取写锁,这里有丶像os,if(加上之后 > MAX_COUNT),throw Error
调⽤的是 acquireShared(int arg) ⽅法获取读锁,
公平锁
公平锁理解起来⾮常简单: 线程将按照它们发出请求的顺序来获取锁 ⾮公平锁就是: 线程发出请求的时可以“插队”获取锁 Lock和synchronize都是默认使⽤⾮公平锁的。如果不是必要的情况下,不要使⽤公平锁 公平锁会来带⼀些性能的消耗
线程安全:
ThreadLocal:空间换时间
共享变量在每个线程都有一个副本,每个线程操作的都是自己的副本
,对另外的线程没有影响。
JDK官方的保证线程安全的集合
run()和start()⽅法区别: run() :仅仅是封装被线程执⾏的代码,直接调⽤是普通⽅法 start() :⾸先启动了线程,然后再由jvm去调⽤该线程的run()⽅法。
jvm虚拟机的启动是单线程的还是多线程的? 是多线程的。不仅仅是启动main线程,还⾄少会启动垃圾回收线程的,不然谁帮你回收不⽤的内 存~
既然有两种⽅式实现多线程,我们使⽤哪⼀种???
⼀般我们使⽤实现Runnable接⼝ 可以避免java中的单继承的限制 可以将并发运⾏任务和运⾏机制解耦
Callable就是Runnable的扩展。 Runnable没有返回值,不能抛出受检查的异常,⽽Callable可以
使⽤守护线程的时候要注意的地⽅
如果有线程组,优先级不会超过线程组
servlet线程数据全在栈上,指的是doPost方法的参数之类的?
sy
重入锁:同一线程可以进了又进
重入读写锁:更细粒度的控制(可以具体控制读和写的不同区别,不是统一加锁了)
JDK:Executor提供了⼀种将“任务提交”与“任务执⾏”分离开来的机制(解耦
count++不是原子的
来源维基百科:
⽐较并交换(compare and swap, CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断 的数据交换操作,从⽽避免多线程同时改写某⼀数据时由于执⾏顺序不确定性以及中断的不可预 知性产⽣的数据不⼀致问题。 该操作通过将内存中的值与指定数据进⾏⽐较,当数值⼀样时将内 存中的数据替换为新的值
失败的线程并不会被挂起,⽽是被告知这次竞争中失 败,并可以再次尝试(或者什么都不做)
毕竟挂起的话可能只能等os唤醒,,但CAS原来的操作不是占用资源,没唤醒这一说吧??话说学OS都是拿硬件资源为例,,要自己主动重试的只见过自选
真的好像乐观锁,,
Atomic包⾥的类基本都是使⽤Unsafe实现的包装类。 Unsafe⾥边有⼏个我们喜欢的⽅ 法(CAS):
⽽Unsafe底层实际上是调⽤C代 码,C代码调⽤汇编,最后⽣成出⼀条CPU指令cmpxchg,完成操作。这也就为啥CAS是原⼦性的,因 为它是⼀条CPU指令,不会被打断
ABC三个线程,一个值,A改了,B又改回去,结果C发现跟自己预期值一样
要解决ABA的问题,我们可以使⽤JDK给我们提供的AtomicStampedReference和 AtomicMarkableReference类
简单来说就是在给为这个对象提供了⼀个版本,并且这个版本如果被修改了,是⾃动更新的。 原理⼤概就是:维护了⼀个Pair对象,Pair对象存储我们的对象引⽤和⼀个stamp值。每次CAS⽐较的是 两个Pair对象
啊好吧,,到这里才是乐观锁
ThreadLocal提供了线程的局部变量,每个线程都可以通过 set() 和 get() 来对这个局部变量进⾏操作,但不会和其他线程的局部变量进⾏冲突,实现了线程的数据隔离
最典型的是管理数据库的Connection:数据库连接池的连接交由ThreadLocal来进⾏管理ThreadLocal能够实现当前线程的操作都是⽤同⼀个Connection,保证了事务
ThreadLocal要避免内存泄漏,及时remove。
。JAVA中Volatile的作用:强制每次都直接读内存,阻止重排序,确保voltile类型的值一旦被写入缓存必定会被立即更新到主存。
Thread.sleep() 本线程在未来***毫秒内不参与cpu竞争,操作系统不会在这段时间内重新计算该线程的优先级