锁的八个问题演示
package com.atguigu.sync;
import java.util.concurrent.TimeUnit;
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
/**
* @Description: 8锁
*
1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail
2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail
3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS
4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS
5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail
6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS
*/
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();
}
}
结论:
一个对象里面如果有多个 synchronized 方法,某一个时刻内,只要一个线程去调用其中一个 synchronized 方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized 方法
锁的是当前对象 this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized 方法,加个普通方法后发现和同步锁无关
换成两个对象后,不是同一把锁了,情况立刻变化。
synchronized 实现同步的基础:Java 中的每一个对象都可以作为锁。
具体表现为以下 3 种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的 Class 对象。
对于同步方法块,锁是 Synchonized 括号里配置的对象
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,
可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
公平锁和非公平锁
从一下代码可以看出
package com.lock;
//第一步 创建资源类,定义属性和操作方法
import java.util.concurrent.locks.ReentrantLock;
class LTicket {
//票数量
private int number = 30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
//卖票方法
public void sale() {
//上锁
lock.lock();
try {
//判断是否有票
if (number > 0) {
//卖出票后,票自动减1
System.out.println( Thread.currentThread().getName() + ":卖出:" + (number--) + " 剩下:" + number );
}
} finally {
//解锁
lock.unlock();
}
}
}
public class LSaleTicket {
//第二步 创建多个线程,都去调用资源类的操作方法
public static void main(String[] args) {
LTicket lTicket = new LTicket();
//创建三个线程
new Thread( () -> {
//调用卖票方法
for (int i = 0; i < 40; i++) {
lTicket.sale();
}
}, "AA" ).start();
new Thread( () -> {
//调用卖票方法
for (int i = 0; i < 40; i++) {
lTicket.sale();
}
}, "BB" ).start();
new Thread( () -> {
//调用卖票方法
for (int i = 0; i < 40; i++) {
lTicket.sale();
}
}, "CC" ).start();
}
}
上述代码中的ReentrantLock类,其实是非公平锁
进入ReentrantLock源码发现,默认是非公平锁
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
有 NonfairSync() 其实就是一个非公平锁
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
上述代码中,ReentrantLock类里面传了一个boolean参数,下面的意思是,如果为true ,则是FairSync,如果为false ,则是NofairSync,如果想在线程当中 ,设置公平锁,则需要在ReentrantLock()里面加入true。
非公平锁:直接占据一个位置进行操作了,
公平锁:如果有位置,就分开,没位置,就继续操作
非公平锁
坏处:会造成线程饿死,假设A,B,C卖同一张票 ,都让A卖了,B,C没事干了
好处:执行效率高
公平锁
坏处:效率相对低
好处:阳光普照一样,大家都能照顾到
可重入锁
首先锁是针对线程的,即只有线程才有锁的概念。
什么是可重入锁呢?
当一个线程获取了某个对象锁以后,还可以再次获得该对象锁。
先上图
简单地说,就是递归
小测试
public class Test {
public static void main(String[] args) {
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() + " 内层" );
}
}
}
}, "t1" ).start();
}
}
第二段测试
public class Test {
public synchronized void add() {
add();
}
public static void main(String[] args) {
new Test().add();
}
}
因为synchronized 关键字是隐式的,它主要是自动上锁和释放锁,测试第二段代码时,会出现栈异常(java.lang.StackOverflowError)
第三段测试
package com.sync;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//可重入锁
public class SyncLockDemo {
public static void main(String[] args) {
//Lock演示可重入锁
Lock 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();
}
},"t1").start();
}
}
第三段测试正常运行状态
第四段测试
package com.sync;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//可重入锁
public class SyncLockDemo {
public static void main(String[] args) {
//Lock演示可重入锁
Lock 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();
}
},"t1").start();
//创建新线程
new Thread(()->{
lock.lock();
System.out.println("aaaa");
lock.unlock();
},"aa").start();
}
}
第四段测试时,建立一个新的线程,并且将第二个try部分中的释放锁取消了,控制台只输出,外层和内层,不输出aaaa字符串,原因是因为,第一个线程中的锁没有完全释放,所以第二个线程不可能释放出来。
死锁
测试个例子,建立两个对象,A和B, 锁A尝试获取锁B,锁B也尝试获取锁A
package com.sync;
import java.util.concurrent.TimeUnit;
/**
* 演示死锁
*/
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();
}
}
验证是否死锁?
1,JPS (类似Linux ps -ef )
2,jstack : jvm里面自带的堆栈跟踪工具
现在开始测试
1,控制台效果
控制台一直没有停止。
2,开始终端查看
输入命令 jps -l 查看进程,代码执行的进程是 黄色画的部分
3,开始jstack 查看
结尾处发现,有一个死锁问题
分析如下:
看画图上的<> 中的后四位,B线程中,在等待cff0释放,目前已经锁住d000,A线程中,在等待d000释放,目前已经所致cff0。
掌握
1,掌握死锁基本概念
2,手写思索代码
3,熟练使用跟踪工具
END