Java基础多线程之线程安全-同步锁三种形式

首先,我们通过一个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共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(); // 释放锁
    }
}

猜你喜欢

转载自blog.csdn.net/sunshinegirl168660/article/details/81545261