多线程
重入
当某个线程请求一个由其他线程获取持有的锁时,该线程就会阻塞。然而,由于synchronized是可重入的,因此如果某个锁线程获取一个已经由它自己持有的锁时,这个请求就会成功。重入意味着获取锁的粒度是线程而不是调用。
重入的一种方法是,给锁关联一个计数器和一个持有者线程。当计数器为0时说明该线程未被线程持有。当线程请求一个未持有的锁时,JVM将记下锁的持有者并且将计数值设置为1,如果同一线程再次访问时将计数器加1,当同一线程退出同步代码块时,计数器减1,当技术器为0时,该锁将被释放。
volatile
当变量被声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
synchronized实现原理
synchronized 同步语句块使用的是monitorenter和monitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置。
synchronized 修饰方法使用的是ACC_SYNCHRONIZED 标识
ReentrantLock实现原理
ReentrantLock 通过AbstractQueuedSynchronizer(即AQS)实现.用FIFO队列实现公平锁和非公平锁,在公平锁中,如果当另一个线程持有锁或者有其他线程在等待队列中这个锁,所以新发出的请求的线程将被放入到度一列中。而非公平锁,只有当锁被其他线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以他们的差别是非公平锁会有更多的机会去抢占锁。
ReenTrantLock的对比
1、两者都是可重入锁
2、synchronized依赖于JVM而ReenTrantLock依赖于API
3、ReenTrantLock比synchronized增加了一些高级功能主要有:等待可中断、可实现公平锁、可实现选择性通知
- ReenTrantLock提供了一种能够中断等待锁的线程机制,通过lock.lockInterruptibly()来实现这个机制。
- ReenTrantLock 可以指定公平锁和非公平锁,而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReenTrantLock默认情况是公平的。
- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”。
AQS实现原理
AQS核心思想是,如果请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源占用,那么需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制是AQS是用CLH(FIFO双向队列)队列锁实现的,即将暂时获取不到的线程加入到队列中。
AQS定义两种资源共享方式
Exclusive(独占);只有一个线程能执行如ReentrantLock.又可分为公平锁和非公平锁:
- 按照线程在队列中的排队顺序,先到者先拿到锁
- 当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock。
AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:
1 | isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 |