Java线程创建(卖票),线程同步(卖包子)的实现示例
作者:码农编程录
1.线程两种创建方式:new Thread(new Runnable() {})
如下FileOutputStream源码中抛出异常,为了让写代码人自己写try catch异常提示信息。
package com.itheim07.thread; /* * 进程和线程 * 1. 进程 : 航空母舰(资源: 燃油 弹药) * 2. 线程 : 舰载机 * 一个软件运行: 一个军事活动, 必须有一艘航母出去,但执行具体任务的是航母上的舰载机 * 一个软件运行,至少一个进程, 一个进程中至少一个线程。谷歌浏览器是多进程,进程多了,占用资源多,速度快 * * cpu: 4核 8线程。线程要运行,需要cpu授予执行权(指挥室),指挥室可以同时调度8架 飞机 * 1. 并行 : 同一时间,同时执行 (并行只能8线程) * 2. 并发 : 同一段时间, 实际上是交替执行, 速度快的时候看起来像是同时执行(频率快)(常见: 并发1800线程) * * cpu调度算法(并发) * 1. 分时调度 : 1800s, 每个线程1s * 2. 抢占式调度 : 按照线程优先级进行分配, 优先级高(可以自己设置)一般就分配的多(随机性强) java * * 为什么需要多线程? * 1. 默认java代码有两个线程 * 1. main方法线程 : 主线程 * 2. GC线程(jvm使用的,我们无法调度) * 2. 一个线程可用, 有什么局限性?只能做一件事 * 3. 如果想要同时执行多个任务 -> 多线程 */ public class ThreadDemo { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { while(true){ System.out.println("播放音乐..."); } } }).start(); //.start()不能改成.run() boolean result = true; while(result){ System.out.println("下载电影..."); } /* while(result){ //虽然骗了编译器,但还是不能执行到这里 System.out.println("播放音乐..."); }*/ } }
如下线程第一种创建方式。
package com.itheima01.thread; /* Thread:1. start() : 启动线程,jvm会创建线程,并调用run方法 2. static Thread currentThread(),返回对当前正在执行的线程对象的引用。 3. String getName() : 获取线程名称 !!! Thread.currentThread().getName() : 获取当前线程名称 线程默认命名规则:1. main线程 : main 2. 子线程(main线程创建的线程) : static int number;static被共享 Thread-0 , 1, 2 ... */ public class ThreadDemo02 { public static void main(String[] args) { // Thread thread = Thread.currentThread(); // String name = thread.getName(); // System.out.println(name); // main //下面一行等同于上面 System.out.println("主:" + Thread.currentThread().getName()); YourThread yt = new YourThread(); yt.start(); //子:Thread-0 YourThread yt2 = new YourThread(); yt.run(); //子:main。 因为子线程YourThread还未执行起飞 ,被main飞机拖着走 YourThread yt3 = new YourThread(); yt3.start(); //子:Thread-2。 不是Thread-1是因为yt2未起飞但依旧new了yt2 // Person p = new Person(); //执行空参构造 // System.out.println(p.number); //0 // Person p2 = new Person(); // System.out.println(p2.number); //1 } } class YourThread extends Thread{ @Override public void run() { System.out.println("子:" + Thread.currentThread().getName()); } } class Person{ static int number=-1; public Person(){ number++; } }
package com.itheima02.runnable; /* * 线程第二种创建方式: 1. 声明实现 Runnable 接口的类。 * 2. 该类然后实现 run 方法。 * 3. 然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。 * Thread(Runnable target) */ public class RunnableDemo { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); // 分配该类的实例 Thread t = new Thread(mr); t.start(); //Thread-0 } } class MyRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
package com.itheima02.runnable; //用匿名内部类简化上面代码 public class RunnableDemo02 { public static void main(String[] args) { /* Runnable mr = new Runnable(){ //用接口名Runnable代替子类类名,匿名对象。 //不用再写class MyRunnable implements Runnable{},Runnable mr = new MyRunable(); 向上转型 @Override public void run() { //new一个接口()再{},是new这个接口的子类对象 System.out.println(Thread.currentThread().getName()); } }; Thread t = new Thread(mr); t.start(); // new Thread(mr).start(); */ //111111111111111111111111111111111111111111111111111111111111111111111111111111 new Thread(new Runnable() { @Override public void run() { //主要关注run System.out.println(Thread.currentThread().getName()); } }).start(); //new Thread(() -> System.out.println(Thread.currentThread().getName())).start(); } }
2.卖票:原子性
package com.itheima03.ticket; /* * 需求假设某航空公司有三个窗口发售某日某次航班的100张票,100张票可以作为共享资源,三个售票窗口需要创建三个线程 * 好处: 多线程执行同一任务,比较快 * 1. 程序(单线程) , 并发1600线程, cpu分配执行权: 1/1600 * 2. 程序(多线程 100) , 并发1700, cpu分配给我们的程序执行权更多:1/17 * 注意: 线程不是越多越好(线程本身很占内存, 慢。票数不多不需要用多线程) */ public class TicketDemo01 { public static void main(String[] args) { MyWindow mw1 = new MyWindow(); //堆中开一块空间,不加static,number=100进堆 mw1.setName("窗口壹"); MyWindow mw2 = new MyWindow(); //同上 mw2.setName("窗口222"); MyWindow mw3 = new MyWindow(); //同上 mw3.setName("窗口三三三"); mw1.start(); mw2.start(); mw3.start(); } } //11111111111111111111111111111111111111111111111111111111111111111111111111111 class MyWindow extends Thread{ static int number = 100; //去掉static,每创建一个MyWindow窗口在堆里开辟一块空间,三个窗口各卖100张 @Override public void run() { while(number > 0){ System.out.println(Thread.currentThread().getName() + "正在卖出第" + number + "张票"); number--; } } }
/* * 两种线程创建方式: 1. 继承Thread * 2. 实现Runnbale * 如上第二种方案会更好一些,不需要加static,因为只new了一个对象 * 1. 实现接口,而不是继承类(扩展性更强) 接口可以多实现,但是类只能单继承(MyWindow继承Thread后,就不能继承另外的类。MyTask可以继承其他类,实现其他接口) * 2. 更符合 面向对象 (高内聚,低耦合:线程独立,和业务代码MyTask分离,传入卖猪肉任务也行)。封装(各干各的,有必要再进行合作) */
如下线程同步问题分析:两种创建方式3个窗口都总卖出102张票,而不是100张。原因:三个窗口同时卡在打印正在卖出第100张票。解决:t1在卖第100张票时,cpu可能会切到t3和t2,可以控制t2和t3不动,等t1的number- -完再动。
3.线程同步:synchronized关键字,Lock接口,ThreadLocal
package com.itheima04.synchronizedd; import java.io.IOException; /* * 1. 代码块 * synchronized(锁对象){ * 代码A * } * 1. 锁对象可以是任意对象,但必须唯一 * 2. 同步代码块中的 代码A 同一时间,只允许一个线程执行 * 使用同步锁的注意点:1. 在保证业务逻辑可用的情况,同步锁加的范围越小越好 * * 2. 锁对象必须唯一:<1> 如果能保证当前对象唯一,this也可以作为锁对象 (更节省内存) * <2> 当前类名.class(最好的锁对象) -> Class对象(一个类被加载,在内存都会有一个Class对象) 反射 */ public class TicketDemo02 { public static void main(String[] args) { MyTask mt = new MyTask(); //上面只new了一个,可以用this Thread t1 = new Thread(mt); t1.setName("窗口壹"); Thread t2 = new Thread(mt); t2.setName("窗口222"); Thread t3 = new Thread(mt); t3.setName("窗口三三三"); t1.start(); t2.start(); t3.start(); } } class MyTask implements Runnable{ int number = 100; // Object obj = new Object(); //锁对象 @Override public void run() { while(number > 0){ //1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 synchronized(MyTask.class){ //MyTask.class也可以换成this if(number <= 0){ break; //跳出while大循环 } System.out.println(Thread.currentThread().getName() + "正在卖出第" + number + "张票"); number--; } //111111111111111111111111111111111111111111111111111111111111111111111111111111111111 //这边只能try catch不能throws,原因:父类Runnable中run方法没有声明抛出编译异常,所以子类也不能throws try { Thread.sleep(1); //线程啥事也不干,暂停1ms,cpu有空闲切换其他线程 } catch (InterruptedException e) { e.printStackTrace(); } } //while里 } }
如下t2卖到0张时出while,而t1和t3还在while里,此时number=0,所以变为0和-1。
如下把synchronized拖到外面也不行。
如下加if(number <= 0),没有加浪费时间代码,所以看不到交替效果,但不会出现0和-1。
obj是锁对象即钥匙,如下钥匙不能进run方法(每个线程一把即三把钥匙了),只能在成员位置。
用this,不用new object(),可以节约内存。
package com.itheima05.method; /* * synchronized 方法(同步方法) * 1. 语法 : 方法声明 + synchronized * 2. 同步方法有没有锁对象? 有 * 1. 普通方法: 是this * 2. 静态方法: 静态不能和对象(this)有关。 是当前类名.class */ public class TicketDemo02 { public static void main(String[] args) { MyTask mt = new MyTask(); Thread t1 = new Thread(mt); t1.setName("窗口壹"); Thread t2 = new Thread(mt); t2.setName("窗口222"); Thread t3 = new Thread(mt); t3.setName("窗口三三三"); t1.start(); t2.start(); t3.start(); } } class MyTask implements Runnable{ static int number = 100; @Override public void run() { while(number > 0){ method(); //非静态方法可以调用静态方法 try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } private static synchronized void method() { //静态方法不能和对象关键字如this相关 //同步方法效果 等价于 同步代码块 if(number <= 0){ return; //break只能写在循环和switch里 } System.out.println(Thread.currentThread().getName() + "正在卖出第" + number + "张票"); number--; } }
package com.itheima06.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* * Lock接口: 1. 实现类 ReentrantLock * 2. lock() 获取锁(获取钥匙) * 3. unlock() 释放锁 (还钥匙) */ public class TicketDemo02 { public static void main(String[] args) { MyTask mt = new MyTask(); Thread t1 = new Thread(mt); t1.setName("窗口壹"); Thread t2 = new Thread(mt); t2.setName("窗口222"); Thread t3 = new Thread(mt); t3.setName("窗口三三三"); t1.start(); t2.start(); t3.start(); } } class MyTask implements Runnable{ int number = 100; Lock lock = new ReentrantLock(); //创建lock对象 @Override public void run() { while(number > 0){ //1111111111111111111111111111111111111111111111111111111111111111111111111 lock.lock(); if(number <= 0){ // System.out.println(Thread.currentThread().getName()); lock.unlock(); // 注意: lock提供了锁的可视化操作(线程执行结束,要记得手动释放。厕所上完不能带走钥匙)//同步代码块return或break后是jvm自动释放锁。//这里不加lock.unlock()程序停不下来。 break; } System.out.println(Thread.currentThread().getName() + "正在卖出第" + number + "张票"); number--; lock.unlock(); } } }
如下ThreadLocal相当于一个map,key就是当前的线程,value就是需要存储的对象。
t1(…,User),如下情况可将User放入ThreadLocal中,每次通过.get拿到线程的User。
4.卖包子:wait,notify
package com.itheima07.bz; public class Demo { public static void main(String[] args) throws InterruptedException { Object obj = new Object(); // obj.wait(); //IllegalMonitorStateException : 非法的监视状态异常,因为.wait()必须锁对象调用如下 synchronized (obj){ //对象变成锁对象 obj.wait(); //不会报错,一直等待。在锁对象中 } } }
如下两个方法wait和notify不是给线程调用的,而是给锁对象【锁对象可以是任意对象】调用的如上所示。BaoZi只能一个线程对其操作。
package com.itheima07.bz; public class BaoZi { boolean isHave=false; //默认没有包子 }
package com.itheima07.bz; public class BaoziPu extends Thread { BaoZi bz; public BaoziPu(BaoZi bz){ this.bz = bz; } @Override public void run() { while(true){ //不停生产包子 //111111111111111111111111111111111111111111111111111111111111111111111111111111 synchronized (bz){ //加锁: 同步代码,生产包子时不让别人打扰我。注意下面wait和notify if(bz.isHave){ try { bz.wait(); //包子铺有包子就等待(此时吃货正在吃包子) } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("包子铺生产包子..."); //没包子 bz.isHave = true; bz.notify(); //唤醒吃货 } } //while里 } }
package com.itheima07.bz; public class ChiHuo extends Thread{ BaoZi bz; public ChiHuo(BaoZi bz){ this.bz = bz; } @Override public void run() { while(true){ //不停吃包子 //1111111111111111111111111111111111111111111111111111111111111111111111111111 synchronized (bz){ if(!bz.isHave){ try { bz.wait(); //吃货没有包子就等待(此时包子铺正在生产包子) } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("吃货吃包子"); //有包子 bz.isHave = false; bz.notify(); //唤醒包子铺 } } } }
package com.itheima07.bz; public class BzDemo { public static void main(String[] args) { BaoZi bz = new BaoZi(); BaoziPu bzp = new BaoziPu(bz); //和下面一行共同操作一个包子对象 ChiHuo ch = new ChiHuo(bz); bzp.start(); ch.start(); } }
如下第一次没有包子,所以绕过2中if到1。运行完1后就有包子了,1时间很短,cpu不切换线程,切换了也没用,因为2中syn…(bz)包子被锁住,就算切换到吃货线程进不去syn…(bz)里,所以1中notify唤不醒吃货线程。
1和2都在sy…(bz)里,bzp线程bz.wait()【有3个好处】进入等待状态即进入监视队列即等待包子被吃
,吃货线程的synchronized锁被打开,有包子不会wait,执行3。
一个线程wait把自己停下来放入堆(监视队列)
中,来年开春,另一个线程中3叫我起来干活。2和3对应,1和4对应。3唤醒了2中wait,但2没钥匙(锁)动不了(鬼压床),钥匙在吃货手上,所以3往后4执行释放锁,1234不停循环执行。
生产消费者模型:用户发请求来
相当于包子铺生产包子即生产者
。服务器
24小时开着相当于消费者
一天24小时等包子吃。不会让消费者线程空转浪费cpu资源,所以没包子设置消费者线程为wait状态不占用cpu资源
。
package com.atguigu.test14; // 线程通信是用来解决生产者与消费者问题。 public class Test14 { public static void main(String[] args) { Workbench tai = new Workbench(); //相当于包子 Cook c = new Cook("崔志恒", tai); //生产者 Waiter w = new Waiter("翠花", tai); //消费者 c.start(); w.start(); } } //11111111111111111111111111111111111111111111111111111111111111111111111111 class Workbench{ private static final int MAX = 10; //假设工作台上最多能够放10盘 private int count; //count是共用的,要考虑线程安全 public synchronized void put(){ //同步方法,非静态方法来说,锁对象就是this //往工作台上放一盘菜 if(count >= MAX){ try { //生产者停下来,等待 wait();//默认是this.wait(),所以上面必须加锁对象synchronized } catch (InterruptedException e) { e.printStackTrace(); } } //上面是安全校验 count++; System.out.println(Thread.currentThread().getName() + "放了一盘菜,剩余:" + count); this.notify(); // 包子/工作台.notify() //唤醒消费者 } //1111111111111111111111111111111111111111111111111111111111111111111111111111 public synchronized void take(){//从工作台上取走一盘菜 if(count<=0){ try { wait(); //工作台没有菜,消费者应该停下来 } catch (InterruptedException e) { e.printStackTrace(); } } //上面是安全校验 count--; System.out.println(Thread.currentThread().getName() + "取走一盘菜,剩余:" + count); this.notify(); //唤醒生产者 } } //1111111111111111111111111111111111111111111111111111111111111111111111111 class Cook extends Thread{ private Workbench tai; public Cook(String name, Workbench tai) { super(name); this.tai = tai; } public void run(){ while(true){ tai.put(); //封装了 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } //111111111111111111111111111111111111111111111111111111111111111111111 class Waiter extends Thread{ private Workbench tai; public Waiter(String name, Workbench tai) { super(name); //name属性在父类中已声明 this.tai = tai; } public void run(){ while(true){ tai.take(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
如下一直交替运行,不停。
如下线程6态:锁就是钥匙上厕所,限时等待就是sleep,记住下面三个红色。
如下B进不去不执行
到此这篇关于Java线程创建(卖票),线程同步(卖包子)的实现示例的文章就介绍到这了,更多相关Java线程创建同步内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!