Java基础多线程之线程安全-同步锁三种形式
程序员文章站
2022-05-04 17:09:42
...
首先,我们通过一个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。我们来模拟电影院的售票窗口,实现多个窗口同时卖 “终结者”这场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟模拟票:
public class MyTicketWrong implements Runnable {
private int tickets = 100; // 总共100张票
@Override
public void run() {
String name = Thread.currentThread().getName();
while (true) {
if (tickets > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "卖出了票:" + tickets--);
}
}
}
}
测试类;
public class Demo02TicketWrong {
public static void main(String[] args) {
Runnable task = new MyTicketWrong();
new Thread(task, "A").start();
new Thread(task, "B").start();
new Thread(task, "C").start();
}
}
此时会出现下面一种现象:
出现了0甚至负数,出现了错误,这是由于线程不安全导致的,图解如下:
解决方案如图下所示:
采用同步锁来保证线程安全:
(一)、同步代码块
直接把最开始的代码修改一下
package day06.demo03;
/*
同步代码块的格式:
synchronized (锁对象) {
// ...
}
小括号当中必须是一个对象引用类型,不能是基本类型。
含义:
只要代码运行到sync所在的一行,当前线程就会尝试霸占小括号当中的锁。
如果霸占成功,那么立刻进入大括号执行内容;如果霸占失败,就会卡死在这一行,直到有人释放锁再重新抢。
直到离开了大括号的范围,就算释放了锁。
*/
public class MyTicketSyncBlock implements Runnable {
private int tickets = 100; // 总共100张票
private final Object LOCK = new Object(); // 全局唯一的锁对象
@Override
public void run() {
String name = Thread.currentThread().getName();
while (true) {
synchronized (LOCK) {
if (tickets > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "卖出了票:" + tickets--);
}
} // sync
}
}
}
(二)同步方法
package day06.demo03;
/*
同步方法的格式:
修饰符 synchronized 返回值类型 方法名称(参数列表) {
方法体
}
对于同步方法来说,其实也有锁对象:
1. 对于普通的成员方法来说:锁对象其实是this当前对象。
2. 对于静态的方法来说:
修饰符 static synchronized 返回值类型 方法名称(参数列表) {...}
静态同步方法的锁对象其实是:该类的反射对象,也就是“类名称.class”。(day14学习反射的时候了解。)
*/
public class MyTicketSyncMethod implements Runnable {
private int tickets = 100; // 总共100张票
@Override
public void run() {
while (true) {
sell();
}
}
private synchronized void sell() {
if (tickets > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "卖出了票:" + tickets--);
}
}
}
(三)Lock
package day06.demo03;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
java.util.concurrent.locks.Lock接口:
public void lock():尝试霸占锁,如果成功则正常完成;如果霸占不成功,则一直卡住,直到成功为止。
public void unlock():释放锁。
使用步骤:
1. 首先创建一个锁对象:Lock lock = new ReentrantLock();
2. 要想上锁:调用lock()方法。相当于“synchronized (锁) {”
3. 要是释放锁:调用unlock()方法。相当于“...}”
*/
public class MyTicketSyncLock implements Runnable {
private int tickets = 100; // 总共100张票
private final Lock LOCK = new ReentrantLock();
@Override
public void run() {
while (true) {
sell();
}
}
private void sell() {
LOCK.lock(); // 上锁,尝试霸占锁
if (tickets > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "卖出了票:" + tickets--);
}
LOCK.unlock(); // 释放锁
}
}
上一篇: Fork/join
下一篇: 深入解析动态加载css的实现方法