synchronized关键字
synchronized有两种类型的锁:
- 类锁:synchronized修饰静态方法、synchronized(T.class)给代码块上锁。
- 对象锁:除了类锁,所有其他的上锁方式都认为是对象锁。synchronized修饰普通方法、synchronized(this)给代码块上锁等
规则:
- 加了相同锁的东西,任一时刻只能有一个线程持有锁,其他线程等待。
- 加了不同锁的东西访问互相不干扰 。
判断:
- 不同类型的锁不是同一把锁。
- 加的是对象锁,那么必须是同一个对象实例才是同一把锁 。
- 加的是类锁,那必须是同一类才是同一把锁。
偏向锁、轻量级锁、重量级锁
偏向锁:无资源竞争情况。
-
访问Mark Word中偏向锁的标识是否设置成01,确认为可偏向状态。
- 可偏向状态时,CAS设置线程ID,如果CAS失败会在safepoint时撤销,然后升级为轻量级锁。
轻量级锁:存在线程竞争时会发生偏向锁的撤销操作。这时有两种状态,无锁状态和轻量级锁。
即代码运行完则是无锁,没运行完就轻量级锁。
- 将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间
- CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针
- 如果多次失败说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁
重量级锁:
在JDK 1.6之前,锁可以认为直接对应底层操作系统中的互斥量(mutex
)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。
户态与核心态之间的切换,会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。
线程阻塞与唤醒方法:
1. sleep() 方法 不释放锁
sleep(毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程阻塞状态,期间得不到cpu的时间片,等到时间过去了,线程重新进入可执行状态。不会释放资源。(暂停线程,不会释放锁)
2.suspend() 和 resume() 方法
挂起和唤醒线程,suspend()使线程进入阻塞状态,只有对应的resume()被调用的时候,线程才会进入可执行状态。(不建议用,容易发生死锁)
3. yield() 方法 不释放锁
会使的线程放弃当前分得的cpu时间片,但此时线程任然处于可执行状态,随时可以再次分得cpu时间片。yield()方法只能使同优先级的线程有执行的机会。调用 yield()的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。(暂停当前正在执行的线程,并执行其他线程,且让出的时间不可知)
4.wait() 和 notify() 方法 释放锁
两个方法搭配使用,wait()使线程进入阻塞状态,调用notify()时,线程进入可执行状态。wait()内可加或不加参数,加参数时是以毫秒为单位,当到了指定时间或调用notify()方法时,进入可执行状态。(属于Object类,而不属于Thread类,wait()会先释放锁住的对象,然后再执行等待的动作。由于wait()所等待的对象必须先锁住,因此,它只能用在同步化程序段或者同步化方法内,否则,会抛出异常IllegalMonitorStateException.)
5.join()方法 释放锁
也叫线程加入。是当前线程A调用另一个线程B的join()方法,当前线程转A入阻塞状态,直到线程B运行结束,线程A才由阻塞状态转为可执行状态。
6.await()和signal() 释放锁
ConditionObject方法,实现线程挂起和唤醒。
sleep和wait的区别有:
- 这两个方法来自不同的类分别是Thread和Object
- 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
- wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。
同步器(Synchronizer)
Semaphore:
可以控制同时访问资源的线程个数
public Semaphore(int permits) //permits线程数
void acquire() //从信号量获取一个许可,如果无可用许可前将一直阻塞等待,
void acquire(int permits) //获取指定数目的许可,如果无可用许可前也将会一直阻塞等待
boolean tryAcquire() //从信号量尝试获取一个许可,如果无可用许可,直接返回false,不会阻塞
boolean tryAcquire(int permits)
boolean tryAcquire(int permits, long timeout, TimeUnit unit)
void release() //释放一个许可,在finally中使用,注意:多次调用该方法,会使信号量的许可数增加,达到动态扩展的效果,如:初始permits为1, 调用了两次release,最大许可会改变为2
int availablePermits() //获取当前信号量可用的许可
CountDownLatch:
使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
public CountDownLatch(int count) //参数count为计数值
public void await() throws InterruptedException //调用该方法的线程会进入阻塞状态,直到count值为0才继续执行
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
public void countDown(); //将CountDownLatch对象count值(初始化时作为参数传入构造方法)减1
Exchanger:
一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据。
Exchanger<T> exchanger = new Exchanger<T>();
//等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象,否则阻塞。
data = exchanger.exchange(T);
//等待另一个线程到达此交换点(除非当前线程被中断或超出了指定的等待时间),然后将给定的对象传送给该线程,并接收该线程的对象。
exchange(T t, long timeout, TimeUnit unit):
CyclicBarrier:
它允许线程集等待直至其中预定数目的线程到达某个状态(这个状态叫公共障栅(barrier)),然后可以选择执行一个处理障栅的动作。适用场景:当多个线程都完成某操作,这些线程才能继续执行时,或都完成了某操作后才能执行指定任务时。对CyclicBarrier对象调用await方法即可让相应线程进入barrier状态,等到预定数目的线程都进入了barrier状态后,这些线程就可以继续往下执行了
//构造函数,parties 是参与线程的个数,Runnable 参数,最后一个到达线程要做的任务。
CyclicBarrier(int parties)
CyclicBarrier(int parties, Runnable barrierAction)
//线程调用await()表示到达栅栏,Exception 表示栅栏已经被破坏,可能是其中一个线程await()时被中断或者超时
int await() throws InterruptedException, BrokenBarrierException
int await(long timeout, TimeUnit unit) throws InterruptedException,BrokenBarrierException, TimeoutException