线程安全问题:JAVA多线程练习,模拟窗口卖票

/*
    电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是葫芦娃大战奥特曼    本次电影的座位共100
    (本场电影只能卖100张票)    我们来模拟电影院的售票窗口,实现多个窗口同时卖葫芦娃大战奥特曼这场电影票
    (多个窗口一起卖这100张票)
    需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟

    使用三个线程去卖票
 */
public class Demo01TicketTest {
    public static void main(String[] args) {
        //创建runnable接口类实现对象
        Ticket t=new Ticket();
        //创建三个线程实现卖票任务并启动
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();

//    如果多个线程操作共享数据,那么有可能引发线程安全问题。本类是出现线程安全的例子
//打印结果:会出现多个窗口卖出同一张票。
    }

}


/*定义票并且这个类是线程任务类,线程要执行的任务是卖票,所以在run方法中执行卖票操作*/

public class Ticket implements Runnable {
    //定义票
    int num = 100;

    @SuppressWarnings("all")
    //run方法中定义买票的操作
    @Override
    public void run() {
        //定义一个永真循环一直去卖票
        while (true)//有票就卖票
        {
            if (num > 0) {
                //卖票手续用了10ms
                try {
                    Thread.sleep(10);//因为其父类没有抛异常,这里只能trycatch处理
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + num + "张票");
                num--;
            }
        }

    }
}
线程安全问题介绍:
如下图,三个箭头代表该程序中的三个线程,当线程1进入while true中,我假设卖买票过程等待了10毫秒,然而在这10毫秒内,假如线程2,线程3 抢夺到了CPU资源,cpu开始执行线程线程2,线程进入10毫秒的位置也等待10毫秒,此时cpu去调度线程3,三个线程都以num=100时候的值,创建了各自的栈,运行下去,都将第100张票卖了出去。最后出现票卖的多于拥有的票。
 
  
 /*
如果要解决线程安全问题,那么可以使用synchronize关键字。
    synchronized这个关键字表示同步, 可以修饰代码块,也可以修饰方法。

    如果synchronize修饰代码块,那么这个就叫做同步代码块

    格式:
        synchronized(锁对象) {
            要同步的代码;
        }

    锁对象就是一个普通的java对象,可以是任意的一个对象。
    锁对象只是做一个标记。

    同步代码块的作用:
        只有持有锁对象的线程才能够进入到同步代码块中。
*/
public class Demo02TicketTest {
   
public static void main(String[] args) {
        Ticket2 t =
new Ticket2();
       
new Thread(t).start();
       
new Thread(t).start();
       
new Thread(t).start();
// 不会出现票卖多的情况

    }

}
public class Ticket2 implements Runnable {
    //定义一个对象,用来当做锁对象
    Object obj = new Object();

    //这个对象并没有特别的作用,只是当做一个标记。
    //锁对象可以是任意对象,可以是ObjectStudentPerson
    //一定要保证多个线程用的是同一个锁对象。
    @Override
    public void run() {
        int num = 100;
        while (true) {//当一个线程执行到synchronize代码块上,这个线程会检查上面还有锁嘛,如果同步代码块上
//            还有锁对象,就会把锁对象拿走,然后进入到同步代码块里面,如果同步代码块上没有锁对象,
//            那么线程就会一直在这里等,什么时候锁对象来了,什么时候进入
            synchronized (obj) {
                if (num > 0) {
                    System.out.println("线程" + Thread.currentThread().getName() + "正在卖第" + num + "张票");
                    num--;
                }
            }//当线程离开代码块,会释放锁 obj

        }

    }
}
/*
    同步方法。

    如果synchronize修饰方法,那么这个方法就是同步方法。

    格式:
        修饰符 synchronized 返回值类型 方法名(参数列表) {
            方法体 ;
        }

    作用:
        同步方法相当于把整个的方法体都加了同步。

    同步方法也是有锁的
    如果这个方法是非静态的方法,那么这个锁对象是this
    如果这个方法是静态方法,那么锁对象是 当前类.class(后期反射会学)


    现在我们学习了两种解决线程安全问题的方式
    方式一是同步代码块
    方式二是同步方法
    同步代码块更加灵活
    同步方法语法更加的简洁更加的清晰。


 */
public class Demo03TicketTest {
    public static void main(String[] args) {
        Ticket3 t=new Ticket3();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();

    }
}
public class Ticket3 implements Runnable{
    //定义票
    int num = 100;
//Object ok=new Object();
    @SuppressWarnings("all")
    //run方法中定义买票的操作
    @Override
    public synchronized  void  run() {

        while (true)//有票就卖票
        {
         if (num > 0) {
                //卖票手续用了10ms
                try {
                    Thread.sleep(10);//因为其父类没有抛异常,这里只能trycatch处理
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + num + "张票");
                num--;
            }
        }

    }
}
JDK5之后,提供了java.util.concurrent包,里面提供了大量对于线程操作的类和接口。
  其中有一个接口,叫做Lock,他可以手动的获取锁以及释放锁。

    void lock​(): 手动获取锁
    void unlock​():手动释放锁。

    Lock是一个接口,如果要用,需要使用对应的实现类
    ReentrantLock 是实现类之一。
 */
public class Demo04TickeTest {
    public static void main(String[] args) {
        Ticket4 t=new Ticket4();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();

    }

}
public class Ticket4 implements Runnable {
    int num = 100;
    Lock lock = new ReentrantLock();

    @SuppressWarnings("all")
    //run方法中定义买票的操作
    @Override
    public void run() {
        //定义一个永真循环一直去卖票
        System.out.println(Thread.currentThread().getName() + "进程等待中");

        while (true)//有票就卖票
        {
            lock.lock();
            if (num > 0) {
                //卖票手续用了10ms
                try {
                    Thread.sleep(10);//因为其父类没有抛异常,这里只能trycatch处理
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + num + "张票");
                num--;

            }
            lock.unlock();

        }

        //手动释放锁

    }//手动释放锁
}

猜你喜欢

转载自blog.csdn.net/qq_28761767/article/details/81005899