当前位置: 首页 > news >正文

想再算命网站上登广告怎么做搜索关键词优化

想再算命网站上登广告怎么做,搜索关键词优化,网站建设的解决办法,如何做交易网站第9章 多线程 学习目标 了解进程和线程的区别 能够理解并发与并行的区别 能够使用继承类的方式创建多线程 能够使用实现接口的方式创建多线程 能够说出实现接口方式的好处 能够解释安全问题的出现的原因 能够使用同步代码块解决线程安全问题 能够使用同步方法解决线程安全问题…第9章 多线程 学习目标 了解进程和线程的区别 能够理解并发与并行的区别 能够使用继承类的方式创建多线程 能够使用实现接口的方式创建多线程 能够说出实现接口方式的好处 能够解释安全问题的出现的原因 能够使用同步代码块解决线程安全问题 能够使用同步方法解决线程安全问题 能够理解线程通信概念 能够理解等待唤醒机制 能够说出线程的生命周期 第九章 多线程 我们在之前学习的程序在没有跳转语句的前提下都是由上至下依次执行那现在想要设计一个程序边打游戏边听歌怎么设计 要解决上述问题,咱们得使用多进程或者多线程来解决. 9.1 相关概念了解 9.1.1 线程与进程 程序为了完成某个任务和功能选择一种编程语言编写的一组指令的集合。 软件1个或多个应用程序相关的素材和资源文件等构成一个软件系统。 进程是指一个内存中运行的应用程序每个进程都有一个独立的内存空间进程也是程序的一次执行过程是系统运行程序的基本单位系统运行一个程序即是一个进程从创建、运行到消亡的过程。 线程线程是进程中的一个执行单元负责当前进程中程序的执行一个进程中至少有一个线程。一个进程中是可以有多个线程的这个应用程序也可以称之为多线程程序。 简而言之一个软件中至少有一个应用程序应用程序的一次运行就是一个进程一个进程中至少有一个线程。 面试题进程是操作系统调度和分配资源的最小单位线程是CPU调度的最小单位。不同的进程之间是不共享内存的。进程之间的数据交换和通信的成本是很高。不同的线程是共享同一个进程的内存的。当然不同的线程也有自己独立的内存空间。对于方法区堆中中的同一个对象的内存线程之间是可以共享的但是栈的局部变量永远是独立的。另外进程之前切换的复杂度要远远高于线程之间的切换调度。 9.1.2 查看进程和线程 我们可以再电脑底部任务栏右键-----打开任务管理器,可以查看当前任务的进程 1、每个应用程序的运行都是一个进程 2、一个应用程序的多次运行就是多个进程 3、一个进程中包含多个线程 9.1.3 并发与并行 并行parallel指两个或多个事件在同一时刻发生同时发生。指在同一时刻有多条指令在多个处理器上同时执行。并发concurrency指两个或多个事件在同一个时间段内发生。指在同一个时刻只能有一条指令执行但多个进程的指令被快速轮换执行使得在宏观上具有多个进程同时执行的效果。 在操作系统中启动了多个程序并发指的是在一段时间内宏观上有多个程序同时运行这在单 CPU 系统中每一时刻只能有一个程序执行即微观上这些程序是分时的交替运行只不过是给人的感觉是同时运行那是因为分时交替运行的时间是非常短的。 而在多个 CPU 系统中则这些可以并发执行的程序便可以分配到多个处理器上CPU实现多任务并行执行即利用每个处理器来处理一个可以并发执行的程序这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU便是多核处理器核越多并行处理的程序越多能大大的提高电脑运行的效率。 例子 并行多项工作一起执行之后再汇总例如泡方便面电水壶烧水一边撕调料倒入桶中 并发同一时刻多个线程在访问同一个资源多个线程对一个点例如春运抢票、电商秒杀… 注意单核处理器的计算机肯定是不能并行的处理多个任务的只能是多个任务在单个CPU上并发运行。同理线程也是一样的从宏观角度上理解线程是并行运行的但是从微观角度上分析却是串行运行的即一个线程一个线程的去运行当系统只有一个CPU时线程会以某种顺序执行多个线程我们把这种情况称之为线程调度。 单核CPU只能并发 多核CPU并行并发 9.1.4 线程调度 分时调度 所有线程轮流使用 CPU 的使用权平均分配每个线程占用 CPU 的时间。 抢占式调度 优先让优先级高的线程使用 CPU如果线程的优先级相同那么会随机选择一个(线程随机性)Java使用的为抢占式调度。 抢占式调度详解 大部分操作系统都支持多进程并发运行现在的操作系统几乎都支持同时运行多个程序。比如现在我们上课一边使用编辑器一边使用录屏软件同时还开着画图板dos窗口等软件。此时这些程序是在同时运行”感觉这些软件好像在同一时刻运行着“。 实际上CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言某个时刻只能执行一个线程而 CPU的在多个线程间切换速度相对我们的感觉要快看上去就是在同一时刻运行。 其实多线程程序并不能提高程序的运行速度但能够提高程序运行效率让CPU的使用率更高。 9.2 另行创建和启动线程 当运行Java程序时其实已经有一个线程了那就是main线程。 那么如何创建和启动main线程以外的线程呢 9.2.1 继承Thread类 Java使用java.lang.Thread类代表线程所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下 定义Thread类的子类并重写该类的run()方法该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。创建Thread子类的实例即创建了线程对象调用线程对象的start()方法来启动该线程 代码如下 自定义线程类 package com.atguigu.thread;public class MyThread extends Thread {//定义指定线程名称的构造方法public MyThread(String name) {//调用父类的String参数的构造方法指定线程的名称super(name);}/*** 重写run方法完成该线程执行的逻辑*/Overridepublic void run() {for (int i 0; i 10; i) {System.out.println(getName()正在执行i);}} }测试类 package com.atguigu.thread;public class TestMyThread {public static void main(String[] args) {//创建自定义线程对象MyThread mt new MyThread(新的线程);//开启新线程mt.start();//在主方法中执行for循环for (int i 0; i 10; i) {System.out.println(main线程i);}} } 9.2.2 实现Runnable接口 Java有单继承的限制当我们无法继承Thread类时那么该如何做呢在核心类库中提供了Runnable接口我们可以实现Runnable接口重写run()方法然后再通过Thread类的对象代理启动和执行我们的线程体run()方法 步骤如下 定义Runnable接口的实现类并重写该接口的run()方法该run()方法的方法体同样是该线程的线程执行体。创建Runnable实现类的实例并以此实例作为Thread的target来创建Thread对象该Thread对象才是真正 的线程对象。调用线程对象的start()方法来启动线程。 代码如下 自定义线程类 package com.atguigu.thread;public class MyRunnable implements Runnable {Overridepublic void run() {for (int i 0; i 20; i) {System.out.println(Thread.currentThread().getName() i);}} }测试类 package com.atguigu.thread;public class TestMyRunnable {public static void main(String[] args) {//创建自定义类对象 线程任务对象MyRunnable mr new MyRunnable();//创建线程对象Thread t new Thread(mr, 长江);t.start();for (int i 0; i 20; i) {System.out.println(黄河 i);}} }通过实现Runnable接口使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程 代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。 在启动的多线程的时候需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象然后调用Thread对象的start()方法来运行多线程代码。 实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此不管是继承Thread类还是实现 Runnable接口来实现多线程最终还是通过Thread的对象的API来控制线程的熟悉Thread类的API是进行多线程编程的基础。 tips:Runnable对象仅仅作为Thread对象的targetRunnable实现类里包含的run()方法仅作为线程执行体。 而实际的线程对象依然是Thread实例只是该Thread线程负责执行其target的run()方法。 9.2.3 使用匿名内部类对象来实现线程的创建和启动 new Thread(新的线程){Overridepublic void run() {for (int i 0; i 10; i) {System.out.println(getName()正在执行i);}}}.start();new Thread(new Runnable(){Overridepublic void run() {for (int i 0; i 10; i) {System.out.println(Thread.currentThread().getName() i);}}}).start();9.3 Thread类 9.3.1 构造方法 public Thread() :分配一个新的线程对象。 public Thread(String name) :分配一个指定名字的新的线程对象。 public Thread(Runnable target) :分配一个带有指定目标新的线程对象。 public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。 9.3.2 常用方法系列1 public void run() :此线程要执行的任务在此处定义代码。 public String getName() :获取当前线程名称。 public static Thread currentThread() :返回对当前正在执行的线程对象的引用。 public final boolean isAlive()测试线程是否处于活动状态。如果线程已经启动且尚未终止则为活动状态。 public final int getPriority() 返回线程优先级 public final void setPriority(int newPriority) 改变线程的优先级 每个线程都有一定的优先级优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级其中setPriority方法需要一个整数并且范围在[1,10]之间通常推荐设置Thread类的三个优先级常量MAX_PRIORITY10最高优先级MIN _PRIORITY 1最低优先级NORM_PRIORITY 5普通优先级默认情况下main线程具有普通优先级。 public static void main(String[] args) {Thread t new Thread(){public void run(){System.out.println(getName() 的优先级 getPriority());}};t.setPriority(Thread.MAX_PRIORITY);t.start();System.out.println(Thread.currentThread().getName() 的优先级 Thread.currentThread().getPriority());}9.3.3 常用方法系列2 public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。 public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停暂时停止执行。 public static void yield()yield只是让当前线程暂停一下让系统的线程调度器重新调度一次希望优先级与当前线程相同或更高的其他线程能够获得执行机会但是这个不能保证完全有可能的情况是当某个线程调用了yield方法暂停之后线程调度器又将其调度出来重新执行。 void join() 等待该线程终止。 void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。如果millis时间到将不再等待。 void join(long millis, int nanos) 等待该线程终止的时间最长为 millis 毫秒 nanos 纳秒。 package com.atguigu.api;public class TestThreadStateChange {public static void main(String[] args) {Thread te new Thread() {Overridepublic void run() {for (int i 2; i 100; i 2) {System.out.println(偶数线程 i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};te.start();Thread to new Thread() {Overridepublic void run() {for (int i 1; i 100; i 2) {System.out.println(奇数线程 i);if (i 5) { // Thread.yield();try {te.join();} catch (InterruptedException e) {e.printStackTrace();}}}}};to.start();} }9.3.4 如何让线程提前结束 一个线程如何让另一个线程提前结束呢 线程的死亡有两种 自然死亡当一个线程的run方法执行完线程自然会停止。 意外死亡当一个线程遇到未捕获处理的异常也会挂掉。 我们肯定希望是让线程自然死亡更好。 public final void stop()强迫线程停止执行。 该方法具有固有的不安全性已经标记为Deprecated已过时、已废弃不建议再使用那么我们就需要通过其他方式来停止线程了其中一种方式是使用变量的值的变化来控制线程是否结束。 标记法 案例 声明一个PrintEvenThread线程类继承Thread类重写run方法实现打印[1,100]之间的偶数要求每隔1毫秒打印1个偶数。 声明一个PrintOddThread线程类继承Thread类重写run方法实现打印[1,100]之间的奇数。 在main线程中 1创建两个线程对象并启动两个线程 2当打印奇数的线程结束了让偶数的线程也停下来就算偶数线程没有全部打印完[1,100]之间的偶数。 package com.atguigu.api;public class PrintEvenThread extends Thread{private boolean flag true;Overridepublic void run() {for (int i 2; i 100 flag; i 2) {System.out.println(偶数线程 i);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}public void setFlag(boolean flag) {this.flag flag;} }package com.atguigu.api;public class PrintOddThread extends Thread {Overridepublic void run() {for (int i 1; i 100; i 2) {System.out.println(奇数线程 i);}} }package com.atguigu.api;public class TestThreadStop {public static void main(String[] args) {PrintEvenThread pe new PrintEvenThread();PrintOddThread po new PrintOddThread();pe.start();po.start();try {po.join();} catch (InterruptedException e) {e.printStackTrace();}pe.setFlag(false);} }9.3.5 守护线程了解 有一种线程它是在后台运行的它的任务是为其他线程提供服务的这种线程被称为“守护线程”。JVM的垃圾回收线程就是典型的守护线程。 守护线程有个特点就是如果所有非守护线程都死亡那么守护线程自动死亡。 调用setDaemon(true)方法可将指定线程设置为守护线程。必须在线程启动之前设置否则会报IllegalThreadStateException异常。 调用isDaemon()可以判断线程是否是守护线程。 public class TestThread {public static void main(String[] args) {MyDaemon m new MyDaemon();m.setDaemon(true);m.start();for (int i 1; i 100; i) {System.out.println(main: i);}} }class MyDaemon extends Thread {public void run() {while (true) {System.out.println(我一直守护者你...);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}} }9.4 线程安全 当我们使用多个线程访问同一资源可以是同一个变量、同一个文件、同一条记录等的时候若多个线程只有读操作那么不会发生线程安全问题但是如果多个线程中对资源有读和写的操作就容易出现线程安全问题。 我们通过一个案例演示线程的安全问题 电影院要卖票我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”本次电影的座位共100个 (本场电影只能卖100张票)。 我们来模拟电影院的售票窗口实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票) 9.4.1 同一个资源问题和线程安全问题 1、局部变量不能共享 示例代码 package com.atguigu.unsafe;public class SaleTicketDemo1 {public static void main(String[] args) {Window w1 new Window();Window w2 new Window();Window w3 new Window();w1.start();w2.start();w3.start();} }class Window extends Thread {public void run() {int total 100;while (total 0) {System.out.println(getName() 卖出一张票剩余: --total);}} }结果发现卖出300张票。 问题局部变量是每次调用方法都是独立的那么每个线程的run()的total是独立的不是共享数据。 2、不同对象的实例变量不共享 package com.atguigu.unsafe;public class SaleTicketDemo2 {public static void main(String[] args) {TicketSale t1 new TicketSale();TicketSale t2 new TicketSale();TicketSale t3 new TicketSale();t1.start();t2.start();t3.start();} }class TicketSale extends Thread {private int total 100;public void run() {while (total 0) {System.out.println(getName() 卖出一张票剩余: --total);}} }结果发现卖出300张票。 问题不同的实例对象的实例变量是独立的。 3、静态变量是共享的 示例代码 package com.atguigu.unsafe;public class SaleTicketDemo3 {public static void main(String[] args) {TicketSaleThread t1 new TicketSaleThread();TicketSaleThread t2 new TicketSaleThread();TicketSaleThread t3 new TicketSaleThread();t1.start();t2.start();t3.start();} } class TicketSaleThread extends Thread{private static int total 100;public void run(){while(total0) {try {Thread.sleep(10);//加入这个使得问题暴露的更明显} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() 卖出一张票剩余: --total);}} }运行结果 Thread-1卖出一张票剩余:99 Thread-2卖出一张票剩余:98 Thread-0卖出一张票剩余:99 Thread-2卖出一张票剩余:96 Thread-0卖出一张票剩余:95 Thread-1卖出一张票剩余:97 Thread-1卖出一张票剩余:94 Thread-2卖出一张票剩余:92 Thread-0卖出一张票剩余:93 Thread-1卖出一张票剩余:91 Thread-2卖出一张票剩余:89 Thread-0卖出一张票剩余:90 Thread-0卖出一张票剩余:87 Thread-1卖出一张票剩余:86 Thread-2卖出一张票剩余:88 Thread-2卖出一张票剩余:85 Thread-0卖出一张票剩余:85 Thread-1卖出一张票剩余:84 Thread-1卖出一张票剩余:83 Thread-0卖出一张票剩余:82 Thread-2卖出一张票剩余:81 Thread-1卖出一张票剩余:80 Thread-0卖出一张票剩余:78 Thread-2卖出一张票剩余:79 Thread-1卖出一张票剩余:77 Thread-2卖出一张票剩余:77 Thread-0卖出一张票剩余:77 Thread-1卖出一张票剩余:76 Thread-0卖出一张票剩余:74 Thread-2卖出一张票剩余:75 Thread-1卖出一张票剩余:73 Thread-0卖出一张票剩余:71 Thread-2卖出一张票剩余:72 Thread-2卖出一张票剩余:70 Thread-0卖出一张票剩余:69 Thread-1卖出一张票剩余:68 Thread-2卖出一张票剩余:66 Thread-1卖出一张票剩余:67 Thread-0卖出一张票剩余:65 Thread-1卖出一张票剩余:64 Thread-2卖出一张票剩余:62 Thread-0卖出一张票剩余:63 Thread-0卖出一张票剩余:60 Thread-1卖出一张票剩余:59 Thread-2卖出一张票剩余:61 Thread-1卖出一张票剩余:58 Thread-0卖出一张票剩余:56 Thread-2卖出一张票剩余:57 Thread-2卖出一张票剩余:55 Thread-0卖出一张票剩余:54 Thread-1卖出一张票剩余:53 Thread-2卖出一张票剩余:52 Thread-0卖出一张票剩余:50 Thread-1卖出一张票剩余:51 Thread-2卖出一张票剩余:49 Thread-1卖出一张票剩余:48 Thread-0卖出一张票剩余:48 Thread-2卖出一张票剩余:47 Thread-1卖出一张票剩余:47 Thread-0卖出一张票剩余:46 Thread-2卖出一张票剩余:45 Thread-1卖出一张票剩余:43 Thread-0卖出一张票剩余:44 Thread-1卖出一张票剩余:42 Thread-0卖出一张票剩余:40 Thread-2卖出一张票剩余:41 Thread-1卖出一张票剩余:39 Thread-2卖出一张票剩余:38 Thread-0卖出一张票剩余:37 Thread-2卖出一张票剩余:36 Thread-0卖出一张票剩余:34 Thread-1卖出一张票剩余:35 Thread-2卖出一张票剩余:33 Thread-1卖出一张票剩余:31 Thread-0卖出一张票剩余:32 Thread-1卖出一张票剩余:29 Thread-2卖出一张票剩余:30 Thread-0卖出一张票剩余:30 Thread-2卖出一张票剩余:28 Thread-1卖出一张票剩余:27 Thread-0卖出一张票剩余:26 Thread-2卖出一张票剩余:25 Thread-0卖出一张票剩余:24 Thread-1卖出一张票剩余:23 Thread-2卖出一张票剩余:21 Thread-0卖出一张票剩余:20 Thread-1卖出一张票剩余:22 Thread-2卖出一张票剩余:18 Thread-1卖出一张票剩余:19 Thread-0卖出一张票剩余:18 Thread-2卖出一张票剩余:17 Thread-1卖出一张票剩余:17 Thread-0卖出一张票剩余:16 Thread-1卖出一张票剩余:14 Thread-2卖出一张票剩余:15 Thread-0卖出一张票剩余:13 Thread-2卖出一张票剩余:12 Thread-0卖出一张票剩余:10 Thread-1卖出一张票剩余:11 Thread-1卖出一张票剩余:9 Thread-2卖出一张票剩余:8 Thread-0卖出一张票剩余:7 Thread-0卖出一张票剩余:5 Thread-1卖出一张票剩余:4 Thread-2卖出一张票剩余:6 Thread-2卖出一张票剩余:3 Thread-0卖出一张票剩余:2 Thread-1卖出一张票剩余:1 Thread-1卖出一张票剩余:0 Thread-2卖出一张票剩余:-1 Thread-0卖出一张票剩余:-2结果发现卖出近100张票。 问题1但是有重复票或负数票问题。 原因线程安全问题 问题2如果要考虑有两场电影各卖100张票等 原因TicketThread类的静态变量是所有TicketThread类的对象共享 4、同一个对象的实例变量共享 示例代码多个Thread线程使用同一个Runnable对象 package com.atguigu.safe;package com.atguigu.unsafe;public class SaleTicketDemo4 {public static void main(String[] args) {TicketSaleRunnable tr new TicketSaleRunnable();Thread t1 new Thread(tr, 窗口一);Thread t2 new Thread(tr, 窗口二);Thread t3 new Thread(tr, 窗口三);t1.start();t2.start();t3.start();} }class TicketSaleRunnable implements Runnable {private int total 100;public void run() {while (total 0) {try {Thread.sleep(10);//加入这个使得问题暴露的更明显} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() 卖出一张票剩余: --total);}} }结果发现卖出近100张票。 问题但是有重复票或负数票问题。 原因线程安全问题 5、抽取资源类共享同一个资源对象 示例代码 package com.atguigu.unsafe;public class SaleTicketDemo5 {public static void main(String[] args) {//2、创建资源对象Ticket ticket new Ticket();//3、启动多个线程操作资源类的对象Thread t1 new Thread(窗口一) {public void run() {while (true) {ticket.sale();}}};Thread t2 new Thread(窗口二) {public void run() {while (true) {ticket.sale();}}};Thread t3 new Thread(new Runnable() {public void run() {ticket.sale();}}, 窗口三);t1.start();t2.start();t3.start();} }//1、编写资源类 class Ticket {private int total 100;public void sale() {if (total 0) {try {Thread.sleep(10);//加入这个使得问题暴露的更明显} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() 卖出一张票剩余: --total);} else {throw new RuntimeException(没有票了);}}public int getTotal() {return total;} }结果发现卖出近100张票。 问题但是有重复票或负数票问题。 原因线程安全问题 9.4.2 尝试解决线程安全问题 要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题Java中提供了同步机制 (synchronized)来解决。 根据案例简述 窗口1线程进入操作的时候窗口2和窗口3线程只能在外等着窗口1操作结束窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候其他线程不能修改该资源等待修改完毕同步之后才能去抢夺CPU资源完成对应的操作保证了数据的同步性解决了线程不安全的现象。 为了保证每个线程都能正常执行原子操作Java引入了线程同步机制。注意:在任何时候,最多允许一个线程拥有同步锁谁拿到锁就进入代码块其他的线程只能在外等着(BLOCKED)。 1、同步机制的原理 同步解决线程安全的原理 同步机制的原理其实就相当于给某段代码加“锁”任何线程想要执行这段代码都要先获得“锁”我们称为它同步锁。因为Java对象在堆中的数据分为分为对象头、实例变量、空白的填充。而对象头中包含 Mark Word记录了和当前对象有关的GC、锁标记等信息。指向类的指针每一个对象需要记录它是由哪个类创建出来的。数组长度只有数组对象才有 哪个线程获得了“同步锁”对象之后”同步锁“对象就会记录这个线程的ID这样其他线程就只能等待了除非这个线程”释放“了锁对象其他线程才能重新获得/占用”同步锁“对象。 2、同步代码块和同步方法 同步方法synchronized 关键字直接修饰方法表示同一时刻只有一个线程能进入这个方法其他线程在外面等着。 public synchronized void method(){可能会产生线程安全问题的代码 }同步代码块synchronized 关键字可以用于某个区块前面表示只对这个区块的资源实行互斥访问。 格式: synchronized(同步锁){需要同步操作的代码 }3、同步锁对象的选择 同步锁对象可以是任意类型但是必须保证竞争“同一个共享资源”的多个线程必须使用同一个“同步锁对象”。 对于同步代码块来说同步锁对象是由程序员手动指定的但是对于同步方法来说同步锁对象只能是默认的 静态方法当前类的Class对象 非静态方法this 4、同步代码的范围选择 锁的范围太小不能解决安全问题 锁的范围太大因为一旦某个线程抢到锁其他线程就只能等待所以范围太大效率会降低不能合理利用CPU资源。 5、代码演示 示例一静态方法加锁 package com.atguigu.safe;public class SaleTicketDemo3 {public static void main(String[] args) {TicketSaleThread t1 new TicketSaleThread();TicketSaleThread t2 new TicketSaleThread();TicketSaleThread t3 new TicketSaleThread();t1.start();t2.start();t3.start();} }class TicketSaleThread extends Thread{private static int total 100;public void run(){//直接锁这里肯定不行会导致只有一个窗口卖票while(total0) {saleOneTicket();}}public synchronized static void saleOneTicket(){//锁对象是TicketSaleThread类的Class对象而一个类的Class对象在内存中肯定只有一个if(total 0) {//不加条件相当于条件判断没有进入锁管控线程安全问题就没有解决System.out.println(Thread.currentThread().getName() 卖出一张票剩余: --total);}} } 示例二非静态方法加锁 package com.atguigu.safe;public class SaleTicketDemo4 {public static void main(String[] args) {TicketSaleRunnable tr new TicketSaleRunnable();Thread t1 new Thread(tr, 窗口一);Thread t2 new Thread(tr, 窗口二);Thread t3 new Thread(tr, 窗口三);t1.start();t2.start();t3.start();} }class TicketSaleRunnable implements Runnable {private int total 1000;public void run() {//直接锁这里肯定不行会导致只有一个窗口卖票while (total 0) {saleOneTicket();}}public synchronized void saleOneTicket(){//锁对象是this这里就是TicketSaleRunnable对象因为上面3个线程使用同一个TicketSaleRunnable对象所以可以if(total 0) {//不加条件相当于条件判断没有进入锁管控线程安全问题就没有解决System.out.println(Thread.currentThread().getName() 卖出一张票剩余: --total);}} }示例三同步代码块 package com.atguigu.safe;public class SaleTicketDemo5 {public static void main(String[] args) {//2、创建资源对象Ticket ticket new Ticket();//3、启动多个线程操作资源类的对象Thread t1 new Thread(窗口一) {public void run() {//不能给run()直接假设因为t1,t2,t3的三个run方法分别属于三个Thread类对象// run方法是非静态方法那么锁对象默认选this那么锁对象根本不是同一个while (true) {synchronized (ticket) {ticket.sale();}}}};Thread t2 new Thread(窗口二) {public void run() {while (true) {synchronized (ticket) {ticket.sale();}}}};Thread t3 new Thread(new Runnable() {public void run() {synchronized (ticket) {ticket.sale();}}}, 窗口三);t1.start();t2.start();t3.start();} }//1、编写资源类 class Ticket {private int total 1000;public void sale() {//也可以直接给这个方法加锁锁对象是this这里就是Ticket对象if (total 0) {System.out.println(Thread.currentThread().getName() 卖出一张票剩余: --total);} else {throw new RuntimeException(没有票了);}}public int getTotal() {return total;} }9.4.6 单例设计模式的线程安全问题 1、饿汉式没有线程安全问题 饿汉式在类初始化时就直接创建单例对象而类初始化过程是没有线程安全问题的 形式一 /* public class HungryOne{public static final HungryOne INSTANCE new HungryOne();private HungryOne(){} }*/ public enum HungryOne{INSTANCE }形式二 package com.atguigu.single.hungry;public class HungrySingle {private static final HungrySingle INSTANCE new HungrySingle();private HungrySingle(){}public static HungrySingle getInstance(){return INSTANCE;} } 测试类 package com.atguigu.single.hungry;public class TestHungry {public static void main(String[] args) {HungryOne h1 HungryOne.INSTANCE;HungryOne h2 HungryOne.INSTANCE;System.out.println(h1 h2);System.out.println(----------------------);HungrySingle s1 HungrySingle.getInstance();HungrySingle s2 HungrySingle.getInstance();System.out.println(s1 s2);} } 2、懒汉式线程安全问题 懒汉式延迟创建对象第一次调用getInstance方法再创建对象 形式一 package com.atguigu.single.lazy;public class LazyOne {private static LazyOne instance;private LazyOne(){}public static synchronized LazyOne getInstance(){if(instance null){instance new LazyOne();}return instance;}//有指令重排问题 /* public static LazyOne getInstance(){if(instance null){synchronized (LazyOne.class) {try {Thread.sleep(10);//加这个代码暴露问题} catch (InterruptedException e) {e.printStackTrace();}if(instance null){instance new LazyOne();}}}return instance;}*/ } 形式二 package com.atguigu.single.lazy;public class LazySingle {private LazySingle instance;private LazySingle(){}private static class Inner{static final LazySingle INSTANCE new LazySingle();}public static LazySingle getInstance(){return Inner.INSTANCE;} }测试类 package com.atguigu.single.lazy;import org.junit.Test;public class TestLazy {Testpublic void test01(){LazyOne s1 LazyOne.getInstance();LazyOne s2 LazyOne.getInstance();System.out.println(s1);System.out.println(s2);System.out.println(s1 s2);}//把s1和s2声明在外面是想要在线程的匿名内部类中为s1和s2赋值LazyOne s1;LazyOne s2;Testpublic void test02(){Thread t1 new Thread(){public void run(){s1 LazyOne.getInstance();}};Thread t2 new Thread(){public void run(){s2 LazyOne.getInstance();}};t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(s1);System.out.println(s2);System.out.println(s1 s2);}LazySingle obj1;LazySingle obj2;Testpublic void test03(){Thread t1 new Thread(){public void run(){obj1 LazySingle.getInstance();}};Thread t2 new Thread(){public void run(){obj2 LazySingle.getInstance();}};t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(obj1);System.out.println(obj2);System.out.println(obj1 obj2);} } 9.5 等待唤醒机制 9.5.1 线程间通信 为什么要处理线程间通信 多个线程在处理同一个资源但是处理的动作线程的任务却不相同。而多个线程并发执行时, 在默认情况下CPU是随机切换线程的当我们需要多个线程来共同完成一件任务并且我们希望他们有规律的执行, 那么多线程之间需要一些通信机制可以协调它们的工作以此来帮我们达到多线程共同操作一份数据。 比如线程A用来生成包子的线程B用来吃包子的包子可以理解为同一资源线程A与线程B处理的动作一个是生产一个是消费此时B线程必须等到A线程完成后才能执行那么线程A与线程B之间就需要线程通信即—— 等待唤醒机制。 9.5.2 等待唤醒机制 什么是等待唤醒机制 这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争race比如去争夺锁但这并不是故事的全部线程间也会有协作机制。 就是在一个线程满足某个条件时就进入等待状态wait()/wait(time) 等待其他线程执行完他们的指定代码过后再将其唤醒notify();或可以指定wait的时间等时间到了自动唤醒在有多个线程进行等待时如果需要可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。 wait线程不再活动不再参与调度进入 wait set 中因此不会浪费 CPU 资源也不会去竞争锁了这时的线程状态即是 WAITING或TIMED_WAITING。它还要等着别的线程执行一个特别的动作也即是“通知notify”或者等待时间到在这个对象上等待的线程从wait set 中释放出来重新进入到调度队列ready queue中notify则选取所通知对象的 wait set 中的一个线程释放notifyAll则释放所通知对象的 wait set 上的全部线程。 注意 被通知线程被唤醒后也不一定能立即恢复执行因为它当初中断的地方是在同步块内而此刻它已经不持有锁所以她需要再次尝试去获取锁很可能面临其它线程的竞争成功后才能在当初调用 wait 方法之后的地方恢复执行。 总结如下 如果能获取锁线程就从 WAITING 状态变成 RUNNABLE可运行 状态否则线程就从 WAITING 状态又变成 BLOCKED等待锁 状态 调用wait和notify方法需要注意的细节 wait方法与notify方法必须要由同一个锁对象调用。因为对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。wait方法与notify方法是属于Object类的方法的。因为锁对象可以是任意对象而任意对象的所属类都是继承了Object类的。wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为必须要通过锁对象调用这2个方法。 9.5.3 生产者与消费者问题 等待唤醒机制可以解决经典的“生产者与消费者”的问题。 生产者与消费者问题英语Producer-consumer problem也称有限缓冲问题英语Bounded-buffer problem是一个多线程同步问题的经典案例。该问题描述了两个多个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中然后重复此过程。与此同时消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据消费者也不会在缓冲区中空时消耗数据。 生产者与消费者问题中其实隐含了两个问题 线程安全问题因为生产者与消费者共享数据缓冲区不过这个问题可以使用同步解决。线程的协调工作问题 要解决该问题就必须让生产者线程在缓冲区满时等待(wait)暂停进入阻塞状态等到下次消费者消耗了缓冲区中的数据的时候通知(notify)正在等待的线程恢复到就绪状态重新开始往缓冲区添加数据。同样也可以让消费者线程在缓冲区空时进入等待(wait)暂停进入阻塞状态等到生产者往缓冲区添加数据之后再通知(notify)正在等待的线程恢复到就绪状态。通过这样的通信机制来解决此类问题。 1、一个厨师一个服务员问题 案例有家餐馆的取餐口比较小只能放10份快餐厨师做完快餐放在取餐口的工作台上服务员从这个工作台取出快餐给顾客。现在有1个厨师和1个服务员。 package com.atguigu.thread5;public class TestCommunicate {public static void main(String[] args) {// 1、创建资源类对象Workbench workbench new Workbench();// 2、创建和启动厨师线程new Thread(厨师) {public void run() {while (true) {workbench.put();}}}.start();// 3、创建和启动服务员线程new Thread(服务员) {public void run() {while (true) {workbench.take();}}}.start();}}// 1、定义资源类 class Workbench {private static final int MAX_VALUE 10;private int num;public synchronized void put() {if (num MAX_VALUE) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}num;System.out.println(Thread.currentThread().getName() 制作了一份快餐现在工作台上有 num 份快餐);this.notify();}public synchronized void take() {if (num 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}num--;System.out.println(Thread.currentThread().getName() 取走了一份快餐现在工作台上有 num 份快餐);this.notify();} } 2、多个厨师多个服务员问题 案例有家餐馆的取餐口比较小只能放10份快餐厨师做完快餐放在取餐口的工作台上服务员从这个工作台取出快餐给顾客。现在有多个厨师和多个服务员。 package com.atguigu.thread5;public class TestCommunicate2 {public static void main(String[] args) {// 1、创建资源类对象WindowBoard windowBoard new WindowBoard();// 2、创建和启动厨师线程// 3、创建和启动服务员线程Cook c1 new Cook(张三,windowBoard);Cook c2 new Cook(李四,windowBoard);Waiter w1 new Waiter(小红,windowBoard);Waiter w2 new Waiter(小绿,windowBoard);c1.start();c2.start();w1.start();w2.start();}} //1、定义资源类 class WindowBoard {private static final int MAX_VALUE 10;private int num;public synchronized void put() {while (num MAX_VALUE) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}num;System.out.println(Thread.currentThread().getName() 制作了一份快餐现在工作台上有 num 份快餐);this.notifyAll();}public synchronized void take() {while (num 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}num--;System.out.println(Thread.currentThread().getName() 取走了一份快餐现在工作台上有 num 份快餐);this.notifyAll();} }//2、定义厨师类 class Cook extends Thread{private WindowBoard windowBoard;public Cook(String name,WindowBoard windowBoard) {super(name);this.windowBoard windowBoard;}public void run(){while(true) {windowBoard.put();}} }//3、定义服务员类 class Waiter extends Thread{private WindowBoard windowBoard;public Waiter(String name,WindowBoard windowBoard) {super(name);this.windowBoard windowBoard;}public void run(){while(true) {windowBoard.take();}} }9.6 线程生命周期 9.6.1 观点15种状态JDK1.5之前 简单来说线程的生命周期有五种状态新建New、就绪Runnable、运行Running、阻塞Blocked、死亡Dead。CPU需要在多条线程之间切换于是线程状态会多次在运行、阻塞、就绪之间切换。 1. 新建 当一个Thread类或其子类的对象被声明并创建时新生的线程对象处于新建状。此时它和其他Java对象一样仅仅由JVM为其分配了内存并初始化了实例变量的值。此时的线程对象并没有任何线程的动态特征程序也不会执行它的线程体run()。 2. 就绪 但是当线程对象调用了start()方法之后就不一样了线程就从新建状态转为就绪状态。JVM会为其创建方法调用栈和程序计数器当然处于这个状态中的线程并没有开始运行只是表示已具备了运行的条件随时可以被调度。至于什么时候被调度取决于JVM里线程调度器的调度。 注意 程序只能对新建状态的线程调用start()并且只能调用一次如果对非新建状态的线程如已启动的线程或已死亡的线程调用start()都会报错IllegalThreadStateException异常。 3. 运行 如果处于就绪状态的线程获得了CPU开始执行run()方法的线程体代码则该线程处于运行状态。如果计算机只有一个CPU在任何时刻只有一个线程处于运行状态如果计算机有多个处理器将会有多个线程并行(Parallel)执行。 当然美好的时光总是短暂的而且CPU讲究雨露均沾。对于抢占式策略的系统而言系统会给每个可执行的线程一个小时间段来处理任务当该时间用完系统会剥夺该线程所占用的资源让其回到就绪状态等待下一次被调度。此时其他线程将获得执行机会而在选择下一个线程时系统会适当考虑线程的优先级。 4. 阻塞 当在运行过程中的线程遇到如下情况时线程会进入阻塞状态 线程调用了sleep()方法主动放弃所占用的CPU资源线程试图获取一个同步监视器但该同步监视器正被其他线程持有线程执行过程中同步监视器调用了wait()让它等待某个通知notify线程执行过程中同步监视器调用了wait(time)线程执行过程中遇到了其他线程对象的加塞join线程被调用suspend方法被挂起已过时因为容易发生死锁 当前正在执行的线程被阻塞后其他线程就有机会执行了。针对如上情况当发生如下情况时会解除阻塞让该线程重新进入就绪状态等待线程调度器再次调度它 线程的sleep()时间到线程成功获得了同步监视器线程等到了通知(notify)线程wait的时间到了加塞的线程结束了被挂起的线程又被调用了resume恢复方法已过时因为容易发生死锁 5. 死亡 线程会以以下三种方式之一结束结束后的线程就处于死亡状态 run()方法执行完成线程正常结束线程执行过程中抛出了一个未捕获的异常Exception或错误Error直接调用该线程的stop()来结束该线程已过时因为容易发生死锁 9.6.2 观点26种状态JDK1.5之后 在java.lang.Thread.State的枚举类中这样定义 public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;}首先它没有区分就绪和运行状态因为对于Java对象来说只能标记为可运行至于什么时候运行不是JVM来控制的了是OS来进行调度的而且时间非常短暂因此对于Java对象的状态来说无法区分。只能我们人为的进行想象和理解。 其次根据Thread.State的定义阻塞状态是分为三种的BLOCKED、WAITING、TIMED_WAITING。 BLOCKED是指互有竞争关系的几个线程其中一个线程占有锁对象时其他线程只能等待锁。只有获得锁对象的线程才能有执行机会。TIMED_WAITING当前线程执行过程中遇到Thread类的sleep或joinObject类的waitLockSupport类的park方法并且在调用这些方法时设置了时间那么当前线程会进入TIMED_WAITING直到时间到或被中断。WAITING当前线程执行过程中遇到遇到Object类的waitThread类的joinLockSupport类的park方法并且在调用这些方法时没有指定时间那么当前线程会进入WAITING状态直到被唤醒。 通过Object类的wait进入WAITING状态的要有Object的notify/notifyAll唤醒通过Condition的await进入WAITING状态的要有Conditon的signal方法唤醒通过LockSupport类的park方法进入WAITING状态的要有LockSupport类的unpark方法唤醒通过Thread类的join进入WAITING状态只有调用join方法的线程对象结束才能让当前线程恢复 说明当从WAITING或TIMED_WAITING恢复到Runnable状态时如果发现当前线程没有得到监视器锁那么会立刻转入BLOCKED状态。 9.7 释放锁操作与死锁 任何线程进入同步代码块、同步方法之前必须先获得对同步监视器的锁定那么何时会释放对同步监视器的锁定呢 1、释放锁的操作 当前线程的同步方法、同步代码块执行结束。 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception导致当前线程异常结束。 当前线程在同步代码块、同步方法中执行了锁对象的wait()方法当前线程被挂起并释放锁。 2、不会释放锁的操作 线程执行同步代码块或同步方法时程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行。 线程执行同步代码块时其他线程调用了该线程的suspend()方法将该该线程挂起该线程不会释放锁同步监视器。应尽量避免使用suspend()和resume()这样的过时来控制线程。 3、死锁 不同的线程分别锁住对方需要的同步监视器对象不释放都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁整个程序既不会发生异常也不会给出任何提示只是所有线程处于阻塞状态无法继续。 public class TestDeadLock {public static void main(String[] args) {Object g new Object();Object m new Object();Owner s new Owner(g,m);Customer c new Customer(g,m);new Thread(s).start();new Thread(c).start();} } class Owner implements Runnable{private Object goods;private Object money;public Owner(Object goods, Object money) {super();this.goods goods;this.money money;}Overridepublic void run() {synchronized (goods) {System.out.println(先给钱);synchronized (money) {System.out.println(发货);}}} } class Customer implements Runnable{private Object goods;private Object money;public Customer(Object goods, Object money) {super();this.goods goods;this.money money;}Overridepublic void run() {synchronized (money) {System.out.println(先发货);synchronized (goods) {System.out.println(再给钱);}}} }4、面试题sleep()和wait()方法的区别 1sleep()不释放锁wait()释放锁 2sleep()指定休眠的时间wait()可以指定时间也可以无限等待直到notify或notifyAll 3sleep()在Thread类中声明的静态方法wait方法在Object类中声明 因为我们调用wait方法是由锁对象调用而锁对象的类型是任意类型的对象。那么希望任意类型的对象都要有的方法只能声明在Object类中。
http://www.zqtcl.cn/news/117664/

相关文章:

  • 微信制作网站设计重庆关键词优化软件
  • 网站的设计与应用论文平台推广计划书模板范文
  • 网站备案用户名忘了怎么办网站做301排名会掉
  • 厦门制作网站企业网站子域名怎么做
  • 青岛微网站开发品牌建设青之见
  • 淄博哪有培训做网站的湖南营销型网站建设企业
  • 动物网站建设深圳最好的营销网站建设公司
  • 各种网站制作陕西建设厅证件查询网站
  • 如何提高一个网站如何做简单网站
  • 游戏网站开发找什么人可建智慧园区设计方案
  • 重庆网站设计公司推荐福州移动网站建设
  • 移动网站功能做网站fjfzwl
  • 食品网站建设的目的中级经济师考试成绩查询
  • 普宁建设局网站免费的网站开发平台
  • 网站域名主机空间区别网站上传系统
  • 建设高端网站公司的目的淮南房产网
  • 网站建设 中山网站建设新得体会
  • 快速搭建网站视频教程看想看的做想做的电影网站好
  • 网站聊天怎么做2345网址导航智能主版
  • 如何优化网站加载速度做推广公司
  • 网站下载不了视频php网站 数据库链接
  • 制作网页网站教程wordpress建立扁平化
  • 网站建设小知识郑州网站建设找伟置
  • 苏中建设官方网站旅游做攻略用什么网站好
  • 信息门户网站制作wordpress改商城
  • 企业类网站有哪些甘肃省和住房建设厅网站
  • 嘉兴市住房和城乡建设局网站wordpress nodejs版本
  • 做网站 百度推广深圳外贸招聘
  • 网站留言板功能网站建设 核对流程
  • WordPress输出当前网址郑州官网seo厂家