目录
Synchronized原理和锁升级
Synchronized使用场景
- Synchronized修饰实例方法:为当前实例this加锁
- Synchronized修饰静态方法:为当前Class实例加锁
- Synchronized修饰代码块:为Synchronized后面括号里修饰的实例加锁
注意: - 同一个类的不同实例拥有不同的锁,因此不会相互阻塞。
- 使用Synchronized修饰Class和实例时,由于Class和实例分别拥有不同的锁,因此不会相互阻塞。
- 如果一个线程正在访问实例的一个Synchronized修饰的实例方法时,其它线程不仅不能访问该Synchronized修饰的实例方法,该实例的其它ynchronized修饰的实例方法也不能访问,因为一个实例只有一个监视器锁,但是其它线程可以访问该实例的无Synchronized修饰的实例方法或Synchronized修饰的静态方法。
Synchronized如何保证线程安全
1.Synchronized保证原子性
Synchronized保证只有一个线程能拿到锁,进入同步代码块
2.synchronized保证可见性
执行synchronized时,对应的lock原子操作会让工作内存中从主内存中更新共享变量的值
3.synchronized保证有序性
synchronized后,虽然进行了重排序,保证只有一个线程会进入同步代码块,也能保证有序性。
Synchronized锁升级
synchronized的锁升级,说白了,就是当JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级。
synchronized是悲观锁,在操作同步资源之前需要给同步资源先加锁,这把锁就是存在Java对象头里的。得到锁的线程能访问同步资源。
Java对象头中的MarkWord
Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
无锁
CAS
偏向锁
适用情况
一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
偏向锁的偏是指会偏向第一个获得锁的线程。
原理
当一个线程访问同步代码块并获取锁时,会通过对象头里存储锁偏向的线程ID。
优点
在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测对象头里是否存 储着指向当前线程的偏向锁。
引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的锁执行操作。
轻量级锁
适用情况
当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,适用在多线 替执行同步块的情况
原理
当一个线程访问同步代码块并获取锁时,会通过CAS操作修改对象头里锁的状态位标记为轻量级锁
优点
在多线程交替执行同步块的情况下,用CAS进行加锁和解锁而不是直接用重量级锁,避免性能消耗
重量级锁
monitor锁
锁升级自己的一些理解:
偏向锁是一段同步代码一直被一个线程所访问,只需要第一次用CAS存储在Mark Word里存储锁偏向的线程ID,后续就直接判断这个线程ID在不在,不需要再使用CAS了。
轻量级锁就是多线程交替执行同步块的情况下,每次都是用CAS操作尝试将对象的MarkWord更新为指向LockRecord的指针,而不是使用重量级锁阻塞其他线程。
synchronized与Lock的区别
两者区别:
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
小例子1:
package com.cn.test.thread.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
/*
* 使用完毕释放后其他线程才能获取锁
*/
public void lockTest(Thread thread) {
lock.lock();//获取锁
try {
System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称
Thread.sleep(2000);//为看出执行效果,是线程此处休眠2秒
} catch (Exception e) {
System.out.println("线程"+thread.getName() + "发生了异常释放锁");
}finally {
System.out.println("线程"+thread.getName() + "执行完毕释放锁");
lock.unlock(); //释放锁
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
//声明一个线程 “线程一”
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.lockTest(Thread.currentThread());
}
}, "thread1");
//声明一个线程 “线程二”
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.lockTest(Thread.currentThread());
}
}, "thread2");
// 启动2个线程
thread2.start();
thread1.start();
}
}
执行结果:
小例子2:
package com.cn.test.thread.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
/*
* 尝试获取锁 tryLock() 它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false
*/
public void tryLockTest(Thread thread) {
if(lock.tryLock()) { //尝试获取锁
try {
System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称
Thread.sleep(2000);//为看出执行效果,是线程此处休眠2秒
} catch (Exception e) {
System.out.println("线程"+thread.getName() + "发生了异常释放锁");
}finally {
System.out.println("线程"+thread.getName() + "执行完毕释放锁");
lock.unlock(); //释放锁
}
}else{
System.out.println("我是线程"+Thread.currentThread().getName()+"当前锁被别人占用,我无法获取");
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.tryLockTest(Thread.currentThread());
}
}, "thread1");
//声明一个线程 “线程二”
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.tryLockTest(Thread.currentThread());
}
}, "thread2");
// 启动2个线程
thread2.start();
thread1.start();
}
}
执行结果:
小例子3:
package com.cn.test.thread.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
public void tryLockParamTest(Thread thread) throws InterruptedException {
if(lock.tryLock(3000, TimeUnit.MILLISECONDS)) { //尝试获取锁 获取不到锁,就等3秒,如果3秒后还是获取不到就返回false
try {
System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称
Thread.sleep(4000);//为看出执行效果,是线程此处休眠2秒
} catch (Exception e) {
System.out.println("线程"+thread.getName() + "发生了异常释放锁");
}finally {
System.out.println("线程"+thread.getName() + "执行完毕释放锁");
lock.unlock(); //释放锁
}
}else{
System.out.println("我是线程"+Thread.currentThread().getName()+"当前锁被别人占用,等待3s后仍无法获取,放弃");
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
lockTest.tryLockParamTest(Thread.currentThread());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "thread1");
//声明一个线程 “线程二”
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
lockTest.tryLockParamTest(Thread.currentThread());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "thread2");
// 启动2个线程
thread2.start();
thread1.start();
}
}
执行结果:
因为此时线程1休眠了4秒,线程2等待了3秒还没有获取到就放弃获取锁了,执行结束
将方法中的 Thread.sleep(4000)改为Thread.sleep(2000)执行结果如下:
因为此时线程1休眠了2秒,线程2等待了3秒的期间线程1释放了锁,此时线程2获取到锁,线程2就可以执行了