JUC并发编程快速入门篇(四)—— 多线程锁
synchronized锁的八个问题
案例
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public static synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
/**
* @Description: 8锁
*
一、 标准访问,先打印短信还是邮件(非静态方法,两个线程操作同一个对象)
------sendSMS
------sendEmail
二、 停4秒在短信方法内,先打印短信还是邮件(非静态方法,两个线程操作同一个对象)
------sendSMS
------sendEmail
第1、2种情况是两个线程操作同一个对象,而 synchronized 锁的是当前this对象,所以先输出------sendSMS,睡眠100毫秒后再输出------sendEmail
====================================================================================
三、 新增普通的hello方法,是先打短信还是hello(短信方法停4秒)
------getHello
------sendSMS
第3中情况是因为 getHello 是普通方法,没有加锁,所以先执行
====================================================================================
四、 现在有两部手机,先打印短信还是邮件(短信方法停4秒)
------sendEmail
------sendSMS
第4种情况是因为 new 了两部手机,这两部手机锁不是用的同一把锁,所以先输出------sendEmail
====================================================================================
五、 两个静态同步方法,1部手机,先打印短信还是邮件(短信方法停4秒)
------sendSMS
------sendEmail
六、 两个静态同步方法,2部手机,先打印短信还是邮件(短信方法停4秒)
------sendSMS
------sendEmail
第5、6中情况是静态同步方法 static synchronized 锁的是当前字节码文件 .class,两部手机用的是同一把锁
====================================================================================
七、 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件(短信方法停4秒)
------sendEmail
------sendSMS
第7中情况 static synchronized 锁的是当前字节码文件 .class,一部手机用的不是同一把锁,sendSMS 方法停留4秒,所以------sendEmail 先输出
====================================================================================
八、 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件(短信方法停4秒)
------sendEmail
------sendSMS
第8中情况 static synchronized 锁当前 class 字节码文件,两部手机用的不是同一把锁,sendSMS 方法停留4秒,所以------sendEmail 先输出
====================================================================================
*/
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
// phone.sendEmail();
// phone.getHello();
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
公平锁和非公平锁
公平锁:资源平分,效率比非公平锁低
private final ReentrantLock lock = new ReentrantLock(true);
非公平锁:会出现线程抢占资源情况,效率高
private final ReentrantLock lock = new ReentrantLock(false);//默认false
可重入锁
可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。简单来说可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
synchronized 和 ReentrantLock 都是可重入锁。
可重入锁的意义之一在于防止死锁。
实现原理实现是通过为每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM 将记录锁的占有者,并且将请求计数器置为1 。
如果同一个线程再次请求这个锁,计数器将递增;
每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。
关于父类和子类的锁的重入:子类覆写了父类的
方法,然后调用父类中的方法,此时如果没有可重入的锁,那么这段代码将产生死锁。
synchonized可重入锁
public class SyncLockDemo {
public static void main(String[] args) {
//synchronized
Object o = new Object();
new Thread(()->{
synchronized (o){
System.out.println(Thread.currentThread().getName()+"外层");
synchronized (o){
System.out.println(Thread.currentThread().getName()+"中层");
synchronized (o){
System.out.println(Thread.currentThread().getName()+"内层");
}
}
}
},"线程一").start();
}
}
ReentrantLock可重入锁
public class SyncLockDemo {
public static void main(String[] args) {
//lock可重入锁
ReentrantLock lock = new ReentrantLock();
//创建线程
new Thread(()->{
try {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+"外层");
try {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+"内层");
}finally {
//解锁
lock.unlock();
}
}finally {
//解锁
lock.unlock();
}
},"线程二").start();
}
}
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁产生的原因:
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
案例
线程a持有锁a,试图获取锁b,线程b持有锁b,试图获取锁a。两个锁资源不释放,互相等待造成死锁。
//死锁
public class DeadLock {
//创建两个死锁对象
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread().getName()+"持有锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获取锁b");
}
}
},"a").start();
new Thread(()->{
synchronized (b){
System.out.println(Thread.currentThread().getName()+"持有锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获取锁a");
}
}
},"b").start();
}
}