day16 多线程售票系统练习 注解

本文详细介绍了两种实现多线程售票系统的方案:继承Thread类和实现Runnable接口,并针对可能出现的数据安全问题进行了同步锁的实现。同时,讲解了自定义注解的定义与使用。最后,讨论了sleep()、wait()、notify()和notifyAll()的区别,强调了它们在多线程控制中的作用。
摘要由CSDN通过智能技术生成

1.继承实现多线程售票系统

package cn.sk.tickets;
/**
 * 需求设计多线程编程模型,四个窗口共计售票100张
 */

/**本类通过继承Thread类的方式实现多线程售票案例*/
public class TestThread {
    public static void main(String[] args) {
        //5.创建多个线程对象
        TicketThread k = new TicketThread();
        TicketThread k1 = new TicketThread();
        TicketThread k2 = new TicketThread();
        TicketThread k3 = new TicketThread();
        //6.
        k.start();
        k1.start();
        k2.start();
        k3.start();
    }

}

//1.自定义线程售票业务类
class TicketThread extends Thread {
    //3.定义一个变量来保存票数
    //int ticket = 100;//不可以会卖400张票
    //7.解决4个线程卖了400张票的BUG
    static int tickets = 100;//静态资源属于类资源,会被全局对象共享,只有一份

    //2.把业务写在run()里
    @Override
    public void run() {
        //4.通过循环结构来一直卖票

        while (true) {
            synchronized (TicketThread.class) {
                if (tickets > 0) {

                    try {
                        //8.如果数据能够经受住sleep的考验,才能说明数据没有了安全隐患--人为制造问题
                        //问题1:产生了重卖:同一张票卖给了多个人
                        // 问题2:产生了超卖:超出了规定卖票,甚至卖出了0和负的票数
                        Thread.sleep(10);//让程序休眠10ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "=" + tickets--);
                }
                //做判断,如果没有票了,就退出死循环
                if (tickets <= 0) break;//注意,死循环一定要设置出口
            }
        }
    }
}

 

2.Runnable完成售票系统

package cn.sk.tickets;
/**
 * 需求设计多线程编程模型,四个窗口共计售票100张
 */

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**本类通过继承Thread类的方式实现多线程售票案例*/

public class TestRunnable {
    public static void main(String[] args) {
        //5.创建目标业务对象
        TicketRunnable target = new TicketRunnable();
        /**ExecutorService: 用来储存线程的池子,把新建线程/启动线程/关闭线程任务都交给池来管理*/
        /**newFixedThreadPool(5);表示创建含有5个线程数的池子*/
        //8.创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //9.通过循环,让线程池中的线程开始执行目标任务
        for (int i = 0; i < 5; i++) {
            /**execute(target),让线程池中的线程来执行任务,每次调用都会启动一个线程*/
            pool.execute(target);
        }
       /* //6.使用Thread类中的含参构造,将目标对象与线程对象做绑定
        Thread thread = new Thread(target);
        Thread thread1 = new Thread(target);
        Thread thread2 = new Thread(target);
        Thread thread3 = new Thread(target);
        //7.以多线程的方式启动线程
        thread.start();
        thread1.start();
        thread2.start();
        thread3.start();*/
    }
}
/**多线程中出现数据安全隐患的原因:
 * 多线程程序 + 有共享数据(成员变量) + 多条语句操作共享数据*/

/**同步锁:相当于给容易出问题的代码加了一把锁,包裹了所有可能会出现安全隐患的代码
 * 加锁后,就有(排队)的效果,但是加锁的范围需要考虑:
 * 加锁范围:不能太大,也不能太小,太大,干啥都得排队,导致程序的效率太低,太小,没锁住,会有安全问题*/
//1.创建自定义多线程类
class TicketRunnable implements Runnable {
    //3.创建成员变量用来保存票数,注意必须是静态的
    static int tickets = 100;
    //8.2创建唯一一个锁对象,不论之后是哪个线程进同步代码块,都是唯一的锁对象
    Object o = new Object();
    //2.添加接口中未实现的方法,把业务放在run()里

