免费网站服务器软件下载大全,建设工程交易网站,无备案网站加速,自己域名做网站线程同步 1.线程同步1.1卖票【应用】1.2卖票案例的问题1.3同步代码块解决数据安全问题【应用】1.4同步方法解决数据安全问题【应用】1.5Lock锁【应用】1.6死锁 2.生产者消费者2.1生产者和消费者模式概述【应用】2.2生产者和消费者案例【应用】2.3生产者和消费者案例优化【应用】… 线程同步 1.线程同步1.1卖票【应用】1.2卖票案例的问题1.3同步代码块解决数据安全问题【应用】1.4同步方法解决数据安全问题【应用】1.5Lock锁【应用】1.6死锁 2.生产者消费者2.1生产者和消费者模式概述【应用】2.2生产者和消费者案例【应用】2.3生产者和消费者案例优化【应用】2.4阻塞队列基本使用2.5阻塞队列实现等待唤醒机制 1.线程同步
1.1卖票【应用】 案例需求 某电影院目前正在上映国产大片共有100张票而它有3个窗口卖票请设计一个程序模拟该电影院卖票 实现步骤 定义一个类SellTicket实现Runnable接口里面定义一个成员变量private int tickets 100; 在SellTicket类中重写run()方法实现卖票代码步骤如下 判断票数大于0就卖票并告知是哪个窗口卖的 卖了票之后总票数要减1 票卖没了线程停止 定义一个测试类SellTicketDemo里面有main方法代码步骤如下 创建SellTicket类的对象 创建三个Thread类的对象把SellTicket对象作为构造方法的参数并给出对应的窗口名称 启动线程 代码实现 public class SellTicket implements Runnable {private int tickets 100;//在SellTicket类中重写run()方法实现卖票代码步骤如下Overridepublic void run() {while (true) {if(ticket 0){//卖完了break;}else{try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket--;System.out.println(Thread.currentThread().getName() 在卖票,还剩下 ticket 张票);}}}
}
public class SellTicketDemo {public static void main(String[] args) {//创建SellTicket类的对象SellTicket st new SellTicket();//创建三个Thread类的对象把SellTicket对象作为构造方法的参数并给出对应的窗口名称Thread t1 new Thread(st,窗口1);Thread t2 new Thread(st,窗口2);Thread t3 new Thread(st,窗口3);//启动线程t1.start();t2.start();t3.start();}
}1.2卖票案例的问题 卖票出现了问题 相同的票出现了多次 出现了负数的票 问题产生原因 线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题
1.3同步代码块解决数据安全问题【应用】 安全问题出现的条件 是多线程环境 有共享数据 有多条语句操作共享数据 如何解决多线程安全问题呢? 基本思想让程序没有安全问题的环境 怎么实现呢? 把多条语句操作共享数据的代码给锁起来让任意时刻只能有一个线程执行即可 Java提供了同步代码块的方式来解决 同步代码块格式 synchronized(任意对象) { 多条语句操作共享数据的代码
}synchronized(任意对象)就相当于给代码加锁了任意对象就可以看成是一把锁 同步的好处和弊端 好处解决了多线程的数据安全问题 弊端当线程很多时因为每个线程都会去判断同步上的锁这是很耗费资源的无形中会降低程序的运行效率 代码演示 public class SellTicket implements Runnable {private int tickets 100;private Object obj new Object();Overridepublic void run() {while (true) {synchronized (obj) { // 对可能有安全问题的代码加锁,多个线程必须使用同一把锁//t1进来后就会把这段代码给锁起来if (tickets 0) {try {Thread.sleep(100);//t1休息100毫秒} catch (InterruptedException e) {e.printStackTrace();}//窗口1正在出售第100张票System.out.println(Thread.currentThread().getName() 正在出售第 tickets 张票);tickets--; //tickets 99;}}//t1出来了这段代码的锁就被释放了}}
}public class SellTicketDemo {public static void main(String[] args) {SellTicket st new SellTicket();Thread t1 new Thread(st, 窗口1);Thread t2 new Thread(st, 窗口2);Thread t3 new Thread(st, 窗口3);t1.start();t2.start();t3.start();}
}1.4同步方法解决数据安全问题【应用】 同步方法的格式 同步方法就是把synchronized关键字加到方法上 修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体
}同步方法的锁对象是什么呢? this 静态同步方法 同步静态方法就是把synchronized关键字加到静态方法上 修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体
}同步静态方法的锁对象是什么呢? 类名.class 代码演示 public class MyRunnable implements Runnable {private static int ticketCount 100;Overridepublic void run() {while(true){if(窗口一.equals(Thread.currentThread().getName())){//同步方法boolean result synchronizedMthod();if(result){break;}}if(窗口二.equals(Thread.currentThread().getName())){//同步代码块synchronized (MyRunnable.class){if(ticketCount 0){break;}else{try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticketCount--;System.out.println(Thread.currentThread().getName() 在卖票,还剩下 ticketCount 张票);}}}}}private static synchronized boolean synchronizedMthod() {if(ticketCount 0){return true;}else{try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticketCount--;System.out.println(Thread.currentThread().getName() 在卖票,还剩下 ticketCount 张票);return false;}}
}public class Demo {public static void main(String[] args) {MyRunnable mr new MyRunnable();Thread t1 new Thread(mr);Thread t2 new Thread(mr);t1.setName(窗口一);t2.setName(窗口二);t1.start();t2.start();}}1.5Lock锁【应用】
虽然我们可以理解同步代码块和同步方法的锁对象问题但是我们并没有直接看到在哪里加上了锁在哪里释放了锁为了更清晰的表达如何加锁和释放锁JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化这里采用它的实现类ReentrantLock来实例化 ReentrantLock构造方法 方法名说明ReentrantLock()创建一个ReentrantLock的实例 加锁解锁方法 方法名说明void lock()获得锁void unlock()释放锁 代码演示 public class Ticket implements Runnable {//票的数量private int ticket 100;private Object obj new Object();private ReentrantLock lock new ReentrantLock();Overridepublic void run() {while (true) {//synchronized (obj){//多个线程必须使用同一把锁.try {lock.lock();if (ticket 0) {//卖完了break;} else {Thread.sleep(100);ticket--;System.out.println(Thread.currentThread().getName() 在卖票,还剩下 ticket 张票);}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}// }}}
}public class Demo {public static void main(String[] args) {Ticket ticket new Ticket();Thread t1 new Thread(ticket);Thread t2 new Thread(ticket);Thread t3 new Thread(ticket);t1.setName(窗口一);t2.setName(窗口二);t3.setName(窗口三);t1.start();t2.start();t3.start();}
}1.6死锁 概述 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源导致这些线程处于等待状态无法前往执行 什么情况下会产生死锁 资源有限同步嵌套 代码演示 public class Demo {public static void main(String[] args) {Object objA new Object();Object objB new Object();new Thread(()-{while(true){synchronized (objA){//线程一synchronized (objB){System.out.println(小康同学正在走路);}}}}).start();new Thread(()-{while(true){synchronized (objB){//线程二synchronized (objA){System.out.println(小薇同学正在走路);}}}}).start();}
}2.生产者消费者
2.1生产者和消费者模式概述【应用】 概述 生产者消费者模式是一个十分经典的多线程协作的模式弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。 所谓生产者消费者问题实际上主要是包含了两类线程 一类是生产者线程用于生产数据 一类是消费者线程用于消费数据 为了解耦生产者和消费者的关系通常会采用共享的数据区域就像是一个仓库 生产者生产数据之后直接放置在共享数据区中并不需要关心消费者的行为 消费者只需要从共享数据区中去获取数据并不需要关心生产者的行为 Object类的等待和唤醒方法 方法名说明void wait()导致当前线程等待直到另一个线程调用该对象的 notify()方法或 notifyAll()方法void notify()唤醒正在等待对象监视器的单个线程void notifyAll()唤醒正在等待对象监视器的所有线程
2.2生产者和消费者案例【应用】 案例需求 桌子类(Desk)定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量 生产者类(Cooker)实现Runnable接口重写run()方法设置线程任务 1.判断是否有包子,决定当前线程是否执行 2.如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子 3.生产包子之后,更新桌子上包子状态,唤醒消费者消费包子 消费者类(Foodie)实现Runnable接口重写run()方法设置线程任务 1.判断是否有包子,决定当前线程是否执行 2.如果没有包子,就进入等待状态,如果有包子,就消费包子 3.消费包子后,更新桌子上包子状态,唤醒生产者生产包子 测试类(Demo)里面有main方法main方法中的代码步骤如下 创建生产者线程和消费者线程对象 分别开启两个线程 代码实现 public class Desk {//定义一个标记//true 就表示桌子上有汉堡包的,此时允许吃货执行//false 就表示桌子上没有汉堡包的,此时允许厨师执行public static boolean flag false;//汉堡包的总数量public static int count 10;//锁对象public static final Object lock new Object();
}public class Cooker extends Thread {
// 生产者步骤
// 1判断桌子上是否有汉堡包
// 如果有就等待如果没有才生产。
// 2把汉堡包放在桌子上。
// 3叫醒等待的消费者开吃。Overridepublic void run() {while(true){synchronized (Desk.lock){if(Desk.count 0){break;}else{if(!Desk.flag){//生产System.out.println(厨师正在生产汉堡包);Desk.flag true;Desk.lock.notifyAll();}else{try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}public class Foodie extends Thread {Overridepublic void run() {
// 1判断桌子上是否有汉堡包。
// 2如果没有就等待。
// 3如果有就开吃
// 4吃完之后桌子上的汉堡包就没有了
// 叫醒等待的生产者继续生产
// 汉堡包的总数量减一//套路://1. while(true)死循环//2. synchronized 锁,锁对象要唯一//3. 判断,共享数据是否结束. 结束//4. 判断,共享数据是否结束. 没有结束while(true){synchronized (Desk.lock){if(Desk.count 0){break;}else{if(Desk.flag){//有System.out.println(吃货在吃汉堡包);Desk.flag false;Desk.lock.notifyAll();Desk.count--;}else{//没有就等待//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}public class Demo {public static void main(String[] args) {/*消费者步骤1判断桌子上是否有汉堡包。2如果没有就等待。3如果有就开吃4吃完之后桌子上的汉堡包就没有了叫醒等待的生产者继续生产汉堡包的总数量减一*//*生产者步骤1判断桌子上是否有汉堡包如果有就等待如果没有才生产。2把汉堡包放在桌子上。3叫醒等待的消费者开吃。*/Foodie f new Foodie();Cooker c new Cooker();f.start();c.start();}
}2.3生产者和消费者案例优化【应用】 需求 将Desk类中的变量,采用面向对象的方式封装起来生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用创建生产者和消费者线程对象,构造方法中传入Desk类对象开启两个线程 代码实现 public class Desk {//定义一个标记//true 就表示桌子上有汉堡包的,此时允许吃货执行//false 就表示桌子上没有汉堡包的,此时允许厨师执行//public static boolean flag false;private boolean flag;//汉堡包的总数量//public static int count 10;//以后我们在使用这种必须有默认值的变量// private int count 10;private int count;//锁对象//public static final Object lock new Object();private final Object lock new Object();public Desk() {this(false,10); // 在空参内部调用带参,对成员变量进行赋值,之后就可以直接使用成员变量了}public Desk(boolean flag, int count) {this.flag flag;this.count count;}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag flag;}public int getCount() {return count;}public void setCount(int count) {this.count count;}public Object getLock() {return lock;}Overridepublic String toString() {return Desk{ flag flag , count count , lock lock };}
}public class Cooker extends Thread {private Desk desk;public Cooker(Desk desk) {this.desk desk;}
// 生产者步骤
// 1判断桌子上是否有汉堡包
// 如果有就等待如果没有才生产。
// 2把汉堡包放在桌子上。
// 3叫醒等待的消费者开吃。Overridepublic void run() {while(true){synchronized (desk.getLock()){if(desk.getCount() 0){break;}else{//System.out.println(验证一下是否执行了);if(!desk.isFlag()){//生产System.out.println(厨师正在生产汉堡包);desk.setFlag(true);desk.getLock().notifyAll();}else{try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}public class Foodie extends Thread {private Desk desk;public Foodie(Desk desk) {this.desk desk;}Overridepublic void run() {
// 1判断桌子上是否有汉堡包。
// 2如果没有就等待。
// 3如果有就开吃
// 4吃完之后桌子上的汉堡包就没有了
// 叫醒等待的生产者继续生产
// 汉堡包的总数量减一//套路://1. while(true)死循环//2. synchronized 锁,锁对象要唯一//3. 判断,共享数据是否结束. 结束//4. 判断,共享数据是否结束. 没有结束while(true){synchronized (desk.getLock()){if(desk.getCount() 0){break;}else{//System.out.println(验证一下是否执行了);if(desk.isFlag()){//有System.out.println(吃货在吃汉堡包);desk.setFlag(false);desk.getLock().notifyAll();desk.setCount(desk.getCount() - 1);}else{//没有就等待//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}public class Demo {public static void main(String[] args) {/*消费者步骤1判断桌子上是否有汉堡包。2如果没有就等待。3如果有就开吃4吃完之后桌子上的汉堡包就没有了叫醒等待的生产者继续生产汉堡包的总数量减一*//*生产者步骤1判断桌子上是否有汉堡包如果有就等待如果没有才生产。2把汉堡包放在桌子上。3叫醒等待的消费者开吃。*/Desk desk new Desk();Foodie f new Foodie(desk);Cooker c new Cooker(desk);f.start();c.start();}
}2.4阻塞队列基本使用
阻塞队列继承结构 常见BlockingQueue: ArrayBlockingQueue: 底层是数组,有界 LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值 BlockingQueue的核心方法: put(anObject): 将参数放入队列,如果放不进去会阻塞 take(): 取出第一个数据,取不到会阻塞 代码示例 public class Demo02 {public static void main(String[] args) throws Exception {// 创建阻塞队列的对象,容量为 1ArrayBlockingQueueString arrayBlockingQueue new ArrayBlockingQueue(1);// 存储元素arrayBlockingQueue.put(汉堡包);// 取元素System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞System.out.println(程序结束了);}
}2.5阻塞队列实现等待唤醒机制 案例需求 生产者类(Cooker)实现Runnable接口重写run()方法设置线程任务 1.构造方法中接收一个阻塞队列对象 2.在run方法中循环向阻塞队列中添加包子 3.打印添加结果 消费者类(Foodie)实现Runnable接口重写run()方法设置线程任务 1.构造方法中接收一个阻塞队列对象 2.在run方法中循环获取阻塞队列中的包子 3.打印获取结果 测试类(Demo)里面有main方法main方法中的代码步骤如下 创建阻塞队列对象 创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象 分别开启两个线程 代码实现 public class Cooker extends Thread {private ArrayBlockingQueueString bd;public Cooker(ArrayBlockingQueueString bd) {this.bd bd;}
// 生产者步骤
// 1判断桌子上是否有汉堡包
// 如果有就等待如果没有才生产。
// 2把汉堡包放在桌子上。
// 3叫醒等待的消费者开吃。Overridepublic void run() {while (true) {try {bd.put(汉堡包);System.out.println(厨师放入一个汉堡包);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Foodie extends Thread {private ArrayBlockingQueueString bd;public Foodie(ArrayBlockingQueueString bd) {this.bd bd;}Overridepublic void run() {
// 1判断桌子上是否有汉堡包。
// 2如果没有就等待。
// 3如果有就开吃
// 4吃完之后桌子上的汉堡包就没有了
// 叫醒等待的生产者继续生产
// 汉堡包的总数量减一//套路://1. while(true)死循环//2. synchronized 锁,锁对象要唯一//3. 判断,共享数据是否结束. 结束//4. 判断,共享数据是否结束. 没有结束while (true) {try {String take bd.take();System.out.println(吃货将 take 拿出来吃了);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Demo {public static void main(String[] args) {ArrayBlockingQueueString bd new ArrayBlockingQueue(1);Foodie f new Foodie(bd);Cooker c new Cooker(bd);f.start();c.start();}
}后记 美好的一天到此结束下次继续努力欲知后续请看下回分解写作不易感谢大家的支持