【原】Java学习笔记032 - 多线程
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 @Override 46 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 Sample03 22 // System.out.println("主线程的线程名称为:" + getName()); 23 24 System.out.println("主线程的线程为:" + Thread.currentThread()); // 主线程的线程为:Thread[main,5,main] 25 System.out.println("主线程的线程名称为:" + Thread.currentThread().getName()); // 主线程的线程名称为:main 26 // 语法错误:Cannot use this in a static context 27 // System.out.println(this.getName()); 28 } 29 } 30 31 class Thread03 extends Thread { 32 @Override 33 public void run() { 34 System.out.println(getName()); // Thread-0 35 System.out.println("线程对象为:" + Thread.currentThread()); // 线程对象为:Thread[Thread-0,5,main] 36 System.out.println("线程对象的线程名称为:" + Thread.currentThread().getName()); // 线程对象的线程名称为:Thread-0 37 System.out.println("this指向的当前对象为:" + this); // this指向的当前对象为:Thread[Thread-0,5,main] 38 System.out.println("线程对象的线程名称为:" + this.getName()); // 线程对象的线程名称为:Thread-0 39 } 40 } 41 42 class Thread03Ex extends Thread { 43 @Override 44 public void run() { 45 System.out.println(getName()); // Thread-1 46 System.out.println("线程对象为:" + Thread.currentThread()); // 线程对象为:Thread[Thread-1,5,main] 47 System.out.println("线程对象的线程名称为:" + Thread.currentThread().getName()); // 线程对象的线程名称为:Thread-1 48 System.out.println("this指向的当前对象为:" + this); // this指向的当前对象为:Thread[Thread-1,5,main] 49 System.out.println("线程对象的线程名称为:" + this.getName()); // 线程对象的线程名称为:Thread-1 50 } 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 @Override 43 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 @Override 34 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 @Override 55 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方法调用之前,自定义线程的活动状态为:false 12 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方法调用之后,自定义线程的活动状态为:false 25 } 26 } 27 28 class Thread06 extends Thread { 29 @Override 30 public void run() { 31 System.out.println("自定义线程的活动状态为:" + isAlive()); // 自定义线程的活动状态为:true 32 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 @Override 27 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 @Override 36 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()); // 静态代码块的使用,对应的线程名称:main 18 System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[main,5,main] 19 // 语法错误:Cannot use this in a static context 20 // System.out.println(this); 21 } 22 23 // 构造代码块 24 { 25 System.out.println("构造代码块的使用,对应的线程名称:" + Thread.currentThread().getName()); // 构造代码块的使用,对应的线程名称:main 26 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()); // 构造函数的使用,对应的线程名称:main 33 System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[main,5,main] 34 System.out.println("当前对象:" + this); // 当前对象:Thread[Thread-0,5,main] 35 } 36 37 // 成员方法 38 @Override 39 public void run() { 40 System.out.println("成员方法run()的使用,对应的线程名称:" + Thread.currentThread().getName()); // 成员方法run()的使用,对应的线程名称:Thread-0 41 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 @Override 45 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 // @Override 15 // public void run() { 16 // System.out.println("通过匿名对象创建自定义线程"); 17 // } 18 // }).start(); // 通过匿名对象创建自定义线程 19 20 System.out.println("----------------------"); 21 22 // 形式2 23 // 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 // @Override 34 // public void run() { 35 // System.out.println("通过匿名内部类创建自定义线程"); 36 // } 37 // })).start(); // 通过匿名内部类创建自定义线程 38 39 System.out.println("----------------------"); 40 41 // 混合形式1 和 形式2 42 (new Thread(new Runnable() { 43 @Override 44 public void run() { 45 System.out.println("通过匿名内部类创建自定义线程"); 46 } 47 }) { 48 @Override 49 public void run() { 50 System.out.println("通过匿名对象创建自定义线程"); 51 } 52 }).start(); // 通过匿名对象创建自定义线程 53 } 54 } 55 56 class Thread10 extends Thread { 57 @Override 58 public void run() { 59 System.out.println("自定义线程对象"); 60 } 61 } 62 63 class Thread10Ex implements Runnable { 64 @Override 65 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 @Override 36 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 @Override 29 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 @Override 36 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()); // 主线程是后台线程嘛?false 18 19 Thread14 thread14 = new Thread14(); 20 Thread thread = new Thread(thread14, "自定义线程"); 21 System.out.println("自定义线程是后台线程嘛?" + thread.isDaemon()); // 自定义线程是后台线程嘛?false 22 // 下句语句如果注释掉,自定义线程的run方法中一直执行死循环 23 // 下句语句不注释,自定义线程的run方法会执行若干条语句,然后整个进程结束 24 thread.setDaemon(true); 25 System.out.println("自定义线程是后台线程嘛?" + thread.isDaemon()); // 自定义线程是后台线程嘛?true 26 thread.start(); 27 28 // 注意:setDaemon方法在start方法之后进行设置,会产生执行异常 29 // 执行异常:java.lang.IllegalThreadStateException 30 // thread.setDaemon(true); 31 } 32 } 33 34 class Thread14 implements Runnable { 35 @Override 36 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:线程可以具有的最高优先级。 10 10 * static int MIN_PRIORITY:线程可以具有的最低优先级。 1 11 * static int NORM_PRIORITY:分配给线程的默认优先级。 5 12 * 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 @Override 33 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 @Override 32 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 的值为:3 29 // 自定义线程获取 i * 10 的值为:50 30 31 // 实际得到的结果中,有 32 // 自定义线程获取 i 的值为:5 33 // 自定义线程获取 i * 10 的值为:30 34 } 35 } 36 37 class Thread18 implements Runnable { 38 // 静态成员变量 39 private static int i = 2; 40 41 @Override 42 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 的值为:3 29 // 自定义线程获取 i * 10 的值为:50 30 31 // 实际得到的结果中,有 32 // 自定义线程获取 i 的值为:5 33 // 自定义线程获取 i * 10 的值为:30 34 } 35 } 36 37 class Thread19 implements Runnable { 38 // 非静态成员变量 39 private int i = 2; 40 41 @Override 42 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 @Override 31 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<T>:提供了线程局部 (thread-local) 变量。 13 * 这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。 14 * ThreadLocal实例通常是类中的 private static字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 15 * 16 * ThreadLocal<T>类的常用成员方法: 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<Integer> threadLocal = new ThreadLocal<Integer>() { 37 protected Integer initialValue() { 38 return 2; 39 } 40 }; 41 42 @Override 43 public void run() { 44 threadLocal.set(threadLocal.get() + 1); // 2 + 1 45 System.out.println(Thread.currentThread().getName() + "获取 线程局部变量 的值为:" + threadLocal.get()); // 3 46 47 // 移除此线程局部变量当前线程的值。 48 threadLocal.remove(); 49 50 threadLocal.set(threadLocal.get() + 2); // 2 + 2 51 System.out.println(Thread.currentThread().getName() + "获取 线程局部变量 * 10 的值为:" + threadLocal.get() * 10); // 40 52 } 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 @Override 35 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中提供了 同步关键字 synchronized 10 * 11 * 线程同步方式1、使用同步代码块 12 * 格式: 13 * synchronized (lock) { 14 * 15 * } 16 * 17 * lock:锁对象,默认标志位为1 18 * 19 * 线程执行同步代码块的顺序: 20 * 1、检查锁对象的标志位,默认为1 21 * 2、执行同步代码块,将标志位设置为0 22 * 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 @Override 42 public void run() { 43 while (true) { 44 // 使用同步代码块 45 // 写法1、使用自定义对象 46 // synchronized (lock) { 47 // 写法2、使用this,即当前线程对象 48 synchronized (this) { // this:cn.temptation.Thread23@790d3283 49 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中提供了 同步关键字 synchronized 10 * 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 @Override 43 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 @Override 105 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 @Override 67 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 @Override 94 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 @Override 106 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 @Override 133 public void run() { 134 for (int i = 0; i < 20; i++) { 135 this.msgFinal.get(); 136 } 137 } 138 }