synchronized 和Lock

synchronized 和Lock

1. synchronized

synchronized 是Java提供的一种原子内置锁

内置锁: Java内置的使用者看不到的锁称为内部锁,也叫监视器锁

内置锁是排它锁,就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁

线程的执行代码在进入synchronized代码块前会自动获取内部锁,其他线程访问该代码块时会被阻塞挂起

当阻塞一个线程时,需要从用户态切换到内核态执行阻塞操作,这是很耗时的操作。

synchronized的使用就会导致上下文切换

1.1 synchronized的内存语义

  • 是把在 synchronized块 内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取,而是直接从主内存中获取。
  • 退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存

1.2 synchronized 作用

  1. 确保线程互斥的访问同步代码
  2. 保证共享变量的修改能够及时可见
  3. 有效解决重排序问题。

1.3 synchronized 使用

  (1)修饰普通方法

  (2)修饰静态方法

  (3)修饰代码块

1.4 synchronized证明

  1. 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

  1. 当去掉method-02 的synchronized 关键字时

b线程又开始和线程A并发执行了

  1. 当b线程通过test2对象调用方法

互不影响

  1. 当synchronized修饰静态方法时,A , b 线程在不同的实例对象都调用method- 01

    互斥的了,因为static方法是属于类的,每个类只对应一个class对象

  2. monitor

    每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

    1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
    2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
    3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
    4. 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

  1. 将上面的代码改成显式锁
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线程“串行进行”。

  1. 将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();
    }
}
posted @ 2022-02-20 16:11  ftfty  阅读(38)  评论(0编辑  收藏  举报