线程安全问题:JAVA多线程练习,模拟窗口卖票
、
/*
如果要解决线程安全问题,那么可以使用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();
//不会出现票卖多的情况
}
}
/* 电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”, 本次电影的座位共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(); //这个对象并没有特别的作用,只是当做一个标记。 //锁对象可以是任意对象,可以是Object,Student,Person //一定要保证多个线程用的是同一个锁对象。 @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(); } //手动释放锁 }//手动释放锁 }