多线程(线程的生命周期&同步代码快&锁&等待唤醒机制)

多线程(线程的生命周期&同步代码快&锁&等待唤醒机制)

一:线程的生命周期

所谓生命周期就是从生到死的过程中经历了哪些阶段。

先用人来举例:人刚开始是小婴儿状态(婴儿),慢慢长大变成了单身状态(单身),然后找个女朋友结婚生子(已婚),GG了。但是人生总是充满了变数,有变数就会有意外(意外状态),(离婚)。只能调节好心情继续生活,重新变成了单身状态。

首先创建线程的时候是新建状态(新建)。新建完了之后要调用start方法运行线程,此时就变成了就绪状态(就绪)。注意是在调用完了start方法之后才变成就绪状态。在就绪状态下,线程开始抢夺CPU的执行权。注意,是正在抢夺,还没有抢到。没抢到就无法执行代码,所以就绪状态是有执行资格,但没有执行权。什么意思?有执行资格就是你可以有资格去抢资格的执行权,没有执行权就是你现在还没有抢到,不能去执行代码 ,所以说是没有执行权的。所以在就绪状态下,线程干的事情就是不停的抢CPU。抢到了之后就会变成运行状态(运行),运行状态下,线程就会运行代码,所以他是有执行资格也是有执行权的。在运行的过程当中,CPU的执行权是有可能被其他线程抢走的,一旦被抢走,此时又会回到就绪状态。如果当前线程把run方法里面所有的代码都执行完了,此时线程就会死亡,变成垃圾(死亡)。如果在运行的过程中,遇到了sleep方法,此时线程就会阻塞(阻塞)。阻塞就是等着,此时什么也干不来,他不能去抢CPU的执行权,也不能执行代码,所以他是没有执行资格,也没有执行权的。当睡眠的时间到了之后,他就会变成就绪状态,开始重新抢夺CPU的执行权。

提问:sleep方法会让线程睡眠,睡眠时间到了之后,立马就会执行下面的代码吗?

不会,因为sleep方法执行完了之后,线程会进入就绪状态,此时需要重新去抢夺CPU的执行权,只有再次抢到了才回去执行代码。。

二:线程的安全问题

(1)需求:某电影院目前正在上映国产大片,共有100张票,而他有三个售票窗口,情设计一个程序模拟该电影院卖票。

见下一点

三:同步代码块

卖票引发的安全问题:

(1)相同票出现了多次

(2)出现了超出范围的票

        while (true) {
   
            try {
   
                Thread.sleep(100);
            } catch (InterruptedException e) {
   
                throw new RuntimeException(e);
            }
            if (ticket < 100) {
   
                ticket++;
                System.out.println(getName() + "正在卖第" +ticket + "张票");
            } else {
   
                break;
            }
        }

分析一下代码就知道为什么了:

重复票:假设现在有三条线程,三条线程要操作的共享数据刚开始为0。线程开启之后,线程在while(true)的地方抢夺CPU的执行权,谁抢到了就会继续往下执行。假设线程1在一开始的时候抢到了执行权,线程1就会继续往下执行,此时是满足ticket < 100的条件的,所以线程1就会进入到if当中,进去之后立马睡100ms,睡觉的时候是不会去抢夺CPU的执行权的,CPU的执行权一定会被其他线程抢走。假设线程2抢到了,线程2就会继续往下执行,此时也是满足ticket < 100的条件,所以线程2也会进入到if里面,进入之后同样睡100ms,此时CPU的执行权就会被线程3抢走,线程3也会进去睡100ms。时间到了之后,这三条线程会陆陆续续醒来执行下面的代码。假设线程1醒来了,抢到了CPU的执行权,线程1就会继续往下执行,ticket++,由0变成了1,但是自增之后还没来得及打印,CPU的执行权又被线程2抢走了,线程2在这里也ticket++,ticket就会从1自增为2,所以我们要知道一句话:就是线程在执行代码的时候,CPU的执行权随时有可能会被其他线程抢走。假设现在又被线程3抢走了,线程3也自增,ticket就变成了3。那么在这个时候,不管是线程1还是线程2还是线程3,在打印票号的时候,打印的都是3!!这就是重复票的由来。根本原因就是线程在执行的时候,他是具有随机性的,CPU的执行权有可能随时会被其他线程抢走,这是第一个原因。

卖超出范围的票:假设现在ticket已经到99了,此时还是3条线程在抢夺CPU执行权。假设线程1抢到了,然后睡了100ms.睡的时候CPU又被线程2抢走了,线程2也进去睡100ms。线程3再进去睡100ms,睡完之后也会陆陆续续醒来。假设线程1醒来了,抢到了CPU的执行权,然ticket++,99变成了100,自增完了之后还没来得及打印,CPU又被线程2抢走了,线程2ticket++,同样还没有打印,CPU又被线程3抢走了,线程3页ticket++,这时候ticket已经等于102了。这个时候不管是线程1还是线程几,他们打印的都是102。原因也是因为线程执行的时候是具有随机性的。

问题发现了,怎么改呢?

假设能把操作共享数据的代码给锁起来,当有线程进去之后,其他的线程就算抢夺到了CPU的执行权,也得在外面等着,不让他进去。只有当线程出来了,其他的线程才能进去。说白了就是把操作共享数据的这段代码锁起来,让所有的线程在这段代码当中能轮流执行,就不会出现上面的两个问题了。

(3)同步代码块

同步代码块:把操作共享数据的代码锁起来

格式:

synchronized () {
   
    // 操作共享数据的代码
}

特点:

1、锁默认打开,有一个线程进去了锁自动关闭

2、里面的代码全部执行完毕,线程出来,锁自动打开

卖电影票的完整代码

public class ThreadDemo1 {
   
    public static void main(String[] args) {
   
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        // 开启线程
        t1.start();
        t2.start();
        t3.start();


    }
}
class MyThread extends Thread {
   
    // 表示这个类的所有对象,都共享ticket数据
    static int ticket = 0;
    static Object object = new Object();
    public void run() {
   
        while (true) {
   
            // 锁对象一定要保证是唯一的
            synchronized (object) {
   
                try {
   
                    Thread.sleep(10);
                } catch (InterruptedException e) {
   
                    throw new RuntimeException(e);
                }
                if (ticket < 100) {
   
                    ticket++;
                    System.out.println(getName() + "正在卖第" +ticket + "张票");
                } else {
   
                    break;
                }
            }
        }
    }
}
四:同步代码块的两个小细节

(1)synchronize同步代码快不能卸载循环的外面,比如:

            // 锁对象一定要保证是唯一的
            synchronized (object) {
        
                while (true) {
   
                try {
   
                    Thread.sleep(10);
                } catch (InterruptedException e) {
   
                    throw new RuntimeException(e);
                }
                if (ticket < 100) {
   
                    ticket++;
                    System.out.println(getName() + "正在卖第" +ticket + "张票");
                } else {
   
                    break;
                }
            }

因为这样的话,第一个抢到CPU执行权的人进去了,要把循环执行完毕了,外面的才能进去。

(2)锁对象,一定要是唯一的

如果不是唯一的,会有什么效果??

    public void run() 
  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值