30.Java 多线程锁(synchronized 锁的八种情况、synchronized 锁、公平锁与非公平锁、可重入锁、死锁)

一、synchronized 锁的八种情况

1、情况一
(1)需求
  • 一部手机,测试先是 AA 线程打印短信还是 BB 线程打印邮件
(2)测试
  • Phone 资源类
package com.my.sync;

public class Phone {

    public synchronized void sendMes() {
        System.out.println("method 1:sendMes");
    }

    public synchronized void sendEmail() {
        System.out.println("method 2:sendEmail");
    }
}
  • 测试
Phone phone = new Phone();

new Thread(() -> {
    phone.sendMes();
}, "AA").start();

new Thread(() -> {
    phone.sendEmail();
}, "BB").start();
  • 结果
method 1:sendMes
method 2:sendEmail
2、情况二
(1)需求
  • 一部手机,在短信方法内停 4 秒,测试先是 AA 线程打印短信还是 BB 线程打印邮件
(2)测试
  • Phone 资源类
package com.my.sync;

import java.util.concurrent.TimeUnit;

public class Phone {

    public synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4);
            System.out.println("method 1:sendMes");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void sendEmail() {
        System.out.println("method 2:sendEmail");
    }
}
  • 测试
Phone phone = new Phone();

new Thread(() -> {
    phone.sendMes();
}, "AA").start();

new Thread(() -> {
    phone.sendEmail();
}, "BB").start();
  • 结果
method 1:sendMes
method 2:sendEmail
3、情况三
(1)需求
  • 一部手机,在短信方法内停 4 秒,测试先是 AA 线程打印短信还是 BB 线程打印 Hello
(2)测试
  • Phone 资源类
package com.my.sync;

import java.util.concurrent.TimeUnit;

public class Phone {

    public synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4);
            System.out.println("method 1:sendMes");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void sendHello() {
        System.out.println("method 3:sendHello");
    }
}
  • 测试
Phone phone = new Phone();

new Thread(() -> {
    phone.sendMes();
}, "AA").start();

new Thread(() -> {
    phone.sendHello();
}, "BB").start();
  • 结果
method 3:sendHello
method 1:sendMes
4、请求四
(1)需求
  • 两部手机,AA 线程和 BB 线程各使用一部,在短信方法内停 4 秒,测试先是 AA 线程打印短信还是 BB 线程打印邮件
(2)测试
  • Phone 资源类
package com.my.sync;

import java.util.concurrent.TimeUnit;

public class Phone {

    public synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4);
            System.out.println("method 1:sendMes");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void sendEmail() {
        System.out.println("method 2:sendEmail");
    }
}
  • 测试
Phone p1 = new Phone();
Phone p2 = new Phone();

new Thread(() -> {
    p1.sendMes();
}, "AA").start();

new Thread(() -> {
    p2.sendEmail();
}, "BB").start();
  • 结果
method 2:sendEmail
method 1:sendMes
5、情况五
(1)需求
  • 一部手机,两个静态方法,在短信方法内停 4 秒,测试先是 AA 线程打印短信还是 BB 线程打印邮件
(2)测试
  • Phone 资源类
package com.my.sync;

import java.util.concurrent.TimeUnit;

public class Phone {

    public static synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4);
            System.out.println("method 1:sendMes");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void sendEmail() {
        System.out.println("method 2:sendEmail");
    }
}
  • 测试
Phone phone = new Phone();

new Thread(() -> {
    phone.sendMes();
}, "AA").start();

new Thread(() -> {
    phone.sendEmail();
}, "BB").start();
  • 结果
method 1:sendMes
method 2:sendEmail
6、情况六
(1)需求
  • 两部手机,AA 线程和 BB 线程各使用一部,两个静态方法,在短信方法内停 4 秒,测试先是 AA 线程打印短信还是 BB 线程打印邮件
(2)测试
  • Phone 资源类
package com.my.sync;

import java.util.concurrent.TimeUnit;

public class Phone {

    public static synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4);
            System.out.println("method 1:sendMes");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void sendEmail() {
        System.out.println("method 2:sendEmail");
    }
}
  • 测试
Phone p1 = new Phone();
Phone p2 = new Phone();

new Thread(() -> {
    p1.sendMes();
}, "AA").start();

new Thread(() -> {
    p2.sendEmail();
}, "BB").start();
  • 结果
method 1:sendMes
method 2:sendEmail
7、情况七
(1)需求
  • 一部手机,在短信方法内停 4 秒并设置为静态方法,测试先是 AA 线程打印短信还是 BB 线程打印邮件
(2)测试
  • Phone 资源类
package com.my.sync;

import java.util.concurrent.TimeUnit;

public class Phone {

    public synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4);
            System.out.println("method 1:sendMes");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void sendEmail() {
        System.out.println("method 2:sendEmail");
    }
}
  • 测试
Phone phone = new Phone();

new Thread(() -> {
    phone.sendMes();
}, "AA").start();

new Thread(() -> {
    phone.sendEmail();
}, "BB").start();
  • 结果
method 2:sendEmail
method 1:sendMes
8、情况八
(1)需求
  • 两部手机,AA 线程和 BB 线程各使用一部,在短信方法内停 4 秒并设置为静态方法,测试先是 AA 线程打印短信还是 BB 线程打印邮件
(2)测试
  • Phone 资源类
package com.my.sync;

import java.util.concurrent.TimeUnit;

public class Phone {