    @Override
    /**如果一个方法中的所有代码都需要同步,那这个方法可以设置成同步方法*/
    //synchronized public void run() {/**被synchronized关键字修饰的方法是同步方法*/
    public void run(){

            while (true) {
                synchronized (this) {
                    if (tickets > 0) {
                    /**同步代码块sychronized(锁对象){会出现安全问题的代码}
                     * 同步代码块中,同一时刻同一资源只能被一个线程独享
                     **/


                    try {
                        //让程序休眠后出现的两个问题:
                        //1.重卖:一张票卖给了多个人
                        //2.超卖:出现了票数为0甚至是负数的情况
                        Thread.sleep(10);//让程序休眠10ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //8.1使用同步代码块包裹可能会出现安全隐患的代码,锁对象类型任意
                    /**这种写法不对相当于每个线程进来都要new一个锁对象,线程间并不是使用同一把锁*/
                    //synchronized (new Object)
                    //8.3修改同步代码块的锁对象为成员变量o,注意,"唯一"很重要

                    //3.获取当前正在卖票的线程名称,以及卖票

                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                    //设置死循环的出口
                    }
                    if (tickets <= 0) break;


            }
        }
    }
}

 

3.注解

package cn.sk.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**本类用于测试自定义注解*/
public class TestAnnotation {

}

/**1.2通过@Target注解表示此自定义注解可以使用的位置
 * 注意:@Target注解使用时需要导包,我们通过"ElementType.静态常量值"的方式来指定
 * 此自定义注解可以使用的位置
 * 如果有多个值,可以使用"{,}"的格式来写
 * */
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})//表示此注解可以加在方法上

/**1.3通过@Retention注解表示自定义注解的生命周期
 * 注意:@Retention使用时也需要导包,通过RetentionPolicy.静态常量值来指定此自定义注解的生命周期
 * 也就是指自定义注解可以存在哪个文件中:源码文件中/字节码文件中/运行时有效
 * 而且这三个值,只能3选1,不能同时写多个*/
@Retention(RetentionPolicy.SOURCE)
/**0.首先注意:自定义注解的语法与java不同,不要停套用java的格式*/
//1.定义自定义注解,注解名Test,并通过两个元注解表示此注解的作用位置和生命周期
/**1.注解定义要使用"@interface 注解名"的方式来定义*/
@interface  Test{
    /**3.自定义注解还可以添加功能--给注解添加属性
     * 注意 int age();不是方法的定义,而是给自定义注解中定义age属性的语法
     * 如果为了使用注解时方便还可以给属性指定默认值,这样就可以直接使用,格式:int age() defult 0;
     * */
    //int age();给注解定义一个普通的属性
    int age() default 0;
    /**4.注解中还可以添加功能-可以定义特殊属性value
     * 特殊属性的定义方式与别的属性相同,主要是使用方式不同
     * 使用此注解给属性赋值的时候,可以不用写成"@Test(value = "Apple")"
     * 格式可以简化"@Test("Apple")"直接写值
     * 但是自定义注解类中的赋予默认值不可以简写,如果自定义了默认值,可以不用赋值,直接使用
     * */
    String value() default "lemon";
}
/**2.使用注解时,只要在指定的自定义注解名字前加上"@"即可使用此注解*/
//2.定义一个类来测试自定义注解
    //@Test(age = 0)
class TestAnno{
    /**测试1:分别给TestAnno类/name属性/eat()都添加了@Test注解,只有方法上不报错
     * 结论:自定义注解能够加在什么位置,取决于@Target注释的值
     * 测试2:修改@Target注解的值:@Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})
     * 结论:注解@Test可以存在于多个位置,如果@Target有多个值,格式是{,}
     * 原因:Target注解的源码:ElementType[] value();
     * */
    //@Test(age = 0)
    String name;
    /**测试3:当我们添加了注解的age属性时,@Test注解报错
     * 结论:当注解没有定义属性时可以直接使用,如果有属性了,就需要给属性赋值
     * 测试4:给@Test注解的age属性赋值以后,就不报错了
     * 结论:给属性赋值的格式"@Test(age = 10)",注意,不能直接写10,这是错误格式
     * 测试5:给age属性赋予默认值后,可以不加属性值,直接使用注解,此时使用的就是age的默认值
     * */
    /**测试6:给特殊属性value赋值时可以简写,相当于value = "Apple"*/
    @Test("Apple")/**测试7:因为已有默认值,所以不用给特殊属性赋值,直接用@Test*/
    //@Test(age = 10)
    public void eat(){
        System.out.println("又到干饭时间啦~");
    }
}

四、sleep(),wait(),notify(),notifyall()的区别

sleep():使当前线程睡眠一段时间,没有释放锁,睡眠时间到了会继续执行,例如sleep(600),使线程睡眠6秒。不释放资源,可以使用在任何方法中,sleep必须捕获异常。

wait():释放锁。其他线程可以使用此同步块或方法。不必捕获异常。

notify():唤醒一个处于等待的线程,但是不确定唤醒哪一个。

notifyall():唤醒在此对象监视器上等待的所有线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值