欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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();
    }

}

此时会出现下面一种现象:
Java基础多线程之线程安全-同步锁三种形式
出现了0甚至负数,出现了错误,这是由于线程不安全导致的,图解如下:
Java基础多线程之线程安全-同步锁三种形式
解决方案如图下所示:
Java基础多线程之线程安全-同步锁三种形式

采用同步锁来保证线程安全:

(一)、同步代码块
直接把最开始的代码修改一下

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(); // 释放锁
    }
}