Object Monitor
5.synchronized
synchronized的特性包含了可见性、有序性、原子性,最重要的是synchronized能够保证原子性。
(要看可见性有序性到上面JMM那里去看,这里只关注锁如何解决原子性问题)
互斥=原子性
- 互斥:互斥指的是“同一时刻只有一个线程执行”
- 原子性:原子性本意是“不可分割性”,意思是指一组对共享变量的操作对于执行线程以外的线程要么没发生要么已结束,不会看到中间状态。所以保证对共享资源的一组操作的原子性就等于保证线程对共享资源的访问互斥。
- 保证原子性:
- 正确的使用排他锁可以保障对共享变量的访问具有互斥性
- 利用处理器提供的CAS指令,CAS实现原子性的方式与锁实现原子性的方式是一样的,只不过CAS是直接在硬件层次实现的。
锁相关概念
synchronized修饰静态方法、非静态方法和代码块的区别:
- 修饰静态方法时,锁定的是类的Class对象
- 修饰非静态方法时锁定的是this实例对象
- 修饰代码块时要显式的传入锁定的对象
synchronized的底层实现:在JVM层面,lock操作被保证了原子性,一个共享变量同一时刻只能被一个线程执行lock操作也只有加锁的线程才能对共享变量执行unlock操作
锁和受保护资源的关系:受保护资源和锁之间的数量关系是 N:1 的关系
多个资源可以交给一把锁保护,但一个资源不可以交给多把锁保护,否则就如同两个观众都买了票却要抢一个座位一样荒唐,对资源的访问不互斥,就没法保证原子性。
细粒度锁:用不同的锁对受保护资源进行精细化管理,能够提升并发性,这种锁叫做细粒度锁。
死锁
死锁是操作系统的通用概念,问死锁的时候,不要问是不是Java的锁,直接站在宏观角度说出四个条件即可。
- 死锁的产生条件:
- 互斥,共享资源只能被一个线程占用;
- 占有且等待,线程获得一个共享资源后等待另一个共享资源
- 不可抢占,线程不能强行获得已被占用的共享资源
- 循环等待,线程1拥有锁1需要锁2,线程B拥有锁2需要锁1
- 避免死锁:避免其中一个死锁的产生条件成立
- 互斥是不可避免的,我们用锁就是为了互斥
- 避免 占有且等待,我们可以一次性申请所有资源
- 避免 不可抢占,我们可以给线程设置等待时间,拿不到想要的资源就释放手中的资源
- 避免 循环等待,可以在申请资源时按照资源序号从小到大申请
手写死锁
package com.Eckel.someCase; public class DeadLock { private static Object lock1=new Object(); private static Object lock2=new Object(); public static void main(String[] args) { Thread t1=new Thread(()->{ synchronized (lock1){ System.out.println("t1 get lock1,wait lock2"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2){ System.out.println("t1 get lock2"); } } }); Thread t2=new Thread(()->{ synchronized (lock2){ System.out.println("t2 get lock2,wait lock1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1){ System.out.println("t2 get lock1"); } } }); t1.start(); t2.start(); } }
Monitor管程与“wait()-notify()”机制
管程
概念:是一种进程同步互斥工具,Java的锁机制就是通过管程技术实现的,synchronized通过Monitor管程实现,ReentrantLock通过AQS管程实现。而synchronized关键字、wait()、notify()、notifyAll()都是Monitor管程的一部分。
管程模型:将共享资源(锁)及对共享资源的操作统一封装起来。想要访问共享变量必须进入管程内部,线程要进入管程需要在管程入口的同步等待队列排队,同一时刻只有一个线程可以进入管程,进入后owner被设为当前线程,退出时变回null。
wait()-notify()机制:在Monitor管程中,如果线程要求的条件不满足无法继续执行时可以使用wait()-notify()的方式来进行线程协作
使用位置: wait()、notify()、notifyAll() 都是 Monitor管程内部 才可以调用,在Monitor管程外调用会出现异常(当然也不能在AQS管程中调用)
实现原理:
- wait():线程在同步等待队列排队,之后进入管程,如果线程要执行某一操作但条件不满足,可以调用wait()方法将线程挂起,将获得的锁释放,线程被放入条件等待队列,被唤醒后需要重新加入同步等待队列排队进入管程
- notify():当某个owner线程执行了某一操作导致某一条件变化的时候,可以用notify()/notifyAll()来唤醒条件等待队列中的(所有)线程
- 使用示例:不用记
class Allocator { private List<Object> als; // 一次性申请所有资源 synchronized void apply(Object from, Object to){ // 经典写法 while(als.contains(from) || als.contains(to)){ try{ wait(); }catch(Exception e){ } } als.add(from); als.add(to); } // 归还资源 synchronized void free( Object from, Object to){ als.remove(from); als.remove(to); notifyAll(); } }#Java开发##内推##春招##实习##笔试题目##面经##秋招##Java#