Java基础 第四节 第二课

举报
我是小白呀iamarookie 发表于 2021/09/10 23:25:40 2021/09/10
【摘要】 线程安全 概述案例模拟票测试类 线程同步同步代码块格式同步锁 同步方法格式代码 Lock 锁 概述 如果有过个线程在同时运行, 而这些线程可能会勇士运行这段代码. 程序每次运行...

概述

如果有过个线程在同时运行, 而这些线程可能会勇士运行这段代码. 程序每次运行结果和单线程运行的结果是一样的, 而其他的变量的值也和预期的是一样的, 就是线程安全的.

案例

我们通过一个案例, 演示线程的安全问题:

电影院要卖票, 我们模拟电影院的卖过程. 假设要播放的电影是 “郭德纲和他嫂子的爱情故事”. 本次电影的座位共有 100 个. (本场电影只能卖 100 张票)

我们来模拟电影院的售票窗口, 实现多个窗口同时卖 “郭德纲和他嫂子的爱情故事” 这场电影票. (多个窗口一起卖这 100 张票)

窗口采用线程对象来模拟, 票采用 Runnable 接口子类来模拟.

模拟票

public class Ticket implements Runnable {
    private int ticket = 100;

    /**
     * 执行卖票操作
     */
    @Override
    public void run() {
        // 每个窗口卖票的操作
        // 窗口永远开启
        while (true) {
            if (ticket > 0) {  // 有票可卖
                // 出票操作
                // 使用sleep模拟一下出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 获取当前线程对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖: " + ticket--);
            }
        }
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

测试类

public class Test51 {
    public static void main(String[] args) {
        // 创建线程任务对象
        Ticket ticket = new Ticket();

        // 创建三个窗口对象
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");

        // 同时卖票
        t1.start();
        t2.start();
        t3.start();
    }
}

输出结果:
窗口1正在卖: 99
窗口3正在卖: 98
窗口2正在卖: 100
窗口2正在卖: 97
窗口1正在卖: 96
窗口3正在卖: 95
窗口2正在卖: 94
窗口1正在卖: 93
窗口3正在卖: 92
窗口3正在卖: 91
窗口1正在卖: 90
窗口2正在卖: 89
窗口2正在卖: 88
窗口3正在卖: 86
窗口1正在卖: 87
窗口1正在卖: 85
窗口2正在卖: 84
窗口3正在卖: 83
窗口1正在卖: 82
窗口2正在卖: 81
窗口3正在卖: 80
窗口3正在卖: 79
窗口2正在卖: 78
窗口1正在卖: 77
窗口3正在卖: 76
窗口2正在卖: 75
窗口1正在卖: 74
窗口1正在卖: 73
窗口2正在卖: 72
窗口3正在卖: 71
窗口1正在卖: 70
窗口2正在卖: 69
窗口3正在卖: 68
窗口3正在卖: 67
窗口1正在卖: 65
窗口2正在卖: 66
窗口1正在卖: 64
窗口2正在卖: 62
窗口3正在卖: 63
窗口3正在卖: 61
窗口1正在卖: 60
窗口2正在卖: 59
窗口2正在卖: 58
窗口1正在卖: 57
窗口3正在卖: 56
窗口3正在卖: 55
窗口2正在卖: 53
窗口1正在卖: 54
窗口1正在卖: 52
窗口3正在卖: 51
窗口2正在卖: 50
窗口1正在卖: 49
窗口2正在卖: 48
窗口3正在卖: 47
窗口2正在卖: 46
窗口3正在卖: 45
窗口1正在卖: 44
窗口1正在卖: 43
窗口3正在卖: 42
窗口2正在卖: 41
窗口2正在卖: 40
窗口3正在卖: 39
窗口1正在卖: 38
窗口1正在卖: 37
窗口3正在卖: 36
窗口2正在卖: 35
窗口1正在卖: 34
窗口3正在卖: 33
窗口2正在卖: 32
窗口2正在卖: 31
窗口3正在卖: 30
窗口1正在卖: 29
窗口3正在卖: 28
窗口1正在卖: 27
窗口2正在卖: 26
窗口2正在卖: 25
窗口3正在卖: 24
窗口1正在卖: 23
窗口3正在卖: 22
窗口2正在卖: 21
窗口1正在卖: 22
窗口2正在卖: 20
窗口1正在卖: 19
窗口3正在卖: 18
窗口3正在卖: 17
窗口2正在卖: 16
窗口1正在卖: 15
窗口3正在卖: 14
窗口2正在卖: 13
窗口1正在卖: 12
窗口1正在卖: 11
窗口2正在卖: 10
窗口3正在卖: 9
窗口3正在卖: 8
窗口2正在卖: 7
窗口1正在卖: 6
窗口2正在卖: 5
窗口1正在卖: 5
窗口3正在卖: 4
窗口2正在卖: 3
窗口1正在卖: 2
窗口3正在卖: 1
窗口2正在卖: 0
窗口1正在卖: -1

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122

发现程序出现了两个问题:

  1. 相同的票数, 比如 5 这张票被卖了两回
  2. 不存在的票, 比如 0 票与 -1, 是不存在的

这种问题, 几个窗口 (线程)票数不同了, 这种问题成为线程不安全.

线程安全问题都是由全局变量及静态变量引起的. 若每个线程中对全局变量, 静态变量只有读操作, 而无写操作, 一般来说, 这个全局变量是线程安全的. 若有多个线程同时执行操作, 一般都需要考虑线程同步, 否则的话就可能影响线程安全.

线程同步

当我们使用多个线程访问同一资源的时候, 且多个线程中对资源有写的操作, 就容易出现线程安全问题.

要解决上述多线程并发访问一个资源的安全性问题. 也就是解决重复票与不存在票问题. Java 中提供了 (synchronized) 来解决.

根据案例描述:

窗口 1 线程进入操作的时候, 窗口 2 和窗口 3
线程只能在外等着. 窗口 1 操作结束, 窗口 1 和窗口 3有机会去执行. 也就是说在某个线程修改共享资源的时候, 其他线程不能去修改该资源, 等待修改完毕同步之后,才能去抢夺 CPU 资源, 完成对应的操作, 保证了数据的同步性, 解决了线程不安全的现象.

为了保证每个线程都能正常秩序原子操作 Java 引入了线程同步机制.

那么怎么去使用呢? 有三种方式完成同步操作:

  1. 同步代码块
  2. 同步方法
  3. 锁机制

同步代码块

同步代码块: synchronized 关键字可以用于方法中的某个区块中. 表示只对这个区块的资源实行互斥访问.

格式

synchronized(同步锁){
    需要同步操作的代码
}

  
 
  • 1
  • 2
  • 3

同步锁

对象的同步锁只是一个概念, 可以想象为在对象上标记了一个锁:

  1. 锁对象, 可也是任意类型
  2. 多个线程对象, 要使用同一把锁

注: 在任何时候, 最多允许一个线程拥有同步锁. 谁拿到所就进入代码块. 其他的线程只能在外面等着. (Blocked)

使用同步代码块解决代码:

public class Ticket implements Runnable {
    private int ticket = 100;

    Object lock = new Object();

    /**
     * 执行卖票操作
     */
    @Override
    public void run() {
        // 每个窗口卖票的操作
        // 窗口, 永远开启
        while (true) {
            synchronized (lock) {
                if (ticket > 0) {  // 有票可卖
                    // 出票操作
                    // 使用sleep模拟一下出票时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 获取当前线程对象的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在卖: " + ticket--);
                }
            }
        }
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

当使用了同步代码块后, 上述的线程的安全问题, 解决了.

同步方法

同步方法: 使用 synchronized 修饰的方法, 就叫做同步方法. 保证 A 线程执行该方法的时候, 其他线程只能在方法外等着.

格式

public synchronized void method(){
    可能会产生线程安全问题的代码
}

  
 
  • 1
  • 2
  • 3

同步锁是谁?

对于非 static 方法, 同步锁就是 this. 对于 static 方法, 我们使用当前方法所在类的字节码对象 (类名.class)

代码

public class Ticket implements Runnable {
   private int ticket = 100;

    /**
     * 执行卖票操作
     */
    @Override
    public void run() {
        // 每个窗口卖票的操作
        // 窗口永远开启
        while (true){
            
        }
    }

    /**
     * 锁对象是谁调用这个方法就是谁
     * 隐含锁对象就是this
     */
    public synchronized void sellTicket(){
        if(ticket > 0){  // 有票可以卖
            // 出票操作
            // 使用sleep模拟一下出票时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 获取当前线程对象的名字
            String name = Thread.currentThread().getName();
            System.out.println(name + "正在卖: " + ticket--);
        }
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

Lock 锁

java.util.concurrent.locks.Lock机制提供了比 synchronized 代码块和 synchronized 方法更广泛的锁定操作, 同步代码块 / 同步方法具有功能 Lock 都有, 除此之外更强大, 更体现面向对象.

Lock 锁也称为同步锁, 加锁与释放锁方法如下:

  • public void lock(): 加同步锁
  • public void unlock(): 释放同步锁

使用如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {
    private int ticket = 100;
    Lock lock = new ReentrantLock();

    /**
     * 执行卖票操作
     */
    @Override
    public void run() {
        // 每个窗口卖票的操作
        // 窗口永远开启
        while (true){
            lock.lock();
            if(ticket > 0){  // 有票可以卖
                // 出票操作
                // 使用sleep模拟一下出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 获取当前线程对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖: " + ticket--);
            }
        }
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

文章来源: iamarookie.blog.csdn.net,作者:我是小白呀,版权归原作者所有,如需转载,请联系作者。

原文链接:iamarookie.blog.csdn.net/article/details/111413489

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。