1 package cn.temptation; 2 3 public class Sample01 { 4 public static void main(String[] args) { 5 /* 6 * 【进程】:正在运行的程序,系统进行资源分配和调用的独立单位 7 * 每一个进程有自己的内存空间和系统资源 8 * 9 * 主流操作系统均为多任务操作系统,可以同时执行多个应用程序10 * 以Window为例,通过任务管理器可以看到进程中多个正在运行的程序,即系统的多个进程11 * 12 * 多任务操作系统表面上看起来支持多进程并发执行,例如:一边听歌一边打游戏13 * 实际上不是同时执行,这些应用程序均由CPU进行执行,CPU的一个核在某一时刻只能运行一个程序,14 * 即某一个时刻(时间片)只能执行一个进程,下一个时刻(时间片)切换到另一个进程的执行15 * 因为CPU执行速度非常快,在很短的时间内在不同进程之间进行切换,给使用者造成一种同时执行多个程序的错觉16 * 17 * 多进程的意义:操作系统支持多进程,没有提高执行速度,而是提高了CPU的使用率18 * 19 * 【线程】:进程中的某个顺序控制流,是一条执行路径,是程序使用CPU的基本单位20 * 21 * 线程必须依赖于进程存在22 * 在一个进程内部可以执行多个任务,每一个任务可以看成一个线程23 * 比如:Windows自带的扫雷游戏,游戏计时是一个任务,在界面上扫雷也是一个任务,这就是多任务(多线程)24 * 25 * 进程中至少有一个线程26 * 27 * 如果一个进程中只有一个线程(执行路径、执行任务),称为单线程程序28 * 理解:类比,独唱(清唱)29 * 如果一个进程中有多个线程(执行路径、执行任务),称为多线程程序30 * 理解:类别,音乐会上的乐团演奏31 * 32 * 多线程的意义:没有提高执行速度,而是提高了应用程序的使用率33 * 34 * 注意:35 * 1、多个线程共享一个进程的资源(堆内存和方法区)36 * 2、对于栈内存是独立的,一个线程一个栈37 * 3、进程中的多个线程抢夺CPU的资源执行,某一时间点上只能有一个线程执行,且哪个线程能抢到是随机的38 * 39 * Java程序执行时,会产生一个进程,该进程默认创建一个线程,在这个线程上运行main主函数40 * 41 * Java语言中对线程的操作 提供了 Thread类 以及 Runnable接口42 * 43 * 类Thread:线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。44 * 45 * Java程序运行原理:Java命令启动Java虚拟机(JVM),启动JVM相当于启动了一个应用程序,也就是启动了一个进程46 * 该进程会启动一个主线程,在主线程中调用某一个类的main主函数(程序的入口)47 * 考虑JVM启动后,伴随着主线程的启动,也启动了GC垃圾回收线程,JVM启动也是多线程的48 */49 50 Thread01 thread01 = new Thread01();51 thread01.run();52 53 for (int i = 0; i < 10; i++) {54 System.out.println("主函数中的i:" + i);55 }56 57 // 执行结果可以看出,Thread01类的实例对象的run方法被执行10次后,主函数的循环才开始执行,此时的程序还是单线程程序58 }59 }60 61 class Thread01 {62 public void run() {63 for (int i = 0; i < 10; i++) {64 System.out.println("成员方法中的i:" + i);65 }66 }67 }
1 package cn.temptation; 2 3 public class Sample02 { 4 public static void main(String[] args) { 5 /* 6 * 从JDK的API手册中得知: 7 * 创建新执行线程有两种方法。 8 * 一种方法是将类声明为Thread的子类。该子类应重写 Thread类的run方法。接下来可以分配并启动该子类的实例。 9 * 另一种方法是声明实现Runnable接口的类。该类然后实现 run方法。然后可以分配该类的实例,在创建 Thread时作为一个参数来传递并启动。10 * 11 * 创建线程的方式1、继承Thread类12 * 线程对象的启动,不是通过调用线程对象的run方法,而是使用start方法来启动线程对象13 * 14 * Thread类的常用成员方法:15 * 1、void run():如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。16 * 2、void start():使该线程开始执行;Java虚拟机调用该线程的run方法。 17 * 18 * 问题:如何理解线程对象的启动是通过start方法,而不是直接调用run方法?19 * 答:Java程序执行就是一个进程启动了,至少启动了一个线程,即主函数所在的主线程20 * 这个主线程是由JVM进行启动的,主线程的start方法相当于通知JVM的线程规划器,主线程已经准备好了,正在等待CPU调用该线程的run方法21 * 而创建的其他线程也和主线程一样,它们的start方法相当于通知JVM的线程规划器,该线程已经准备好了,正在等待CPU调用该线程的run方法22 * 只是其他线程的创建恰好写在主线程的主函数之中了23 */24 25 // 创建Thread02类的实例对象26 Thread02 thread02 = new Thread02();27 // 下句语句对于多线程程序的启动,写法错误28 // thread02.run();29 // 正确写法:主线程被JVM调用,其他线程也被JVM调用,这样的执行才是多线程程序的执行30 thread02.start();31 32 // 多次调用线程对象的start()方法会产生执行异常33 // 执行异常:java.lang.IllegalThreadStateException,指示线程没有处于请求操作所要求的适当状态时抛出的异常。34 thread02.start();35 36 for (int i = 0; i < 10; i++) {37 System.out.println("主线程的主函数中的i:" + i);38 }39 }40 }41 42 // 创建线程类43 class Thread02 extends Thread {44 // 重写Thread类的run方法45 @Override46 public void run() {47 for (int i = 0; i < 10; i++) {48 System.out.println("线程类中的成员方法中的i:" + i);49 }50 }51 52 // 从执行结果可以清楚的看到,线程对象启动时,自定义的其他成员方法不会被调用53 public void method() {54 System.out.println("线程启动时,会调用我么?");55 }56 }
1 package cn.temptation; 2 3 public class Sample03 { 4 public static void main(String[] args) { 5 /* 6 * Thread类的常用成员方法: 7 * 1、String getName():返回该线程的名称。 8 * 2、static Thread currentThread():返回对当前正在执行的线程对象的引用。 9 */10 11 Thread03 thread03 = new Thread03();12 thread03.start();13 14 System.out.println("-----------------");15 16 Thread03Ex thread03Ex = new Thread03Ex();17 thread03Ex.start();18 19 System.out.println("-----------------");20 21 // 语法错误:The method getName() is undefined for the type Sample0322 // System.out.println("主线程的线程名称为:" + getName());23 24 System.out.println("主线程的线程为:" + Thread.currentThread()); // 主线程的线程为:Thread[main,5,main]25 System.out.println("主线程的线程名称为:" + Thread.currentThread().getName()); // 主线程的线程名称为:main26 // 语法错误:Cannot use this in a static context27 // System.out.println(this.getName());28 }29 }30 31 class Thread03 extends Thread {32 @Override33 public void run() {34 System.out.println(getName()); // Thread-035 System.out.println("线程对象为:" + Thread.currentThread()); // 线程对象为:Thread[Thread-0,5,main]36 System.out.println("线程对象的线程名称为:" + Thread.currentThread().getName()); // 线程对象的线程名称为:Thread-037 System.out.println("this指向的当前对象为:" + this); // this指向的当前对象为:Thread[Thread-0,5,main]38 System.out.println("线程对象的线程名称为:" + this.getName()); // 线程对象的线程名称为:Thread-039 }40 }41 42 class Thread03Ex extends Thread {43 @Override44 public void run() {45 System.out.println(getName()); // Thread-146 System.out.println("线程对象为:" + Thread.currentThread()); // 线程对象为:Thread[Thread-1,5,main]47 System.out.println("线程对象的线程名称为:" + Thread.currentThread().getName()); // 线程对象的线程名称为:Thread-148 System.out.println("this指向的当前对象为:" + this); // this指向的当前对象为:Thread[Thread-1,5,main]49 System.out.println("线程对象的线程名称为:" + this.getName()); // 线程对象的线程名称为:Thread-150 }51 }52 53 // 查看Thread类的getName()方法的源码54 //public final String getName() {55 // return name;56 //}57 58 //public Thread() {59 // init(null, null, "Thread-" + nextThreadNum(), 0);60 //}61 62 //private void init(ThreadGroup g, Runnable target, String name,63 // long stackSize) {64 // init(g, target, name, stackSize, null, true);65 //}66 67 //private void init(ThreadGroup g, Runnable target, String name,68 // long stackSize, AccessControlContext acc,69 // boolean inheritThreadLocals) {70 // if (name == null) {71 // throw new NullPointerException("name cannot be null");72 // }73 // 74 // this.name = name;75 // 76 // ...77 //}78 79 //private static int threadInitNumber; // 静态的成员变量 --- 类变量(对象们的变量)80 //private static synchronized int nextThreadNum() {81 // return threadInitNumber++;82 //}
1 package cn.temptation; 2 3 public class Sample04 { 4 public static void main(String[] args) { 5 /* 6 * Thread类的常用构造函数: 7 * Thread(String name):分配新的 Thread 对象。 8 * 9 * Thread类的常用成员方法:10 * void setName(String name):改变线程名称,使之与参数 name 相同。 11 */12 13 // 使用Thread类的构造函数设置线程对象的名称14 Thread04 thread04 = new Thread04("自定义线程");15 thread04.start();16 System.out.println(thread04.getName()); // 自定义线程17 18 Thread04Ex thread04Ex = new Thread04Ex();19 // thread04Ex.setName("又一个自定义线程");20 // thread04Ex.start();21 22 // 问题:如果将19行和20行语句颠倒,是否执行出错?23 // 答:不会出错,和之前的效果一致24 thread04Ex.start();25 thread04Ex.setName("又一个自定义线程");26 }27 }28 29 class Thread04 extends Thread {30 // 构造函数(无参)31 public Thread04() {32 super();33 }34 35 // 构造函数(有参)36 public Thread04(String name) {37 super(name);38 }39 }40 41 class Thread04Ex extends Thread {42 @Override43 public void run() {44 System.out.println("当前线程的名称为:" + Thread.currentThread().getName()); // 当前线程的名称为:又一个自定义线程45 }46 }
1 package cn.temptation; 2 3 public class Sample05 { 4 public static void main(String[] args) { 5 /* 6 * Thread类的常用成员方法: 7 * static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 8 */ 9 // 注意:第10行的语句和第15行、16行语句无法测量出另一个线程执行耗时,因为是多线程抢占CPU资源10 // long start = System.currentTimeMillis();11 12 Thread05 thread05 = new Thread05();13 thread05.start();14 15 // long end = System.currentTimeMillis();16 // System.out.println("程序执行耗时:" + (end - start) + "秒");17 18 Thread05Ex thread05Ex = new Thread05Ex();19 thread05Ex.start();20 21 // 执行结果:22 // 线程Thread05开始执行...23 // 线程Thread05Ex开始执行...24 // 线程Thread05休眠1秒后,再执行...25 // 程序执行耗时:1000毫秒26 // 线程Thread05Ex休眠5秒后,再执行...27 28 // 可以清楚的得知,线程在休眠时,不会独占CPU资源,且不会丢失对当前线程的监视,等到休眠结束时又开始执行29 }30 }31 32 class Thread05 extends Thread {33 @Override34 public void run() {35 long start = System.currentTimeMillis();36 37 System.out.println("线程Thread05开始执行...");38 39 try {40 // 设置线程休眠1秒41 Thread.sleep(1000);42 } catch (InterruptedException e) {43 e.printStackTrace();44 }45 46 System.out.println("线程Thread05休眠1秒后,再执行...");47 48 long end = System.currentTimeMillis();49 System.out.println("程序执行耗时:" + (end - start) + "毫秒");50 }51 }52 53 class Thread05Ex extends Thread {54 @Override55 public void run() {56 System.out.println("线程Thread05Ex开始执行...");57 58 try {59 // 设置线程休眠5秒60 Thread.sleep(5000);61 } catch (InterruptedException e) {62 e.printStackTrace();63 }64 65 System.out.println("线程Thread05Ex休眠5秒后,再执行...");66 }67 }
1 package cn.temptation; 2 3 public class Sample06 { 4 public static void main(String[] args) { 5 /* 6 * Thread类的常用成员方法: 7 * boolean isAlive():测试线程是否处于活动状态。 8 */ 9 Thread06 thread06 = new Thread06();10 System.out.println("在线程对象的start方法调用之前,自定义线程的活动状态为:" + thread06.isAlive());11 // 在线程对象的start方法调用之前,自定义线程的活动状态为:false12 thread06.start();13 14 // 在自定义线程启动后,让主线程休眠10秒,等待自定义线程的操作都完成再观察自定义线程的活动状态15 try {16 // 下句语句想在自定义线程启动后且开始休眠时观察自定义线程的活动状态,但是不太准确17 // System.out.println(thread06.isAlive());18 Thread.sleep(10000);19 } catch (InterruptedException e) {20 e.printStackTrace();21 }22 23 System.out.println("在线程对象的start方法调用之后,自定义线程的活动状态为:" + thread06.isAlive());24 // 在线程对象的start方法调用之后,自定义线程的活动状态为:false25 }26 }27 28 class Thread06 extends Thread {29 @Override30 public void run() {31 System.out.println("自定义线程的活动状态为:" + isAlive()); // 自定义线程的活动状态为:true32 33 try {34 Thread.sleep(5000);35 // 下句语句其实看不到效果,因为此时安静的等待线程休眠结束36 // System.out.println("执行线程休眠后,自定义线程的活动状态为:" + isAlive()); 37 } catch (InterruptedException e) {38 e.printStackTrace();39 }40 41 System.out.println("自定义线程结束休眠后,自定义线程的活动状态为:" + isAlive());42 }43 }
1 package cn.temptation; 2 3 public class Sample07 { 4 public static void main(String[] args) { 5 /* 6 * Thread类的常用成员方法: 7 * void join():等待该线程终止。 8 */ 9 Thread07 thread07 = new Thread07();10 thread07.start();11 12 // 现象:下面的语句块如果不写,随机实心五角星和空心五角星交替呈现;写后,实心五角星全部先呈现,空心五角星再呈现13 try {14 // 下句语句表示等待Thread07线程对象运行终止后,后续的线程对象才开始执行15 thread07.join();16 } catch (InterruptedException e) {17 e.printStackTrace();18 }19 20 Thread07Ex thread07Ex = new Thread07Ex();21 thread07Ex.start();22 }23 }24 25 class Thread07 extends Thread {26 @Override27 public void run() {28 for (int i = 0; i < 20; i++) {29 System.out.println("★");30 }31 }32 }33 34 class Thread07Ex extends Thread {35 @Override36 public void run() {37 for (int j = 0; j < 20; j++) {38 System.out.println("☆");39 }40 }41 }
1 package cn.temptation; 2 3 public class Sample08 { 4 public static void main(String[] args) { 5 Thread08 thread08 = new Thread08(); 6 thread08.start(); 7 8 // 注意: 9 // 非主线程对象的静态代码块、构造代码块、构造函数均被main主线程调用,只有成员方法的run()方法被非主线程对象自己调用10 // 理解:非主线程对象创建时靠主线程调用,非主线程对象启动时靠线程对象自身11 }12 }13 14 class Thread08 extends Thread {15 // 静态代码块16 static {17 System.out.println("静态代码块的使用,对应的线程名称:" + Thread.currentThread().getName()); // 静态代码块的使用,对应的线程名称:main18 System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[main,5,main]19 // 语法错误:Cannot use this in a static context20 // System.out.println(this);21 }22 23 // 构造代码块24 {25 System.out.println("构造代码块的使用,对应的线程名称:" + Thread.currentThread().getName()); // 构造代码块的使用,对应的线程名称:main26 System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[main,5,main]27 System.out.println("当前对象:" + this); // 当前对象:Thread[Thread-0,5,main]28 }29 30 // 构造函数31 public Thread08() {32 System.out.println("构造函数的使用,对应的线程名称:" + Thread.currentThread().getName()); // 构造函数的使用,对应的线程名称:main33 System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[main,5,main]34 System.out.println("当前对象:" + this); // 当前对象:Thread[Thread-0,5,main]35 }36 37 // 成员方法38 @Override39 public void run() {40 System.out.println("成员方法run()的使用,对应的线程名称:" + Thread.currentThread().getName()); // 成员方法run()的使用,对应的线程名称:Thread-041 System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[Thread-0,5,main]42 System.out.println("当前对象:" + this); // 当前对象:Thread[Thread-0,5,main]43 }44 }
1 package cn.temptation; 2 3 public class Sample09 { 4 public static void main(String[] args) { 5 /* 6 * 创建线程的方式2、实现Runnable接口(推荐) 7 * 8 * 接口 Runnable:应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run的无参数方法。 9 * 设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。10 * Runnable为非 Thread 子类的类提供了一种激活方式。11 * 通过实例化某个 Thread 实例并将自身作为运行目标,就可以运行实现 Runnable 的类而无需创建 Thread 的子类。12 * 大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。13 * 这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。14 * 15 * 16 * Runnable接口的常用成员方法:17 * void run():使用实现接口 Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。 18 * 19 * Thread类的常用构造函数:20 * Thread(Runnable target):分配新的 Thread 对象。21 * 22 * 注意:采用实现Runnable接口方式的线程对象创建方法在线程对象启动时,不需要直接调用线程对象的run方法,23 * 借助于Thread类对象的构造函数以及start方法24 * 25 * 优点:26 * 1、将线程任务(实现Runnable接口的类)从线程类中分离出来,单独封装,这是面向对象思想的体现27 * 2、因为Java的类是单继承的,为了避免这种继承的局限,通过接口提高了操作的灵活性28 */29 30 Thread09 thread09 = new Thread09();31 // 下句语句写法错误,这样写,还是单线程程序32 // thread09.run();33 // 正确写法:实现了Runnable接口的线程对象,需要借助于Thread类34 Thread thread = new Thread(thread09);35 thread.start();36 37 for (int i = 0; i < 10; i++) {38 System.out.println("主线程的主函数中的i:" + i);39 }40 }41 }42 43 class Thread09 implements Runnable {44 @Override45 public void run() {46 System.out.println("自定义线程对象启动了...");47 }48 }
1 package cn.temptation; 2 3 public class Sample10 { 4 public static void main(String[] args) { 5 // 思考创建线程的方式1 和 创建线程的方式2 6 7 // 形式1 8 // Thread10 thread10 = new Thread10(); 9 // thread10.start();10 // 11 // (new Thread10()).start();12 13 // (new Thread() {14 // @Override15 // public void run() {16 // System.out.println("通过匿名对象创建自定义线程");17 // }18 // }).start(); // 通过匿名对象创建自定义线程19 20 System.out.println("----------------------");21 22 // 形式223 // Thread10Ex thread10Ex = new Thread10Ex();24 // Thread thread = new Thread(thread10Ex);25 // thread.start();26 27 // Thread thread = new Thread(new Thread10Ex());28 // thread.start();29 30 // (new Thread(new Thread10Ex())).start();31 32 // (new Thread(new Runnable() {33 // @Override34 // public void run() {35 // System.out.println("通过匿名内部类创建自定义线程");36 // }37 // })).start(); // 通过匿名内部类创建自定义线程38 39 System.out.println("----------------------");40 41 // 混合形式1 和 形式242 (new Thread(new Runnable() {43 @Override44 public void run() {45 System.out.println("通过匿名内部类创建自定义线程");46 }47 }) {48 @Override49 public void run() {50 System.out.println("通过匿名对象创建自定义线程");51 }52 }).start(); // 通过匿名对象创建自定义线程53 }54 }55 56 class Thread10 extends Thread {57 @Override58 public void run() {59 System.out.println("自定义线程对象");60 }61 }62 63 class Thread10Ex implements Runnable {64 @Override65 public void run() {66 System.out.println("自定义线程对象");67 }68 }
1 package cn.temptation; 2 3 public class Sample11 { 4 public static void main(String[] args) { 5 // 需求:模拟三个窗口售卖20张票(使用创建方式1) 6 7 (new Thread11("窗口1")).start(); 8 (new Thread11("窗口2")).start(); 9 (new Thread11("窗口3")).start();10 11 // 从执行结果(使用局部变量的写法)可以看出,三个窗口(三个线程)各自售卖20张票,自己卖自己的12 // 即这三个线程没有共享20张票13 14 // 从执行结果(使用静态成员变量的写法)可以看出,三个窗口(三个线程)共同售卖20张票15 // 即这三个线程共享20张票16 }17 }18 19 class Thread11 extends Thread {20 // 成员变量:非静态的成员变量是属于对象的,无法做到不同对象间的共享21 // private int tickets = 20;22 // 成员变量:静态的成员变量是属于对象们(类)的,可以做到不同对象间的共享23 private static int tickets = 20;24 25 // 构造函数26 public Thread11() {27 super();28 }29 30 public Thread11(String name) {31 super(name);32 }33 34 // 成员方法35 @Override36 public void run() {37 // 局部变量:局部变量作用范围在方法中,使用时随着方法的调用而产生,无法做到不同对象间的共享38 // int tickets = 20;39 40 while (tickets > 0) {41 System.out.println(Thread.currentThread().getName() + "正在售卖" + (tickets--) + "张票");42 }43 }44 }
1 package cn.temptation; 2 3 public class Sample12 { 4 public static void main(String[] args) { 5 // 需求:模拟三个窗口售卖20张票(使用创建方式2) 6 7 /* 8 * Thread类的常用构造函数: 9 * Thread(Runnable target, String name):分配新的 Thread 对象。10 */11 12 Thread12 thread12 = new Thread12();13 14 (new Thread(thread12, "窗口1")).start();15 (new Thread(thread12, "窗口2")).start();16 (new Thread(thread12, "窗口3")).start();17 18 // 对比上一个例子 和 这个例子19 // 显然,上一个例子start()方法调用的是new创建的线程对象自身的run()方法20 // 这个例子start()方法调用的是构造函数传入的目标线程对象的run()方法21 }22 }23 24 class Thread12 implements Runnable {25 // 成员变量:因为三个线程对象调用的是同一个线程任务对象的run方法,所以这里实际就是用的这一个对象的成员变量,看起来像是被共享26 private int tickets = 20;27 28 @Override29 public void run() {30 // 局部变量31 // int tickets = 20;32 33 while (tickets > 0) {34 System.out.println(Thread.currentThread().getName() + "正在售卖" + (tickets--) + "张票");35 }36 }37 }
1 package cn.temptation; 2 3 public class Sample13 { 4 public static void main(String[] args) { 5 // 需求:模拟三个窗口售卖20张票(使用Thread类的继承子类按给Thread类的构造函数传入Runnable类型的参数的方式使用) 6 7 Thread13 thread13 = new Thread13(); 8 9 (new Thread(thread13, "窗口1")).start();10 (new Thread(thread13, "窗口2")).start();11 (new Thread(thread13, "窗口3")).start();12 13 // 注意:14 // 这样的写法,功能也可以实现,但是很别扭15 // 对比这三个例子,Thread11对象具有start()方法,可以自己启动16 // Thread12对象不具有start方法,需要借助于Thread类的对象启动来调用它的run()方法17 // Thread13对象具有start()方法,但是自己不启动,却借助于Thread类的对象启动来调用它的run()方法,明显多此一举18 }19 }20 21 class Thread13 extends Thread {22 // 成员变量23 private int tickets = 20;24 25 // 构造函数26 public Thread13() {27 super();28 }29 30 public Thread13(String name) {31 super(name);32 }33 34 // 成员方法35 @Override36 public void run() {37 // 局部变量38 // int tickets = 20;39 40 while (tickets > 0) {41 System.out.println(Thread.currentThread().getName() + "正在售卖" + (tickets--) + "张票");42 }43 }44 }
1 package cn.temptation; 2 3 public class Sample14 { 4 public static void main(String[] args) { 5 /* 6 * 守护线程(后台线程):程序运行时在后台提供一种通用服务的线程,且这种线程不属于程序中不可获取的部分 7 * 例如:Java中的GC线程 8 * 9 * 创建出的线程默认是应用线程(前台线程),Java程序中只要有应用线程(前台线程)在运行,进程就不会结束;10 * 反之,如果只有后台线程运行,这个进程就会结束11 * 12 * Thread类的常用成员方法:13 * 1、boolean isDaemon():测试该线程是否为守护线程。14 * 2、void setDaemon(boolean on):将该线程标记为守护线程或用户线程。 15 */16 17 System.out.println("主线程是后台线程嘛?" + Thread.currentThread().isDaemon()); // 主线程是后台线程嘛?false18 19 Thread14 thread14 = new Thread14();20 Thread thread = new Thread(thread14, "自定义线程");21 System.out.println("自定义线程是后台线程嘛?" + thread.isDaemon()); // 自定义线程是后台线程嘛?false22 // 下句语句如果注释掉,自定义线程的run方法中一直执行死循环23 // 下句语句不注释,自定义线程的run方法会执行若干条语句,然后整个进程结束24 thread.setDaemon(true);25 System.out.println("自定义线程是后台线程嘛?" + thread.isDaemon()); // 自定义线程是后台线程嘛?true26 thread.start();27 28 // 注意:setDaemon方法在start方法之后进行设置,会产生执行异常29 // 执行异常:java.lang.IllegalThreadStateException30 // thread.setDaemon(true);31 }32 }33 34 class Thread14 implements Runnable {35 @Override36 public void run() {37 while (true) {38 System.out.println("自定义线程的run方法");39 }40 }41 }
1 package cn.temptation; 2 3 public class Sample15 { 4 public static void main(String[] args) { 5 /* 6 * 线程的生命周期:(五个状态,两种流程) 7 * 1、新建状态(new):创建一个线程后,这个线程就出于新建状态,此时该线程对象不能运行,由JVM为其分配内容 8 * 2、就绪状态(runnable):线程对象调用start()方法,该线程对象就进入就绪状态,此时具备了运行条件,是否运行等系统调用 9 * 3、运行状态(running):处于就绪状态的线程对象获取CPU使用权,开始执行run()方法中的语句,此时该线程对象就处于运行状态10 * 4、阻塞状态(blocked):线程对象在某些特殊情况下,比如执行耗时的输入输出操作时,会放弃CPU使用权,进入阻塞状态11 * 当引起阻塞原因被消除后,线程对象才可以转为就绪状态12 * 5、死亡状态(terminated):线程对象执行完毕后,进入死亡状态,此时线程对象不能运行,也不能转换为其他状态13 * 14 * 两种流程:15 * 1、新建 -----> 就绪 -----> 运行 -----> 死亡16 * 2、新建 -----> 就绪 -----> 运行 -----> 阻塞 -----> 就绪 -----> 运行 -----> 死亡 17 */18 }19 }
1 package cn.temptation; 2 3 public class Sample16 { 4 public static void main(String[] args) { 5 /* 6 * 线程的优先级:用1~10之间的整数表示,数字越大表示优先级越高 7 * 8 * Thread类的常用字段: 9 * static int MAX_PRIORITY:线程可以具有的最高优先级。 1010 * static int MIN_PRIORITY:线程可以具有的最低优先级。 111 * static int NORM_PRIORITY:分配给线程的默认优先级。 512 * 13 * Thread类的常用成员方法:14 * void setPriority(int newPriority):更改线程的优先级。 15 * 16 * 注意:17 * 只能把线程优先级作为提高线程执行概率的手段,无法依赖线程优先级的设置18 */19 20 Thread threadMax = new Thread(new Thread16(), "线程优先级最高 ★");21 Thread threadMin = new Thread(new Thread16(), "线程优先级最低 ☆");22 23 threadMax.setPriority(Thread.MAX_PRIORITY);24 threadMin.setPriority(Thread.MIN_PRIORITY);25 26 threadMax.start();27 threadMin.start();28 }29 }30 31 class Thread16 implements Runnable {32 @Override33 public void run() {34 for (int i = 0; i < 10; i++) {35 System.out.println(Thread.currentThread().getName());36 }37 }38 }
1 package cn.temptation; 2 3 public class Sample17 { 4 public static void main(String[] args) { 5 /* 6 * 线程让步:让当前正在运行的线程暂停 7 * 8 * Thread类的常用成员方法: 9 * static void yield():暂停当前正在执行的线程对象,并执行其他线程。10 * 这里的暂停会放弃CPU资源,且放弃CPU资源的时间不确定,可能刚放弃就又获得CPU资源,也可能放弃了一会儿才重新获取CPU资源 11 * 12 * 线程让步 和 线程休眠的区别:13 * 相同点:线程让步 和 线程休眠都可以让当前正在运行的线程暂停14 * 不同点:15 * 线程让步:不会阻塞线程,只能让线程的状态转换为就绪状态,让CPU重新调度16 * 线程休眠:会阻塞线程,将线程状态转换为阻塞状态17 */18 19 Thread17 thread17 = new Thread17();20 21 Thread thread = new Thread(thread17, "自定义线程");22 thread.start();23 24 for (int i = 0; i < 20; i++) {25 System.out.println("主线程中的i:" + i);26 }27 }28 }29 30 class Thread17 implements Runnable {31 @Override32 public void run() {33 for (int j = 0; j < 20; j++) {34 System.out.println(Thread.currentThread().getName() + "的j:" + j);35 if (j == 10) {36 Thread.yield();37 }38 }39 }40 }
1 package cn.temptation; 2 3 public class Sample18 { 4 public static void main(String[] args) { 5 /* 6 * 线程安全:包括变量安全 和 线程同步 两个方面 7 * 8 * 1、变量安全: 9 * 进程中多个线程同时运行,这些线程可能会在某一时刻同时运行某一段代码,10 * 如果每次运行结果和单线程运行的结果一致,且其他变量的值也和预期一样,则称为变量安全;否则就是变量不安全11 */12 13 // 变量安全:14 // 问题:静态的成员变量是线程安全的么?15 // 答:不论单例还是多例,多线程中的静态的成员变量都是不安全16 // 因为静态的成员变量就是对象们的变量(类变量),为所有对象共享,共享一份内存,一旦静态成员变量被修改,其他对象均对修改可见17 Thread18 thread18 = new Thread18();18 19 for (int i = 0; i < 1000; i++) {20 // 调用run()方法所在的线程任务对象是单例的21 // (new Thread(thread18, "自定义线程")).start();22 23 // 调用run()方法所在的线程任务对象是多例的24 (new Thread(new Thread18(), "自定义线程")).start();25 }26 27 // 执行结果,如果线程安全,应该显示:28 // 自定义线程获取 i 的值为:329 // 自定义线程获取 i * 10 的值为:5030 31 // 实际得到的结果中,有32 // 自定义线程获取 i 的值为:533 // 自定义线程获取 i * 10 的值为:3034 }35 }36 37 class Thread18 implements Runnable {38 // 静态成员变量39 private static int i = 2;40 41 @Override42 public void run() {43 i = 3;44 System.out.println(Thread.currentThread().getName() + "获取 i 的值为:" + i);45 i = 5;46 System.out.println(Thread.currentThread().getName() + "获取 i * 10 的值为:" + i * 10);47 }48 }
1 package cn.temptation; 2 3 public class Sample19 { 4 public static void main(String[] args) { 5 /* 6 * 线程安全:包括变量安全 和 线程同步 两个方面 7 * 8 * 1、变量安全: 9 * 进程中多个线程同时运行,这些线程可能会在某一时刻同时运行某一段代码,10 * 如果每次运行结果和单线程运行的结果一致,且其他变量的值也和预期一样,则称为变量安全;否则就是变量不安全11 */12 13 // 变量安全:14 // 问题:非静态的成员变量是线程安全的么?15 // 答:多线程中的非静态成员变量对于单例调用时,线程不安全;对于多例调用时,线程安全16 // 因为单例时,每个线程都在修改同一个线程任务对象的成员变量;多例时,每个线程都在修改各自不同的线程任务对象的成员变量17 Thread19 thread19 = new Thread19();18 19 for (int i = 0; i < 1000; i++) {20 // 调用run()方法所在的线程任务对象是单例的21 // (new Thread(thread19, "自定义线程")).start();22 23 // 调用run()方法所在的线程任务对象是多例的24 (new Thread(new Thread19(), "自定义线程")).start();25 }26 27 // 执行结果,如果线程安全,应该显示:28 // 自定义线程获取 i 的值为:329 // 自定义线程获取 i * 10 的值为:5030 31 // 实际得到的结果中,有32 // 自定义线程获取 i 的值为:533 // 自定义线程获取 i * 10 的值为:3034 }35 }36 37 class Thread19 implements Runnable {38 // 非静态成员变量39 private int i = 2;40 41 @Override42 public void run() {43 i = 3;44 System.out.println(Thread.currentThread().getName() + "获取 i 的值为:" + i);45 i = 5;46 System.out.println(Thread.currentThread().getName() + "获取 i * 10 的值为:" + i * 10);47 }48 }
1 package cn.temptation; 2 3 public class Sample20 { 4 public static void main(String[] args) { 5 /* 6 * 线程安全:包括变量安全 和 线程同步 两个方面 7 * 8 * 1、变量安全: 9 * 进程中多个线程同时运行,这些线程可能会在某一时刻同时运行某一段代码,10 * 如果每次运行结果和单线程运行的结果一致,且其他变量的值也和预期一样,则称为变量安全;否则就是变量不安全11 */12 13 // 变量安全:14 // 问题:局部变量是线程安全的么?15 // 答:不论单例还是多例,多线程中的局部变量都是安全16 // 因为每个线程执行时都会把局部变量放在各自栈的工作内存中,线程之间不共享局部变量的,所以不存在变量安全的问题17 Thread20 thread20 = new Thread20();18 19 for (int i = 0; i < 1000; i++) {20 // 调用run()方法所在的线程任务对象是单例的21 // (new Thread(thread20, "自定义线程")).start();22 23 // 调用run()方法所在的线程任务对象是多例的24 (new Thread(new Thread19(), "自定义线程")).start();25 }26 }27 }28 29 class Thread20 implements Runnable {30 @Override31 public void run() {32 int i = 3;33 System.out.println(Thread.currentThread().getName() + "获取 i 的值为:" + i);34 i = 5;35 System.out.println(Thread.currentThread().getName() + "获取 i * 10 的值为:" + i * 10);36 }37 }
1 package cn.temptation; 2 3 public class Sample21 { 4 public static void main(String[] args) { 5 /* 6 * 线程安全:包括变量安全 和 线程同步 两个方面 7 * 8 * 1、变量安全: 9 * 进程中多个线程同时运行,这些线程可能会在某一时刻同时运行某一段代码,10 * 如果每次运行结果和单线程运行的结果一致,且其他变量的值也和预期一样,则称为变量安全;否则就是变量不安全11 * 12 * 类 ThreadLocal:提供了线程局部 (thread-local) 变量。13 * 这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。14 * ThreadLocal实例通常是类中的 private static字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。15 * 16 * ThreadLocal 类的常用成员方法:17 * 1、T get():返回此线程局部变量的当前线程副本中的值。 18 * 2、protected T initialValue():返回此线程局部变量的当前线程的“初始值”。 19 * 3、void remove():移除此线程局部变量当前线程的值。 20 * 4、void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。 21 */22 23 Thread21 thread21 = new Thread21();24 25 for (int i = 0; i < 1000; i++) {26 // 调用run()方法所在的线程任务对象是单例的27 // (new Thread(thread21, "自定义线程")).start();28 29 // 调用run()方法所在的线程任务对象是多例的30 (new Thread(new Thread21(), "自定义线程")).start();31 }32 }33 }34 35 class Thread21 implements Runnable {36 ThreadLocal threadLocal = new ThreadLocal () {37 protected Integer initialValue() {38 return 2;39 }40 };41 42 @Override43 public void run() {44 threadLocal.set(threadLocal.get() + 1); // 2 + 145 System.out.println(Thread.currentThread().getName() + "获取 线程局部变量 的值为:" + threadLocal.get()); // 346 47 // 移除此线程局部变量当前线程的值。48 threadLocal.remove();49 50 threadLocal.set(threadLocal.get() + 2); // 2 + 251 System.out.println(Thread.currentThread().getName() + "获取 线程局部变量 * 10 的值为:" + threadLocal.get() * 10); // 4052 }53 }
1 package cn.temptation; 2 3 public class Sample22 { 4 public static void main(String[] args) { 5 /* 6 * 线程安全:包括变量安全 和 线程同步 两个方面 7 * 8 * 2、线程同步: 9 * 代码中的业务逻辑是一个原子性的动作,一旦分割执行就可能导致丧失其本来意义10 * 在多线程环境中,运行线程被线程调度器(线程规划器)暂停的可能性随时存在,这就给原子性的操作造成了潜在的危险11 * 在多线程的操作中,必须启动线程同步机制,即在一个线程执行完这组动作之前,其他线程必须不能进入这段代码12 * 13 * 问题:为什么需要进行线程同步?14 * 答:15 * 1、多个线程再操作同一份共享数据16 * 2、操作同一份共享数据的代码有多个17 * 18 */19 20 Thread22 thread22 = new Thread22();21 22 (new Thread(thread22, "窗口1")).start();23 (new Thread(thread22, "窗口2")).start();24 (new Thread(thread22, "窗口3")).start();25 26 // 执行结果中,有窗口显示售卖第0张票,还有窗口显示售卖第-1张票27 }28 }29 30 class Thread22 implements Runnable {31 // 成员变量32 private int tickets = 20;33 34 @Override35 public void run() {36 while (tickets > 0) {37 // 整个动作包括 动作1 和 动作2,但是多线程的操作会分割这两个动作,即破坏了动作的原子性38 // 动作1、模拟实际场景,查看票数等操作会花费一些时间39 try {40 Thread.sleep(10);41 } catch (InterruptedException e) {42 e.printStackTrace();43 }44 45 // 动作2、卖票46 System.out.println(Thread.currentThread().getName() + "正在售卖第" + (tickets--) + "张票");47 }48 }49 }
1 package cn.temptation; 2 3 public class Sample23 { 4 public static void main(String[] args) { 5 /* 6 * 线程安全:包括变量安全 和 线程同步 两个方面 7 * 8 * 问题:如何进行线程同步? 9 * 答: 能保证动作的完整性(原子性),Java中提供了 同步关键字 synchronized10 * 11 * 线程同步方式1、使用同步代码块12 * 格式:13 * synchronized (lock) {14 * 15 * }16 * 17 * lock:锁对象,默认标志位为118 * 19 * 线程执行同步代码块的顺序:20 * 1、检查锁对象的标志位,默认为121 * 2、执行同步代码块,将标志位设置为022 * 3、当其他线程对象执行到同步代码块时,因为锁对象标志位为0,其他线程对象发生阻塞23 * 4、等待同步代码块执行完毕,再将标志位设置位为1,其他线程对象才可以进入同步代码块24 * 25 * 理解:类比上厕所把门锁上了26 */27 Thread23 thread23 = new Thread23();28 29 (new Thread(thread23, "窗口1")).start();30 (new Thread(thread23, "窗口2")).start();31 (new Thread(thread23, "窗口3")).start();32 }33 }34 35 class Thread23 implements Runnable {36 // 成员变量37 private int tickets = 20;38 // 随便创建一个对象39 Object lock = new Object();40 41 @Override42 public void run() {43 while (true) {44 // 使用同步代码块45 // 写法1、使用自定义对象46 // synchronized (lock) {47 // 写法2、使用this,即当前线程对象48 synchronized (this) { // this:cn.temptation.Thread23@790d328349 if (tickets > 0) {50 // 整个动作包括 动作1 和 动作2,但是多线程的操作会分割这两个动作,即破坏了动作的原子性51 // 动作1、模拟实际场景,查看票数等操作会花费一些时间52 try {53 Thread.sleep(10);54 } catch (InterruptedException e) {55 e.printStackTrace();56 }57 58 // 动作2、卖票59 System.out.println(Thread.currentThread().getName() + "正在售卖第" + (tickets--) + "张票");60 } else {61 break;62 }63 }64 }65 }66 }
1 package cn.temptation; 2 3 public class Sample24 { 4 public static void main(String[] args) { 5 /* 6 * 线程安全:包括变量安全 和 线程同步 两个方面 7 * 8 * 问题:如何进行线程同步? 9 * 答: 能保证动作的完整性(原子性),Java中提供了 同步关键字 synchronized10 * 11 * 线程同步方式2、使用同步方法12 * 格式:13 * synchronized 返回值类型 方法名(参数列表) {14 * 15 * }16 * 17 * 使用synchronized修饰的方法在某一个时刻只允许一个线程对象的调用,其他调用该方法的线程对象都会发生阻塞,18 * 直到该方法执行完毕,其他线程对象才能调用19 * 20 * 问题:同步代码块 和 同步方法 的区别:21 * 1、同步代码块的锁是任意对象(可以是this)22 * 2、同步方法的锁是this(当前线程对象)23 * 24 * 25 * 注意:线程同步的优缺点26 * 优点:解决了动作的原子性问题(保障了线程安全)27 * 缺点:多个线程对象需要判断锁,比较消耗资源28 */29 30 Thread24 thread24 = new Thread24();31 32 (new Thread(thread24, "窗口1")).start();33 (new Thread(thread24, "窗口2")).start();34 (new Thread(thread24, "窗口3")).start();35 }36 }37 38 class Thread24 implements Runnable {39 // 成员变量40 private int tickets = 20;41 42 @Override43 public void run() {44 while (true) {45 // 调用同步方法46 sale();47 48 if (tickets <= 0) {49 break;50 }51 }52 }53 54 // 定义同步方法55 public synchronized void sale() {56 if (tickets > 0) {57 // 整个动作包括 动作1 和 动作2,但是多线程的操作会分割这两个动作,即破坏了动作的原子性58 // 动作1、模拟实际场景,查看票数等操作会花费一些时间59 try {60 Thread.sleep(10);61 } catch (InterruptedException e) {62 e.printStackTrace();63 }64 65 // 动作2、卖票66 System.out.println(Thread.currentThread().getName() + "正在售卖第" + (tickets--) + "张票");67 }68 }69 }
1 package cn.temptation; 2 3 public class Sample25 { 4 public static void main(String[] args) { 5 // 多线程的典型问题:生产者、消费者问题 6 Message msg = new Message(); 7 8 (new Thread(new Producer(msg), "生产者")).start(); 9 (new Thread(new Consumer(msg), "消费者")).start(); 10 11 // 执行效果可以看到: 12 // 1、设置的数据错位; 13 // 2、数据无法生产一条、消费一条 14 } 15 } 16 17 // 作为生成和消费的对象 ----- 信息类 18 class Message { 19 // 成员变量 20 // 标题 21 private String title; 22 // 内容 23 private String content; 24 25 // 成员方法 26 public String getTitle() { 27 return title; 28 } 29 30 public void setTitle(String title) { 31 this.title = title; 32 } 33 34 public String getContent() { 35 return content; 36 } 37 38 public void setContent(String content) { 39 this.content = content; 40 } 41 } 42 43 // 生产者 44 class Producer implements Runnable { 45 // 成员变量 46 private Message msg = null; 47 48 // 构造函数 49 public Producer() { 50 super(); 51 } 52 53 public Producer(Message msg) { 54 super(); 55 this.msg = msg; 56 } 57 58 // 成员方法 59 @Override 60 public void run() { 61 for (int i = 0; i < 10; i++) { 62 if (i % 2 == 0) { // 奇数时生产的是一种信息 63 // 下面代码显然无法保证动作的原子性 64 this.msg.setTitle("★"); 65 66 try { 67 Thread.sleep(100); 68 } catch (InterruptedException e) { 69 e.printStackTrace(); 70 } 71 72 this.msg.setContent("★★★"); 73 } else { // 偶数时生产的是另一种信息 74 // 下面代码显然无法保证动作的原子性 75 this.msg.setTitle("☆"); 76 77 try { 78 Thread.sleep(100); 79 } catch (InterruptedException e) { 80 e.printStackTrace(); 81 } 82 83 this.msg.setContent("☆☆☆"); 84 } 85 } 86 } 87 } 88 89 // 消费者 90 class Consumer implements Runnable { 91 // 成员变量 92 private Message msg = null; 93 94 // 构造函数 95 public Consumer() { 96 super(); 97 } 98 99 public Consumer(Message msg) {100 super();101 this.msg = msg;102 }103 104 @Override105 public void run() {106 for (int i = 0; i < 10; i++) {107 try {108 Thread.sleep(100);109 } catch (InterruptedException e) {110 e.printStackTrace();111 }112 113 System.out.println(this.msg.getTitle() + "----->" + this.msg.getContent());114 }115 }116 }
1 package cn.temptation; 2 3 public class Sample26 { 4 public static void main(String[] args) { 5 // 多线程的典型问题:生产者、消费者问题 6 MessageEx msgEx = new MessageEx(); 7 8 (new Thread(new ProducerEx(msgEx), "生产者")).start(); 9 (new Thread(new ConsumerEx(msgEx), "消费者")).start();10 11 // 执行效果可以看到:12 // 1、数据无法生产一条、消费一条13 }14 }15 16 // 作为生成和消费的对象 ----- 信息类17 class MessageEx {18 // 成员变量19 // 标题20 private String title;21 // 内容22 private String content;23 24 // 成员方法25 // 同步方法进行赋值26 public synchronized void set(String title, String content) {27 this.title = title;28 29 try {30 Thread.sleep(100);31 } catch (InterruptedException e) {32 e.printStackTrace();33 }34 35 this.content = content;36 }37 38 // 同步方法进行取值39 public synchronized void get() {40 try {41 Thread.sleep(100);42 } catch (InterruptedException e) {43 e.printStackTrace();44 }45 46 System.out.println(this.title + "----->" + this.content);47 }48 }49 50 // 生产者51 class ProducerEx implements Runnable {52 // 成员变量53 private MessageEx msgEx = null;54 55 // 构造函数56 public ProducerEx() {57 super();58 }59 60 public ProducerEx(MessageEx msgEx) {61 super();62 this.msgEx = msgEx;63 }64 65 // 成员方法66 @Override67 public void run() {68 for (int i = 0; i < 20; i++) {69 if (i % 2 == 0) { // 奇数时生产的是一种信息70 this.msgEx.set("★", "★★★");71 } else { // 偶数时生产的是另一种信息72 this.msgEx.set("☆", "☆☆☆");73 }74 }75 }76 }77 78 // 消费者79 class ConsumerEx implements Runnable {80 // 成员变量81 private MessageEx msgEx = null;82 83 // 构造函数84 public ConsumerEx() {85 super();86 }87 88 public ConsumerEx(MessageEx msgEx) {89 super();90 this.msgEx = msgEx;91 }92 93 @Override94 public void run() {95 for (int i = 0; i < 20; i++) {96 this.msgEx.get();97 }98 }99 }
1 package cn.temptation; 2 3 public class Sample27 { 4 public static void main(String[] args) { 5 // 多线程的典型问题:生产者、消费者问题 6 /* 7 * Object类的常用成员方法: 8 * 1、void wait():在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 9 * 2、void notify():唤醒在此对象监视器上等待的单个线程。 10 */ 11 12 MessageFinal msgFinal = new MessageFinal(); 13 14 (new Thread(new ProducerFinal(msgFinal), "生产者")).start(); 15 (new Thread(new ConsumerFinal(msgFinal), "消费者")).start(); 16 17 // 执行效果可以看到之前列出的两个问题都解决 18 } 19 } 20 21 // 作为生成和消费的对象 ----- 信息类 22 class MessageFinal { 23 // 成员变量 24 // 标题 25 private String title; 26 // 内容 27 private String content; 28 // 判断标识:设置为true,表示可以生产,不能消费;设置为false,表示可以消费,不能生产 29 private boolean flag = true; 30 31 // 成员方法 32 // 同步方法进行赋值 33 public synchronized void set(String title, String content) { 34 if (this.flag == false) { // 可以消费,不能生产 35 try { 36 // 下面两条语句效果相同 37 // this.wait(); // 等待 38 super.wait(); // 等待 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 } 43 44 this.title = title; 45 46 try { 47 Thread.sleep(100); 48 } catch (InterruptedException e) { 49 e.printStackTrace(); 50 } 51 52 this.content = content; 53 54 this.flag = false; // 已经完成了生产,可以进行消费了,修改标识位 55 56 // 下面两条语句效果相同 57 // this.notify(); // 唤醒等待线程 58 super.notify(); // 唤醒等待线程 59 } 60 61 // 同步方法进行取值 62 public synchronized void get() { 63 if (this.flag == true) { // 可以生产,不能消费 64 try { 65 // 下面两条语句效果相同 66 // this.wait(); // 等待 67 super.wait(); // 等待 68 } catch (InterruptedException e) { 69 e.printStackTrace(); 70 } 71 } 72 73 try { 74 Thread.sleep(100); 75 } catch (InterruptedException e) { 76 e.printStackTrace(); 77 } 78 79 System.out.println(this.title + "----->" + this.content); 80 81 this.flag = true; // 已经完成了消费,可以进行生产了,修改标识位 82 83 // 下面两条语句效果相同 84 // this.notify(); // 唤醒等待线程 85 super.notify(); // 唤醒等待线程 86 } 87 } 88 89 // 生产者 90 class ProducerFinal implements Runnable { 91 // 成员变量 92 private MessageFinal msgFinal = null; 93 94 // 构造函数 95 public ProducerFinal() { 96 super(); 97 } 98 99 public ProducerFinal(MessageFinal msgFinal) {100 super();101 this.msgFinal = msgFinal;102 }103 104 // 成员方法105 @Override106 public void run() {107 for (int i = 0; i < 20; i++) {108 if (i % 2 == 0) { // 奇数时生产的是一种信息109 this.msgFinal.set("★", "★★★");110 } else { // 偶数时生产的是另一种信息111 this.msgFinal.set("☆", "☆☆☆");112 }113 }114 }115 }116 117 // 消费者118 class ConsumerFinal implements Runnable {119 // 成员变量120 private MessageFinal msgFinal = null;121 122 // 构造函数123 public ConsumerFinal() {124 super();125 }126 127 public ConsumerFinal(MessageFinal msgFinal) {128 super();129 this.msgFinal = msgFinal;130 }131 132 @Override133 public void run() {134 for (int i = 0; i < 20; i++) {135 this.msgFinal.get();136 }137 }138 }