前言
我们首先使用学习到的多线程知识,模拟电影票售票过程,引出线程安全问题。
/*
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口
请设计一个程序模拟该电影院售票过程。
使用Thread类实现
分析: 1.三个窗口相当于三个线程
2.100张票属于共享资源
*/
- 代码1:采用多线程实现方式1实现
public class 案例演示1 {
public static void main(String[] args) {
CellThread th1 = new CellThread("窗口1");
CellThread th2 = new CellThread("窗口2");
CellThread th3 = new CellThread("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class CellThread extends Thread{
//设置票为静态变量,即共享资源
static int piao = 100;
//提供无参构造
public CellThread() {
}
//创建有参构造,不再调用setName()方法给线程起名字
public CellThread(String name) {
super(name);
}
@Override
public void run() {
while (true){
if(piao>0){
System.out.println(Thread.currentThread().getName()+"正在售卖"+(piao--)+"张票");
}
}
}
}
- 代码2:采用多线程实现方式2实现
public class 案例演示2 {
public static void main(String[] args) {
Myrunable myrunable = new Myrunable();
//我们只创建了一个任务,所以是共同售卖100张票。
Thread th1 = new Thread(myrunable, "窗口1");
Thread th2 = new Thread(myrunable, "窗口2");
Thread th3 = new Thread(myrunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Myrunable implements Runnable{
static int piao=1000;
@Override
public void run() {
while (true) {
if (piao > 0) {
//模拟了一下网络延迟,发现出现不合理的数据,即出现了线程安全问题。
//1.出现了0票和负数票,是因为线程并发执行导致的
//2,出现了重复票,是因为原子性所导致的。(自行科普)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
}
}
}
}
上述代码出现了线程安全问题
- 线程安全问题产生的条件:
- 是多线程环境
- 多个线程存在共享资源
- 有多条语句在操作共享资源,对共享资源的操作不是原子性操作
一、同步代码块解决线程安全问题
- 使用同步代码块的格式:
/*
我们使用同步代码块,将可能出现线程安全问题的代码包裹起来。
synchronized (锁对象){
需要同步的代码
}
锁对象:可以是java中的任意一个对象,常new Object()。注意不可以在括号内new对象,这样每个线程持有的不是同一把锁,没有效果。需要将该对象定义为静态成员变量,被线程共享。
需要同步的代码:可能出现线程安全的问题。(千万不可以出现死循环,这样线程会出现阻塞)
*/
public class 使用同步代码块解决线程安全问题 {
public static void main(String[] args) {
Myrunable1 myrunable = new Myrunable1();
Thread th1 = new Thread(myrunable, "窗口1");
Thread th2 = new Thread(myrunable, "窗口2");
Thread th3 = new Thread(myrunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Myrunable1 implements Runnable {
static int piao = 100;
//注意:多个线程要使用同一把锁
static Object obj=new Object();
@Override
public void run() {
while (true) {
//同步代码块,包裹可能出现线程安全问题的代码块
//锁对象:可以是java中的任意一个对象,常new Object()
synchronized (obj){
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
}
}
}
}
}
/*
为什么加了同步代码块就能解决线程安全问题?
某个线程一旦抢占cpu时间片,进入同步代码块,就会持有该锁。
其他线程没有该锁,只能等待,无法并发执行。
当持有锁的线程出了同步代码块,就会释放锁,然后所有线程再次争抢时间片。
但是加锁,数据安全,效率会降低。
*/
二、同步方法解决线程安全问题
- 我们可以将出现线程安全的代码抽成方法,并在该方法上加上synchronized,将该方法变成同步方法,这样不需要设置锁对象。
public class 同步方法解决线程安全问题 {
public static void main(String[] args) {
Myrunable2 myrunable = new Myrunable2();
Thread th1 = new Thread(myrunable, "窗口1");
Thread th2 = new Thread(myrunable, "窗口2");
Thread th3 = new Thread(myrunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Myrunable2 implements Runnable {
static int piao = 100;
@Override
public void run() {
//1.将可能出现线程安全问题的代码抽成方法
maipiao();
}
//2.在方法上加上synchronized,将该方法变成个同步方法
public synchronized void maipiao(){
while (true){
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
}
}
}
}
/*
我们单独使用同步代码块或同步方法都可以解决线程安全问题,
但是我们混合使用同步代码块和同步方法时候,可能不能解决线程安全问题。
那么是因为同步代码块和同步方法使用的不是同一把锁。
同步方法使用的默认锁对象是this(当将同步方法定义成静态方法时,锁对象是当前类对象的字节码对象)
同步代码块使用的锁对象是任意java对象
解决方法:将同步代码块的锁对象与同步方法保持一致。
*/
- 注意锁对象
- 同步代码块的锁对象是任意java对象
- 同步方法的锁对象是this
- 静态同步方法的锁对象是当前字节码对象
不要混合使用同步代码块和同步方法,如果使用注意将同步代码块的锁对象与同步方法的锁对象保持一致。
三、Lock锁解决线程安全问题
- JDK5以后提供了一个新的锁对象Lock,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
- ReentrantLock是其接口的一个实现类
- void lock() 加锁 void unlock() 释放锁
- 格式:
/*
加锁
lock.lock();
try{
可能出现线程安全的代码
}catch (Exception e){
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
*/
public class jdk15之后lock锁 {
public static void main(String[] args) {
Myrunable3 myrunable = new Myrunable3();
Thread th1 = new Thread(myrunable, "窗口1");
Thread th2 = new Thread(myrunable, "窗口2");
Thread th3 = new Thread(myrunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Myrunable3 implements Runnable {
static int piao = 100;
//1.创建lock实现类的一个对象
static Lock lock =new ReentrantLock();
@Override
public void run() {
while (true) {
//2.加锁
lock.lock();
try{
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
}
}catch (Exception e){
e.printStackTrace();
}finally {
//3.释放锁
lock.unlock();
}
}
}
}