    public static synchronized void sendMes() {
        try {
            TimeUnit.SECONDS.sleep(4);
            System.out.println("method 1:sendMes");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void sendEmail() {
        System.out.println("method 2:sendEmail");
    }
}
  • 测试
Phone p1 = new Phone();
Phone p2 = new Phone();

new Thread(() -> {
    p1.sendMes();
}, "AA").start();

new Thread(() -> {
    p2.sendEmail();
}, "BB").start();
  • 结果
method 2:sendEmail
method 1:sendMes

二、synchronized 锁

1、synchronized 锁的 8 种情况总结
  • 情况 1 - 情况 2:同一对象访问不同同步锁

    • 按顺序执行
  • 情况 3:同一对象访问同步锁与不同步锁

    • 不同步锁先执行
  • 情况 4:不同对象访问不同同步锁

    • 按照顺序执行
  • 情况 5:同一对象访问不同静态同步锁

    • 按顺序执行
  • 情况 6:不同对象访问不同静态同步锁

    • 按顺序执行
  • 情况 7:同一对象访问静态同步锁和同步锁

    • 同步锁先执行
  • 情况 8:不同对象访问静态同步锁与同步锁

    • 同步锁先执行
2、synchronized 锁总结
  • synchronized 实现同步的基础是 Java 中的每一个对象都可以作为锁,具体表现为以下 3 种形式

    • 对于普通同步方法,synchronized 锁的是当前实例对象

    • 对于静态同步方法,synchronized 锁的是当前类的 Class 对象

    • 对于同步方法块,synchronized 锁的是 Synchonized 括号里配置的对象

(1)非静态同步方法
  • 一个对象里面如果有多个非静态同步方法,某一个时刻,只要有一个线程去调用了其中的一个非静态同步方法,其它的线程都只能等待,即某一个时刻内,只能有一个线程去访问这些非静态同步方法

  • 非静态同步方法锁的是当前对象(this),被锁后,其它线程都不能进入到当前对象的其它的非静态同步方法

  • 当一个线程试图访问同步代码块时,首先必须得到锁,退出或抛出异常时必须释放锁,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁

(2)静态同步方法
  • 不同实例对象的非静态同步方法使用不同的锁,但是所有的静态同步方法使用同一把锁,即类对象本身,这两把锁锁的是两个不同的对象,所以静态同步方法与非静态同步方法之间不会有竞争

  • 一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间都是如此,因为它们是同一个类的实例对象


三、公平锁与非公平锁

1、基本介绍
  • 公平锁效率相对低

  • 非公平锁效率高,但是线程容易饿死

2、创建公平锁与非公平锁
  • 创建公平锁
ReentrantLock lock = new ReentrantLock(false);
  • 创建非公平锁
ReentrantLock lock = new ReentrantLock(false);

// 或

ReentrantLock lock = new ReentrantLock();
2、原理分析
  • ReentrantLock 类构造器源码
public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
  • 公平锁源码
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
        }
        return false;
    }
}
  • 非公平锁源码
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

四、可重入锁

  • synchronized 和 Lock 都是可重入锁

  • sychronized 是隐式锁,不用手工上锁与解锁,而 Lock 为显示锁,需要手工上锁与解锁

  • 可重入锁也叫递归锁,有了可重入锁之后,破解第一把之后就可以一直进入到内层结构

1、synchronized 可重入锁机制
  • 演示 synchronized 可重入锁机制
package com.my.reentrylock;

public class SynchronizedRLockTest {
    public static void main(String[] args) {
        Object obj = new Object();

        new Thread(() -> {
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName() + " 外层");
                synchronized (obj) {
                    System.out.println(Thread.currentThread().getName() + " 中层");
                    synchronized (obj) {
                        System.out.println(Thread.currentThread().getName() + " 内层");
                    }
                }
            }
        }, "AA").start();
    }
}
  • synchronized 递归锁,会抛出栈溢出异常(java.lang.StackOverflowError)
public synchronized void add() {
    add();
}
2、Lock 可重入锁机制
  • 演示 Lock 可重入锁机制
package com.my.reentrylock;

import java.util.concurrent.locks.ReentrantLock;

public class LockRLockTest {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

        new Thread(() -> {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " 外层");

                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + " 内层");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "BB").start();
    }
}
  • 在同一把锁中,内部嵌套锁没解锁还是可以执行,但是如果跳出该线程,执行另外一个线程就会造成因为获取不到未解锁的锁而造成死锁,抛出异常(java.lang.IllegalMonitorStateException)
ReentrantLock lock = new ReentrantLock();

new Thread(() -> {
    try {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + " 外层");
        lock.lock();
        System.out.println(Thread.currentThread().getName() + " 内层");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}, "BB").start();

new Thread(() -> {
    lock.unlock();
    System.out.println(Thread.currentThread().getName());
    lock.unlock();
}, "BB").start();
  • Lock 递归锁,会抛出栈溢出异常(java.lang.StackOverflowError)
private ReentrantLock lock = new ReentrantLock();

public void add() {
    lock.lock();
    add();
    lock.unlock();
}

五、死锁

1、基本介绍
  • 两个或以上的线程因为争夺资源而造成互相等待资源的现象称为死锁
2、死锁的产生原因
  • 系统资源不足

  • 系统资源分配不当

  • 进程运行顺序不当

3、死锁演示
package com.my.deadlock;

import java.util.concurrent.TimeUnit;

public class DeadlockTest {
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();

        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");
                }
            }
        }, "AA").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");
                }
            }
        }, "BB").start();
    }
}
4、死锁验证
(1)基本介绍
  • jps:类似于 Linux 中的 ps -ef 命令

  • jstack:JVM 自带的堆栈跟踪工具

(2)具体使用
  • 命令行输入如下指令
jps -l
jstack 【进程号】

你可能感兴趣的:(Java,-,基础入门,开发语言,ide,java-ee,java,intellij-idea,intellij,idea,jdk)