synchronized(自动释放)
静态方法与非静态方法
静态方法:在Java中每个类的实例都有自己的一把锁,不同实例之间互不影响,一把锁只能同时被一个线程获取,其他线程只能等待。
非静态方法:如果锁对象为.class文件或者synchronized修饰的是static方法,则该锁控制类的所有实例的访问。
功能
- 原子性:线程互斥的访问同步代码
- 可见性:保证共享变量修改及时可见
- 有序性:解决重排序问题
原理
synchronized的底层语义通过monitor对象完成,类似的还有wait/notify等方法。
monitor对象一共有两个指令:
- monitorenter:执行该指令尝试获取锁monitor的所有权,该锁可重入。
1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;2.如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权
- monitorexit:执行该指令的线程必须是锁的所有者,每次执行时,monitor的进入数都会减一,直到为0,不再是这个锁的所有者,其他阻塞线程可以尝试获取。
- 指令的执行是JVM通过调用操作系统的互斥原语mutex实现的,来回切换用户态和内核态,对性能的影响较大。
volatile
功能
- 保证变量的内存可见性
- 禁止指令重排序
原理
volatile修饰共享变量后,每个线程操作该变量都会从主内存中将其拷贝到本地内存作为副本,当线程操作副本变量并写回主内存时,会通过CPU总线嗅探机制告知其他线程它们的副本变量已经失效,需要重新从主内存中读取。
**CPU总线嗅探机制:**每个用户处理器通过监听在总线传播的数据来判断自己的本地缓存副本是否已经过期,如果处理器发现自己副本对应的内存地址有修改,则将当前副本数据设置为无效状态,当需要对对应数据进行修改时,会重新从主内存读取数据到本地缓存。
volatile和synchronized的区别
- volatile本质是在告诉JVM当前变量在工作内存中的值是不确定的,需要从主内存中读取;synchronized则是锁定当前变量,只有拥有锁的线程才可以访问该变量
- volatile的作用域为变量;synchronized的作用域为变量、方法和类
- volatile保证变量的可见性;synchronized保证可见性和原子性
- volatile不会引起线程阻塞;synchronized会造成线程阻塞
- volatile修饰的变量不会被编译器优化;synchronized可以被优化
reentrantLock
原理
reentrantLock属于乐观锁类型,新建一个reentrantLock对象,底层则会新建一个NonfairSync对象,其中NonfairSync和fairSync都是基于AQS队列实现的
获取锁
1)CAS操作抢占锁,抢占成功则修改锁的状态为1,将线程信息记录到锁当中,返回state=1
2)抢占不成功,tryAcquire获取锁资源,获取成功直接返回,获取不成功,新建一个检点插入到
当前AQS队列的尾部,并等待唤醒再次去获取锁
释放锁
1)获取锁的状态值,释放锁将状态值-1
2)判断当前释放锁的线程和锁中保存的线程信息是否一致,不一致会抛出异常
3)状态-1直到为0,锁状态值为0表示不再占用,为空闲状态
ThreadLocal
ThreadLocal为变量在每个线程中都创建了副本,使用起来都是在线程的本地工作内存中操作,并且提供了set和get方法来访问变量副本。底层封装了ThreadLocalMap绑定当前线程和副本的关系,各个线程独立并且访问安全。
设计思想:
(1) ThreadLocal作为变量访问的入口;
(2) 每个Thread对象都有ThreadLocalMap对象,这个ThreadLocalMap持有对象的引用;
(3) ThreadLocalMap以当前的threadLocal对象为key,以真正的存储对象为value。get()方法通过threadLocal实例就可以找到当前线程上的副本对象。
public void set(T value) {
Thread t = Thread.currentThread();//1.首先获取当前线程对象
ThreadLocalMap map = getMap(t);//2.获取该线程对象的ThreadLocalMap
if (map != null)
map.set(this, value);//如果map不为空,执行set操作,以当前threadLocal对象为key,实际存储对象为value进行set操作
else
createMap(t, value);//如果map为空,则为该线程创建ThreadLocalMap
}
ThreadLocal只不过是个入口,真正的变量副本绑定到当前线程上的。
//Thread中的成员变量
ThreadLocal.ThreadLocalMap threadLocals = null; //每个Thread线程中都封装了一个ThreadLocalMap对象
//ThreadLocal类中获取Thread类中的ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//ThreadLocal类中创建Thread类中的ThreadLocalMap成员对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
synchronized和reentrantLock的区别
- synchronized本质是JVM层面上的锁,是Java关键字;reentrantLock是JDK1.5后提供的API层面上的锁
- synchronized修饰的代码执行完成后,系统会自动让线程释放锁;reentrantLock需要手动释放,否则会造成死锁现象
- synchronized是不可中断类型的锁,除非执行完成或者出现异常,而reentrantLock可以进行中断(超时,interrupt)
- synchronized不能绑定condition,reentrantLock可以通过condition实现线程精确唤醒
- synchronized只能作为非公平锁使用,reentrantLock没有限制
Runnable和Callable的区别
- Callable规定的方法是call(),Runnable规定的方法是run().
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值
- call方法可以抛出异常,run方法不可以
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
interrupt()和stop()
stop()方法过于暴力,强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。
interrupt()方法通过修改线程的中断状态来告知那个线程, 说它被中断了,中断状态置回为true。
被中断的线程不一定要立即停止正在做的事情。相反,中断是礼貌地请求另一个线程在它愿意并且方便的时候停止它正在做的事情
sleep()和wait()的区别
1、sleep()是Thread类的静态本地方法,wait()是Object类的成员本地方法
2、sleep()是任何地方都可使用,wait()方法只能在同步方法或者同步代码块中使用
3、sleep()会指定当前线程休眠时间,释放CPU资源,不释放对象锁,休眠时间到自动苏醒执行;wait()方法放弃持有的对象锁,进入等待队列,只有当对象被调用notify()/notifyAll()方法后才会竞争对象锁
4、均需要捕获 InterruptedException 异常