章节情况:
第一部分
为什么需要lock
第一点:就是说synchronized 它只有在执行完代码,或者里面抛出异常的时候,才会进行释放锁。
第二点:不够灵活,加锁和释放锁的时机单一
第三点:不知道能不能获取到锁
lock的优点
lock():方法要是陷入了死锁,就会一直进行等待。所以,也非常的不好。
为了解决这个问题,出现了tryLock()这个方法
下面先介绍tryLock的兄弟方法
如果一定时间内,可以得到锁就成功,不能得到就放弃。用了tryLock这个方法可以避免死锁的问题,一般来说,死锁就是,锁一获取到了A对象,而这个A对象想去获取B对象内容。而锁二获取到了B对象,而它想去获取A对象的内容,由于它们相互锁着,着就导致了死锁的产生。这时候,如果使用了tryLock就可以解决这个问题了。就以刚刚这个例子为例:锁获取了A对象,而这个A对象想去获取B对象,而B对象呢也被锁住,它想去获取A对象。这个时候,如果用tryLock去获取的话,就可以给他传入时间,如果一定时间内获取不到,那么就代表获取失败,这时候我可以先将我锁住的A对象进行释放。然后,再重新执行之前的程序,那么由于A对象被释放了,所以,锁二就可以获取到A对象了。最终结果就是B对象也会被释放,当之前程序第二次执行时,就可以先锁A对象,再锁B对象了。
tryLock:成功避免了死锁的方法,虽然他们会进行争夺资源,但是他们也会进行谦让。成功避免了死锁发生。
上述代码如下:
package lock;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述: 用tryLock来避免死锁
*/
public class TryLockDeadlock implements Runnable {
//采取不同的标志位来执行不同的逻辑,来模拟锁争抢的情况
int flag = 1;
static Lock lock1 = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
TryLockDeadlock r1 = new TryLockDeadlock();
TryLockDeadlock r2 = new TryLockDeadlock();
r1.flag = 1;
r2.flag = 0;
new Thread(r1).start();
new Thread(r2).start();
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (flag == 1) {
try {
if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
System.out.println("线程1获取到了锁1");
Thread.sleep(100);
//在里面已经获取到第一把锁了,现在准备尝试获取第二把锁
if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
System.out.println("线程1获取到了锁2");
System.out.println("线程1获取到了两把锁");
} finally {
lock2.unlock();
}
} else {
System.out.println("线程1没有获取到第二把锁,已重试");
}
//说明没有拿到这把锁
} else {
//它获取失败,但是,还是想重新获取,就使用for来实现
System.out.println("线程1获取锁1失败,已重试");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock1.unlock();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
if (flag == 0) {
try {
if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
System.out.println("线程2获取到了锁2");
Thread.sleep(100);
//在里面已经获取到第一把锁了,现在准备尝试获取第二把锁
if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
try {
System.out.println("线程2获取到了锁1");
System.out.println("线程2获取到了两把锁");
} finally {
lock1.unlock();
}
} else {
System.out.println("线程2没有获取到第二把锁,已重试");
}
//说明没有拿到这把锁
} else {
//它获取失败,但是,还是想重新获取,就使用for来实现
System.out.println("线程2获取锁2失败,已重试");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock2.unlock();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
在等待锁的过程中,线程可以被中断,这个是synchronized所不具备的能力。
测试代码如下:
package lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockInterruptibly implements Runnable {
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
LockInterruptibly lockInterruptibly = new LockInterruptibly();
Thread thread0 = new Thread(lockInterruptibly);
Thread thread1 = new Thread(lockInterruptibly);
thread0.start();
thread1.start();
try{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
thread0.interrupt();
}
@Override
public void run() {
System.out.println( Thread.currentThread().getName() + "尝试获取锁");
try {
//如果拿不到锁就会在等待
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + "获取到了锁");
//不然第二个线程轻易的拿到锁
Thread.sleep(5000);
}catch (InterruptedException e){
System.out.println("睡眠期间被中断了");
}finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了锁");
}
} catch (InterruptedException e) {
System.out.println("等锁期间被中断了");
}
}
}
结果如下:
新建了两个线程,其中肯定有一个线程会先获取到锁的,但是不知道是哪一个能最先获取到。如上面我的测试,是Thread 0 先获取到锁,然后,Thread 0 就会一直执行,这时候,它在sleep过程中,被打断了。这时候,0就会释放锁。Thread 1就可以获得锁,然后1就会执行,然后执行结束,进行释放。
那么如果就这个情况,打断的是Thread1 由于它是在等待期间被中断的 。那就不会再执行了。结果如下:
这个中断功能是synchronized所不具备的功能。所以,lock比较灵活。
unlock要注意的就是获取锁后 先写一个try 然后,再写一个finally 把它进行解锁。确保锁安全。因为,要是忘了unlock 或者中途中抛出异常而没有执行unlock,导致没有解锁的话,就会陷入死锁。
在tryLock期间和lockInterruptibly期间都可以响应中断。
锁的分类:
悲观锁:互斥同步锁
乐观锁:非互斥同步锁
优先级反转:解释如下:如果线程中有一个优先级比较低,但是,它获取到了执行的权利,在他执行过程中,锁住了对象,如果锁的时间过长,其他线程虽然,优先级比较高,但是,它还是需要进行等待。这种就叫做优先级反转。
具体的逻辑如下:线程一和线程二对共享资源同时进行计算,如果线程一计算完毕,发现他修改期间的数据没有被别改过。那么他就进行修改。而线程二进行计算,这个时候,他发现,他修改的数据已经被该过了,那么他就选择报错或者重试了。
因为如果使用悲观锁,我在写代码的时间内 我会把仓库锁住,你是不能提交的。如果我写了一天 那么这一天你就不能提交了。
总结一下:悲观锁适用于写入多,并发大,乐观锁适用于,写入少,大部分是读取的场景。让读取性能极大提高。
场景:电影院买票
package lock.reentrantlock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 通过多线程模拟电影院预定座位
* 证明了ReentrantLock是一个可重用锁
*/
public class CinemaBookSeat {
private static Lock lock = new ReentrantLock();
public static void bookSeat(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "开始预定座位");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "完成预定座位");
Thread.sleep(500);
}catch(Exception e){
e.printStackTrace();
}
finally {
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(() -> {
bookSeat();
}).start();
new Thread(() -> {
bookSeat();
}).start();
new Thread(() -> {
bookSeat();
}).start();
new Thread(() -> {
bookSeat();
}).start();
}
}
结论如下:
一定要等第一个线程先释放了锁,剩下的线程才可以继续获得该锁。也是符合他的使用场景的。
总结:当我们需要确保任务的顺序时,就可以使用锁来提供帮助。
reentrant:可再进入的,可重入的。
可重入锁:同一个线程可以多次获取到同一个锁。
好处:避免死锁
假设两个方法都被锁锁住了,A运行第一个方法,它拿到这个锁了,如果它不是可重入锁,它他就没办法运行B方法了,因为要无法拿到B这个锁,需要A先释放锁,B再去拿这个锁。如果不是可重入性就死锁了,手里拿着这把锁,还想获得这把锁,这样就是死锁。
package lock.reentrantlock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
private static Lock lock = new ReentrantLock();
private void a(){
lock.lock();
try{
System.out.println("执行a方法");
b();
}finally {
lock.unlock();
}
}
private void b(){
lock.lock();
try{
System.out.println("执行b方法");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
//由于ReentrantLock是可重入性锁,所以,a拿到了该锁,b也可以拿到该锁。两个方法都可以拿到该锁,不会导致死锁
new Test().a();
}
}
结论如下:
package lock.reentrantlock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
/**
* 注意在Lock接口中是没有这个方法的,只有在ReentrantLock这个类中有
* 通过这个方法来查询当前线程锁定的个数
*/
System.out.println(lock.getHoldCount());
lock.lock();
System.out.println(lock.getHoldCount());
lock.lock();
System.out.println(lock.getHoldCount());
lock.lock();
System.out.println(lock.getHoldCount());
lock.lock();
System.out.println(lock.getHoldCount());
lock.unlock();
System.out.println(lock.getHoldCount());
lock.unlock();
System.out.println(lock.getHoldCount());
lock.unlock();
System.out.println(lock.getHoldCount());
lock.unlock();
System.out.println(lock.getHoldCount());
}
}
通过getHoldCount()这个方法可以查询当前线程保持此锁定的个数,即调用lock()方法的次数。
总结:可重入性,就是当前线程可以调用它锁多次,而且,不同方法之间也可以进行调用锁来进行锁定。并不会说在同一个线程中,这个锁锁住了就不能获取它了。
可重入锁分析:
c代表的是当前的占有锁的线程个数,如果c > 0 说明当前的线程已经有锁锁住了, 则对它进行加一。
如果释放锁的话也是如此。当c==0时,才给他进行释放。
非可重入锁,它只有 0 和 1这两种情况。
可重入锁一些其他方法
举个例子:比如说买火车票,我前面的人买完了,然后,轮到我了,这时候,我由于排太久队伍还蒙了一下。这时候,他回来问了一句说,几点开车。他是属于清醒状态。而我是属于蒙的状态。这时候他确实也算是插队了。java默认策略是非公平的
因为,在把挂起的线程恢复的时间,它是需要时间的。如果严格要求公平,那么这段时间谁都拿不到锁。 但是,如果采用非公平的策略。那么可能发生如下的情况:
A请求锁,B线程被挂起,当A释放锁的时候,这个时候,B进行唤醒。但是,此时,C希望拿到锁,那么C就去拿锁了。这个时候,C进行执行完毕。进行释放,然后B也唤醒完全,这时候B也可以拿到这个锁。这就实现了双赢。
ReentrantLock默认是一个非公平锁,但是,在创建的时候,传入true就会成为公平锁了。
下面以代码来演示
package lock.reentrantlock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 演示公平和不公平两种情况
*/
public class FairLock {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new Job(printQueue));
}
for (int i = 0; i < 10 ; i++) {
threads[i].start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Job implements Runnable{
private PrintQueue printQueue;
public Job(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始打印");
printQueue.printJob(new Object());
System.out.println(Thread.currentThread().getName() + "打印完毕");
}
}
class PrintQueue{
//创建一个公平锁
private Lock queueLock = new ReentrantLock(true);
public void printJob(Object document){
queueLock.lock();
try{
System.out.println(Thread.currentThread() + "正在打印,需要1秒");
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
finally {
queueLock.unlock();
}
//如果使用的是非公平锁,这时候,虽然线程1-9都在排队,但是此时线程0又需要锁了,所以,会先给线程0
queueLock.lock();
try{
System.out.println(Thread.currentThread() + "正在打印,需要1秒");
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
finally {
queueLock.unlock();
}
}
}
上面使用的是公平锁,那么他的执行顺序完全就是按照排队的队列中来。
这里是按照顺序添加的十个线程:每个线程都有打印任务,都需要获取锁,那么结果就是
当线程9执行完以后,按照排队规则,下一个应该就是线程0 会严格按照顺序来进行执行。
当使用的是非公平策略时。
当线程0执行完打印以后,释放锁后,队列中拍着线程1正准备唤醒,这时候,线程0又想获得锁了。这时候,因为线程0是处于活动状态,所以,系统会把这个锁先给线程0,这样就是非公平策略。
结果如下:符合预期
公平锁会去判断,有没有在队列中,但是非公平锁就不会去判断。它是直接尝试去获取
这就是解决了ReentrantLock的问题。就是在读的过程中是没有线程安全问题的
因为要是有人读有人写的话,会出现问题。
案例:电影院升级版
程序模拟:
package lock.reentrantlock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CinemaReadWrite {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
//生成一个读锁
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
//生成一个写锁
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void read(){
readLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
System.out.println(Thread.currentThread().getName() + "释放了读锁");
readLock.unlock();
}
}
private static void write(){
writeLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写数据");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
System.out.println(Thread.currentThread().getName() + "释放了写锁");
writeLock.unlock();
}
}
public static void main(String[] args) {
new Thread(() -> read(),"Thread1").start();
new Thread(() -> read(),"Thread2").start();
new Thread(() -> write(),"Thread3").start();
new Thread(() -> write(),"Thread4").start();
}
}
结论如下:
线程1 和线程2是可以一起读的,但是,线程3想去写的时候,发现前面有线程在读,那不行啊。就在等待,等待他执行完毕以后,在进行写,当线程4想去写的时候,发现也不行,线程3还在写,于是,就等线程3执行完毕以后,才执行线程4。
如果读的线程太多 ,可能线程3很久都不能写 ,因为会有很多线程再来想读取。
总结:
注意:这里的写锁插队指的是,如果读锁在读,想获取写锁,就可以插队。防止它一直写不到
如果是,写锁在写,那么它也得乖乖的等着,直到轮到它为止,当然,前面如果是读锁,也可能被其他读锁在进行插队。
首先,先进入ReentrantReadWriteLock源码,在里面的结构中找到,NonfairSync和FairSync其对应的是公平和非公平
在这里看读锁是不是应该排队和写锁是不是应该排队。
所以,在公平的情况下,只要队列中有人在排队,你就得排队。
现在,我们看一下非公平的情况:
它是直接返回false 直接就是不排队的,等前面的读执行完毕,就开始写(就是开始插队),因为一般读锁的情况多。
所以,对于非公平策略,写锁直接就可以插队,当然,如果是前面有读锁的话,要等他执行完毕。
读锁 调用的FirstQueuedIsExclusive()判断队列的第一个是不是排它锁,看方法名就知道其大概的内容。
这里的排它锁就是写锁。所以说,如果是想获得写锁的线程的话,就不插队了。如果是想获得读锁的线程的话就插队。因为在一个线程池中能运行的线程是有限的(这是自己理解,比如,对我自己电脑
来说,16线程,就算有很多读锁的线程,也需要在队列中排队,正常只能让16个线程在线程池中进行执行)
下面进行的是代码演练
package lock.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CinemaReadWrite {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
//生成一个读锁
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
//生成一个写锁
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void read(){
readLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
System.out.println(Thread.currentThread().getName() + "释放了读锁");
readLock.unlock();
}
}
private static void write(){
writeLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写数据");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
System.out.println(Thread.currentThread().getName() + "释放了写锁");
writeLock.unlock();
}
}
public static void main(String[] args) {
new Thread(() -> write(),"Thread1").start();
new Thread(() -> read(),"Thread2").start();
new Thread(() -> read(),"Thread3").start();
new Thread(() -> write(),"Thread4").start();
new Thread(() -> read(),"Thread5").start();
}
}
结果如下:当第3个线程在读的过程中,看看第五个线程有没有读,由于,第四个线程是写,在排队的第一个线程是写锁的线程,所以,它就只能是等待了,不能插队。
注意:在非公平策略下,读锁在读取数据的过程中,如果有读锁开始获取,如果前面队列中的第一个是写锁,那么它是不能插队的,但是,如果是读锁。是可以插队的。
其实模拟这个还是比较困难的,因为理论上,当第一个为读锁的时候,它是很快就去读的,我们需要在它从获取锁的队列中激活的时间中,去获取到,才可以插队成功。
下面用代码进行模拟:
package lock.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CinemaReadWrite {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
//生成一个读锁
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
//生成一个写锁
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void read(){
System.out.println(Thread.currentThread().getName() +"准备获取读锁");
readLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
System.out.println(Thread.currentThread().getName() + "释放了读锁");
readLock.unlock();
}
}
private static void write(){
writeLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写数据");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
System.out.println(Thread.currentThread().getName() + "释放了写锁");
writeLock.unlock();
}
}
public static void main(String[] args) {
new Thread(() -> write(),"Thread1").start();
new Thread(() -> read(),"Thread2").start();
new Thread(() -> read(),"Thread3").start();
new Thread(() -> write(),"Thread4").start();
new Thread(() -> read(),"Thread5").start();
new Thread( new Runnable() {
@Override
public void run() {
Thread thread[] = new Thread[1000];
for (int i = 0; i < 1000; i++) {
thread[i] = new Thread(() -> read(),"子线程创建的Thread" + i);
}
for (int i = 0; i < 1000; i++) {
thread[i].start();
}
}
}).start();
}
}
当Thread1释放了写锁的时候,其实在锁队列中排着的第一个是Thread2这个需要的是一个读锁,这个时候,在线程1释放写锁以后,其实子线程创建的Thread264准备获取读锁,其实它现在处于激活状态,所以,在非公平的策略下,它可以进行插队。
如果是公平锁的话,就完全按照队列来进行分配锁。
作用的引入:比如说我有一个任务,刚开始的时候是写入,后来的时候是读取,这时候我不希望一直是写锁的状态。这样浪费资源。如果可以支持降级,从我的写锁拿到我的读锁,再把我的写锁释放掉,这样就提高了整体的效率。支持锁的降级但是不可以升级,因为降级简单。
代码如下:
package lock.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Upgrading {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);
//生成一个读锁
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
//生成一个写锁
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void readUpgrading(){
readLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
try {
System.out.println("升级会带来阻塞");
Thread.sleep(1000);
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "获取到了写锁,升级成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
System.out.println(Thread.currentThread().getName() + "释放了读锁");
readLock.unlock();
}
}
private static void writeDowngrading(){
writeLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写数据");
try {
Thread.sleep(1000);
readLock.lock();
System.out.println("在不释放写锁的情况下,直接获得读锁,成功降级");
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了写锁");
writeLock.unlock();
}
}
public static void main(String[] args) {
new Thread(()->writeDowngrading(),"Thread1").start();
new Thread(()-> readUpgrading(),"Thread2").start();
}
}
结论如下:
因为不能又读又有写,如果我们想升级为写,要求其他的运行读锁的线程执行完毕。
如果两个拥有读锁的线程都想升级,那么它们就得等别的线程先把读锁释放,它们就得等,这时候线程A就会等线程B,线程B也会等着线程A 那么它们就会构成死锁。
它会一直执行,直到修改成功。
自己写一个简单的自旋锁
代码如下:
package lock.readwrite;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁
*/
public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();
public void lock(){
Thread current = Thread.currentThread();
while (!sign.compareAndSet(null,current)){
System.out.println("自旋获取失败,再次尝试");
}
}
public void unlock(){
Thread current = Thread.currentThread();
sign.compareAndSet(current,null);
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始尝试获取自旋锁");
spinLock.lock();
System.out.println(Thread.currentThread().getName() + "获取到了自旋锁");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了自旋锁");
}
}
};
Thread thread = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread.start();
thread2.start();
}
}
结果如下:
这里可以看到,线程1获取到了自旋锁,但是,线程0获取不到。所以,可以看到线程0中会一直在判断,判断它能不能获取到,其cpu是一直在工作的。
但判断到,线程1释放了自旋锁以后,线程0才开始获得自旋锁。
第一种:Java虚拟机对锁的优化
自旋锁和自适应:在尝试自旋锁的时候,比如说尝试10或者20次以后,虚拟机就会把锁转为阻塞锁
锁消除:就是说它检测到有一些锁本身就已经是安全的,这时候就会把锁进行消除。
锁粗化:指的是比如说synchronized 加锁的是两个对象,那么它就会把他合成一个。这样就是锁粗化。
第二点的原因:由于你方法可能内容会比较多,而且方法以后可能还会加东西,所以,最好不要锁住方法。
第三点:十个线程都想用写的操作,改进以后把想写的内容都放在了一个线程上,进行写,这样就只要加锁一次。
热点就是需要加锁的数据,避免人为制造热点的意思是:比如说hashMap 的size() 我需要知道map中的数量。那么如果调用size就需要给它加锁,这个时候我们就可以取一个变量,然后,当hashMap中加1的时候,给他加1。这样就给它进行加1了。少了一个热点。