synchronized 和Lock
synchronized 和Lock
1. synchronized
synchronized 是Java提供的一种原子内置锁
内置锁: Java内置的使用者看不到的锁称为内部锁,也叫监视器锁
内置锁是排它锁,就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁
线程的执行代码在进入synchronized代码块前会自动获取内部锁,其他线程访问该代码块时会被阻塞挂起
当阻塞一个线程时,需要从用户态切换到内核态执行阻塞操作,这是很耗时的操作。
synchronized的使用就会导致上下文切换
1.1 synchronized的内存语义
- 是把在 synchronized块 内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取,而是直接从主内存中获取。
- 退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存
1.2 synchronized 作用
- 确保线程互斥的访问同步代码
- 保证共享变量的修改能够及时可见
- 有效解决重排序问题。
1.3 synchronized 使用
(1)修饰普通方法
(2)修饰静态方法
(3)修饰代码块
1.4 synchronized证明
- synchronized 修饰普通方法,同一个对象上的锁(monitor)
public class SynchronizedTest {
public synchronized void method1(){
System.out.println(Thread.currentThread().getName()+"come into method-01");
try{
System.out.println(Thread.currentThread().getName()+" IS SLEEP ");
TimeUnit.SECONDS.sleep(3);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"method- 01 is END");
}
public synchronized void method2(){
System.out.println(Thread.currentThread().getName()+"come into method-02");
try{
System.out.println(Thread.currentThread().getName()+" IS SLEEP ");
TimeUnit.SECONDS.sleep(3);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"method-02 is END");
}
public static void main(String[] args){
SynchronizedTest test1 = new SynchronizedTest();
SynchronizedTest test2 = new SynchronizedTest();
new Thread(() ->{
test1.method1();
},"AAAAAAAAA").start();
new Thread(() ->{
test1.method2();
},"bbbbbbbbb").start();
}
}
b 线程等待 A 线程执行完才开始执行 method-02
-
当去掉method-02 的synchronized 关键字时
b线程又开始和线程A并发执行了
-
当b线程通过test2对象调用方法
互不影响
-
当synchronized修饰静态方法时,A , b 线程在不同的实例对象都调用method- 01
互斥的了,因为static方法是属于类的,每个类只对应一个class对象
-
monitor
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
- 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
- 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
- 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
- monitorexit 则是由其拥有者执行,执行减一操作
1.5 问题
public static Integer i = 0;
synchronized(i){
i++
}
代码实际使用了一个Integer.valueOf()方法新建了一个Integer对象,并将他赋值 i
但是-128~127不同
Integer.valueOf()实际是一个工厂方法,他会倾向于返回一个代表指定数值的Integer对象实例。
2. Lock
基于内部类Sync实现锁的相关功能,Sync是AQS的子类
AbstractQueuedSynchronizer 类是一个抽象类,
它是所有的锁队列管理器的父类,JDK 中的各种形式的锁其内部的队列管理器都继承了这个类,它是 Java 并发世界的核心基石。
比如 ReentrantLock、ReadWriteLock、CountDownLatch、Semaphone、ThreadPoolExecutor 内部的队列管理器都是它的子类。
其中的state 的 数值表示该锁目前的状态,如=0:则为无线程占有
而waitStatus则是表示node结点封装线程的状态
对state的修改都是基于CAS
- 将上面的代码改成显式锁
public class ReentrantTest {
ReentrantLock reentrantLock = new ReentrantLock();
ReentrantLock reentrantLock1 = new ReentrantLock();
public void method1(){
reentrantLock.lock();
System.out.println(Thread.currentThread().getName()+"come into method-01");
try{
System.out.println(Thread.currentThread().getName()+" IS SLEEP ");
TimeUnit.SECONDS.sleep(3);
}catch(InterruptedException e){
e.printStackTrace();
}
finally {
System.out.println(Thread.currentThread().getName()+"method- 01 is END");
reentrantLock.unlock();
}
}
public void method2(){
reentrantLock1.lock();
//reentrantLock.lock();
System.out.println(Thread.currentThread().getName()+"come into method-02");
try{
System.out.println(Thread.currentThread().getName()+" IS SLEEP ");
TimeUnit.SECONDS.sleep(3);
}catch(InterruptedException e){
e.printStackTrace();
}finally {
reentrantLock1.unlock();
//reentrantLock.unlock();
System.out.println(Thread.currentThread().getName()+"method-02 is END");
}
}
public synchronized static void main(String[] args){
ReentrantTest test1 = new ReentrantTest();
ReentrantTest test2 = new ReentrantTest();
new Thread(() ->{
test1.method1();
},"AAAAAAAAA").start();
new Thread(() ->{
test1.method2();
},"bbbbbbbbb").start();
new Thread(() ->{
test1.method2();
},"CCCCCC").start();
}
}
可以看到b线程和A线程并发进行,而b线程和C线程“串行进行”。
- 将method- 02 再加上一把锁(也就是有两把锁,即把注释去掉)
三个线程又“串行进行了”,说明new 一个对象就是一把锁。
3. synchronized 和Lock有什么区别
重入锁可以完全替代关键字synchronized
在JDK5.0早期的版本中可重入锁的新能远优于synchronized,在JDK6.0开始,JDK在关键字synchronized上了做了大量优化,使得两者性能差距不大
synchronized | lock | |
---|---|---|
synchronized 属于JVM层面,是java的关键字 | Lock是属于API层面,它是jdk5后的一个接口 java.util.concurrent.locks.Lock |
|
内部锁 | 显式锁 | |
使用方法 | synchronized 不需要用户手动去释放锁,当synchronized代码执行完成后,系统会自动让线程释放对锁的占用 | ReentrantLock则需要用户去手动释放锁,若没有手动释放锁,就有可能导致出现死锁现象(try/finally) |
等待是否可中断 | synchronized不可以中断,除非抛出异常或者正常运行 | ReentrantLock 可中断 设置超时方法 tryLock(long timeout,TimeUnit unit) lockInterruptibly()放代码块种,调用interrupt方法可中断 |
加锁是否公平 | synchronized是非公平锁 | ReentrantLock 可定义,默认非公平 |
锁绑定多个条件Condition | 没有 | 可用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么全部唤醒 |
优点 | 简单易用,不会导致锁泄露 | 锁泄露 |
缺点 | 使用无灵活性 | 功能强大 |
/**
* 题目:
* 多线程之间顺序调用,实现A-B-C,三个线程启动
* AA打印5次,BB打印10次,CC打印15次
*/
class ShareResource{
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void print5(){
lock.lock();
try{
//判断
while(number != 1){
condition1.await();
}
//干活
for(int i = 0; i < 5; i++){
System.out.println(Thread.currentThread().getName()+"\t"+number);
}
//唤醒
number++;
condition2.signal();
}catch (Exception e){
e.getStackTrace();
}finally {
//解锁
lock.unlock();
}
}
public void print10(){
lock.lock();
try{
//判断
while(number != 2){
condition2.await();
}
//干活
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName()+"\t"+number);
}
//h
number++;
condition3.signal();
}catch (Exception e){
e.getStackTrace();
}finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
try{
while(number != 3){
condition3.await();
}
for(int i = 0; i < 15; i++){
System.out.println(Thread.currentThread().getName()+"\t"+number);
}
number = 1;
condition1.signal();
}catch (Exception e){
e.getStackTrace();
}finally {
lock.unlock();
}
}
}
public class SyncAndReentrantLockDemo {
public static void main(String[] args){
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 0; i < 10; i++){
shareResource.print5();
}
},"aa").start();
new Thread(()->{
for (int i = 0; i < 10; i++){
shareResource.print10();
}
},"bb").start();
new Thread(()->{
for (int i = 0; i < 10; i++){
shareResource.print15();
}
},"cc").start();
}
}