1.基本概念:程序、进程、线程

1.1 程序(program)

概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。

1.2 进程(process)

概念:程序的一次执行过程,或是正在运行的一个程序。 说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

1.3 线程(thread)

概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。 说明:线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的开销小。
七:多线程 (2022.5.7 施工完毕✅) - 图1

2.并行与并发

2.1 单核CPU和多核CPU

单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。涉及到CPU处理线程的方式,CPU在单位时间(也就是说一个时间片内)内只能处理一个线程,于是就将其他的线程设置为阻塞状态,加入到阻塞队列中,等到处理完成当前线程后从就绪队列中取出新的线程进行处理,由于切换和处理时间很快用户感知不到,于是用户便认为CPU在同一时间内处理多个线程。
>多核CPU,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
>一个Java应用程序java.exe,其实至少三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

2.2并行与并发的理解

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
>并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事

为什么要使用多线程?

当我们在进行商品抢购的时候,在支付按钮上总是有个计时器在进行倒计时,但是我们此时仍然可以进行商品信息的查看,这个计时器和我们浏览商品信息的线程是同时进行的,这样也就实现了抢购场景,增加了用户的体验。

1.多线程的优点

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率。
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

    2.应用场景

  4. 程序需要同时执行两个或多个任务。

  5. 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等
  6. 需要一些后台运行的程序时

    3.Thread类

    Java语言的JVM允许程序运行多个线程,它通过 java. lang.Thread类来体现

    3.1线程的创建方式一:继承Thread类

    ```java package com.test.java;

/**

  • 多线程的创建,方式一:继承于Thread类
  • 1.创建一个继承于Thread类的子类
  • 2.重写Thread类中的run()方法
  • 3.创建子类对象
  • 4.调用start()方法 *
  • 例子:遍历100以内的所有偶数 */ public class ThreadTest { public static void main(String[] args) {
    1. MyThread t = new MyThread();
    2. t.start();//1.启动当前线程 2.调用当前线程的run()方法
    3. for (int i = 0; i <= 100; i++) {
    4. if (i % 2 != 0) {
    5. System.out.println(i + "******主线程");
    6. }
    7. }
    } }

class MyThread extends Thread { @Override public void run() { for (int i = 0; i <= 100; i++) { if (i % 2 == 0) { System.out.println(i + “**线程”); } } } }

  1. <a name="ZVZqn"></a>
  2. ### 3.2方式一练习
  3. ```java
  4. package com.test.exer;
  5. /**
  6. * 练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
  7. */
  8. public class ThreadDemo {
  9. public static void main(String[] args) {
  10. // MyThread1 t1 = new MyThread1();
  11. // MyThread2 t2 = new MyThread2();
  12. // t1.start();
  13. // t2.start();
  14. //更简洁的方式:匿名子类的匿名对象
  15. new Thread(){
  16. @Override
  17. public void run() {
  18. for (int i = 0; i < 100; i++) {
  19. if (i % 2 == 0) {
  20. System.out.println(Thread.currentThread().getName() + i);
  21. }
  22. }
  23. }
  24. }.start();
  25. new Thread(){
  26. @Override
  27. public void run() {
  28. for (int i = 0; i < 100; i++) {
  29. if (i % 2 != 0) {
  30. System.out.println(Thread.currentThread().getName() + i);
  31. }
  32. }
  33. }
  34. }.start();
  35. }
  36. }
  37. class MyThread1 extends Thread {
  38. @Override
  39. public void run() {
  40. for (int i = 0; i < 100; i++) {
  41. if (i % 2 == 0) {
  42. System.out.println(Thread.currentThread().getName() + i);
  43. }
  44. }
  45. }
  46. }
  47. class MyThread2 extends Thread {
  48. @Override
  49. public void run() {
  50. for (int i = 0; i < 100; i++) {
  51. if (i % 2 != 0) {
  52. System.out.println(Thread.currentThread().getName() + i);
  53. }
  54. }
  55. }
  56. }

3.3线程的常用方法

  1. package com.test.java;
  2. /**
  3. * 测试Thread类的常用方法
  4. * 1.start()方法:启动当前线程;调用当前线程的run()方法
  5. * 2.run()方法:重写此方法,写创建该线程需要执行的操作
  6. * 3.CurrentThread()方法:静态方法,返回执行当前代码的线程
  7. * 4.getName()方法:获取当前线程的名字
  8. * 5.setName()方法:设置当前线程的名字
  9. * 6.yield()方法:释放当前CPU执行权
  10. * 7.join()方法:在线程a中调用线租b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程q才结束阳塞状杰
  11. * 8.stop()方法:强制结束此线程
  12. * 9.sleep()方法:阻塞此线程多少毫秒
  13. * 10.isAlive()方法:判断该线程是否存活
  14. */
  15. public class ThreadMethodTest {
  16. public static void main(String[] args) {
  17. HelloThread h = new HelloThread("线程一");
  18. //h.setName("线程一");setName()方法命名
  19. h.start();
  20. //给主线程命名
  21. Thread.currentThread().setName("主线程");
  22. for (int i = 0; i <= 100; i++) {
  23. if (i % 2 == 0) {
  24. System.out.println(i + "******" + Thread.currentThread().getName());
  25. }
  26. if (i == 20) {
  27. try {
  28. h.join();
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. System.out.println(h.isAlive());//false
  35. }
  36. }
  37. class HelloThread extends Thread {
  38. @Override
  39. public void run() {
  40. for (int i = 0; i <= 100; i++) {
  41. if (i % 2 == 0) {
  42. System.out.println(i + "******" + Thread.currentThread().getName());
  43. }
  44. try {
  45. sleep(2000);
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. if (i % 20 == 0) {
  50. yield();
  51. }
  52. }
  53. }
  54. //构造器方式命名
  55. public HelloThread(String name){
  56. super(name);
  57. }
  58. }

3.4线程优先级的设置

image.png

  1. package com.test.java;
  2. /**
  3. * 线程优先级
  4. * 1.MAX_PRIORITY 10
  5. * 2.MIN_PRIORITY 1
  6. * 3.NORM_PRIORITY 5
  7. * 4.getPriority():获取线程的优先级
  8. * 5.setPriority():设置线程的优先级
  9. * 注:并不是高优先级一定先执行,而是高概率执行
  10. */
  11. public class ThreadTest {
  12. public static void main(String[] args) {
  13. MyThread t = new MyThread();
  14. t.setPriority(Thread.MAX_PRIORITY);
  15. t.start();
  16. for (int i = 0; i <= 100; i++) {
  17. if (i % 2 != 0) {
  18. System.out.println(i + "******主线程");
  19. }
  20. }
  21. }
  22. }
  23. class MyThread extends Thread {
  24. @Override
  25. public void run() {
  26. for (int i = 0; i <= 100; i++) {
  27. if (i % 2 == 0) {
  28. System.out.println(i + "******线程");
  29. }
  30. }
  31. }
  32. }

3.5例题:方式一 继承Thread的多窗口卖票

  1. package com.test.java;
  2. /**
  3. * 例:创建三个窗口卖票,总票数为100张
  4. * 存在线程的安全问题:待解决
  5. */
  6. public class WindowTest {
  7. public static void main(String[] args) {
  8. Window w1 = new Window();
  9. Window w2 = new Window();
  10. Window w3 = new Window();
  11. w1.setName("窗口1");
  12. w2.setName("窗口2");
  13. w3.setName("窗口3");
  14. w1.start();
  15. w2.start();
  16. w3.start();
  17. }
  18. }
  19. class Window extends Thread{
  20. private static int ticket = 100;
  21. @Override
  22. public void run() {
  23. while (true){
  24. if (ticket > 0){
  25. System.out.println(this.getName() + "卖票,票号为" + (101 - ticket));
  26. ticket--;
  27. }
  28. else {
  29. break;
  30. }
  31. }
  32. }
  33. }

3.6线程的创建方式二:实现Runnable接口

  1. package com.test.java;
  2. /**
  3. * 创建多线程的方式二:实现Runnable接口
  4. * 1.创建一个实现了Runnable接口的类
  5. * 2.实现类去实现Runnable中的抽象方法:run()
  6. * 3.创建实现类的对象
  7. * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  8. * 5.通过Thread类的对象调用start()
  9. */
  10. public class ThreadTest1 {
  11. public static void main(String[] args) {
  12. //3.创建实现类的对象
  13. MThread m = new MThread();
  14. //4.将对象传入Thread构造器中
  15. Thread t = new Thread(m);
  16. //5.通过Thread类的对象调用start()
  17. t.start();
  18. }
  19. }
  20. //1.创建一个实现Runnable接口的类
  21. class MThread implements Runnable{
  22. //2.重写run方法
  23. @Override
  24. public void run() {
  25. for (int i = 0; i <= 100; i++) {
  26. if (i % 2 != 0) {
  27. System.out.println(i + "******线程");
  28. }
  29. }
  30. }
  31. }

3.7例题:方式二 实现Runnable接口的多窗口卖票

  1. package com.test.java;
  2. /**
  3. * 例:创建三个窗口卖票,总票数为100张,使用Runnable接口方式
  4. * 存在线程的安全问题:待解决
  5. */
  6. public class WindowTest1 {
  7. public static void main(String[] args) {
  8. MyThread1 m = new MyThread1();
  9. Thread t1 = new Thread(m);
  10. Thread t2 = new Thread(m);
  11. Thread t3 = new Thread(m);
  12. t1.setName("窗口1");
  13. t2.setName("窗口2");
  14. t3.setName("窗口3");
  15. t1.start();
  16. t2.start();
  17. t3.start();
  18. }
  19. }
  20. class MyThread1 implements Runnable{
  21. //此处没有static因为上面传入Thread类的是一个对象不是多个
  22. private int ticket = 100;
  23. @Override
  24. public void run() {
  25. while (true){
  26. if (ticket > 0){
  27. System.out.println(Thread.currentThread().getName()+"卖票成功,票号:"+ ticket);
  28. ticket--;
  29. }else {
  30. break;
  31. }
  32. }
  33. }
  34. }

3.8继承方式和实现方式的联系与区别

  1. /**
  2. * 继承方式和实现方式的联系与区别
  3. * 比较创建线程的两种方式。
  4. * 开发中:优先选择:实现Runnable接口的方式
  5. * 原因:1. 实现的方式没有类的单继承性的局限性
  6. * 2. 实现的方式更适合来处理多个线程有共享数据的情况。
  7. *
  8. * 联系:public class Thread implements Runnable
  9. * 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
  10. */

4.线程的生命周期

新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
七:多线程 (2022.5.7 施工完毕✅) - 图3

5.线程的同步

5.1理解线程的安全问题

*问题的提出:
>多个线程执行的不确定性引起执行结果的不稳定
>多个线程对账本的共享,会造成操作的不完整性,会破坏数据。
下面两个线程如果if同时判断的话,就会欠债了!(不允许这种情况发生)
image.png

5.2线程安全问题的举例

  1. package com.test.java;
  2. /**
  3. * 存在线程的安全问题:有重票,错票
  4. */
  5. public class WindowTest1 {
  6. public static void main(String[] args) {
  7. MyThread1 m = new MyThread1();
  8. Thread t1 = new Thread(m);
  9. Thread t2 = new Thread(m);
  10. Thread t3 = new Thread(m);
  11. t1.setName("窗口1");
  12. t2.setName("窗口2");
  13. t3.setName("窗口3");
  14. t1.start();
  15. t2.start();
  16. t3.start();
  17. }
  18. }
  19. class MyThread1 implements Runnable{
  20. //此处没有static因为上面传入Thread类的是一个对象不是多个
  21. private int ticket = 100;
  22. @Override
  23. public void run() {
  24. while (true){
  25. if (ticket > 0){
  26. //多加一个sleep()就是让线程通过判断条件后在卖票,这样ticket--执行就是或许同步执行的了,重票概率大大增加
  27. try {
  28. Thread.sleep(100);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. System.out.println(Thread.currentThread().getName()+"卖票成功,票号:"+ ticket);
  33. ticket--;
  34. }else {
  35. break;
  36. }
  37. }
  38. }
  39. }

理想状态:
image.png
极端状态:
image.png

5.3同步代码块处理实现Runnable的线程安全问题

  1. package com.test.java;
  2. /**
  3. * 1.问题:卖票过程中出现了重票错票-->线程的安全问题
  4. * 2.原因:当某个线程操作ticket的过程中尚未完成时,其他线程就加入进来操作ticket
  5. * 3.解决:当一个线程在操作共享数据(ticket)的时候,其他线程不能参与进来。直到该线程操作完共享数据(ticket)后,其他线程才能操作。
  6. * 4.在Java中我们通过同步机制来解决这个问题。
  7. * 方式一:同步代码块
  8. * synchronized(同步监视器){
  9. * 需要被同步的代码
  10. * }
  11. * 说明:1.操作共享下数据的代码即为需要同步的代码
  12. * 2.共享数据:多个线程共同操作的变量,此例为ticket
  13. * 3.同步监视器:俗称 锁 任何一个类的对象都可以充当锁
  14. * 要求:多个线程必须要用同一把锁
  15. *
  16. * 方式二:同步方法
  17. *
  18. * 5.同步的方式,解决了线程的安全问题。---好处
  19. * 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。---局限性
  20. */
  21. public class WindowTest1 {
  22. public static void main(String[] args) {
  23. MyThread1 m = new MyThread1();
  24. Thread t1 = new Thread(m);
  25. Thread t2 = new Thread(m);
  26. Thread t3 = new Thread(m);
  27. t1.setName("窗口1");
  28. t2.setName("窗口2");
  29. t3.setName("窗口3");
  30. t1.start();
  31. t2.start();
  32. t3.start();
  33. }
  34. }
  35. class MyThread1 implements Runnable{
  36. //此处没有static因为上面传入Thread类的是一个对象不是多个
  37. private int ticket = 100;
  38. final Object obj = new Object();
  39. @Override
  40. public void run() {
  41. while (true){
  42. //用了同步锁之后就是舒服捏
  43. //synchronized(obj){
  44. //this也行
  45. synchronized (this){
  46. if (ticket > 0){
  47. //多加一个sleep()就是让线程通过判断条件后在卖票,这样ticket--执行就是或许同步执行的了,重票概率大大增加
  48. try {
  49. Thread.sleep(100);
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. System.out.println(Thread.currentThread().getName()+"卖票成功,票号:"+ ticket);
  54. ticket--;
  55. }else {
  56. break;
  57. }
  58. }
  59. }
  60. }
  61. }

锁原理:
image.png

5.4同步代码块处理继承Thread类的线程安全问题

  1. package com.test.java;
  2. /**
  3. * 使用同步代码块解决继承Thread类的方式的线程安全问题
  4. *
  5. * 例子:创建三个窗口卖票,总票数为100张
  6. */
  7. class Windows extends Thread{
  8. private static int ticket = 100;
  9. //共用同一把锁,obj要加static
  10. private static final Object obj = new Object();
  11. @Override
  12. public void run() {
  13. while(true){
  14. //正确的
  15. // synchronized (obj) {
  16. synchronized (Windows.class){ //Class clazz = Windows.class
  17. //错误的,因为此时this表示的是t1,t2,t3三个对象
  18. // synchronized (this) {
  19. if (ticket > 0) {
  20. try {
  21. Thread.sleep(100);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println(getName() + ":卖票,票号为: " + ticket);
  26. ticket--;
  27. } else {
  28. break;
  29. }
  30. }
  31. }
  32. }
  33. }
  34. public class WindowsTest2 {
  35. public static void main(String[] args) {
  36. Windows t1 = new Windows();
  37. Windows t2 = new Windows();
  38. Windows t3 = new Windows();
  39. t1.setName("窗口1");
  40. t2.setName("窗口2");
  41. t3.setName("窗口3");
  42. t1.start();
  43. t2.start();
  44. t3.start();
  45. }
  46. }

5.5同步方法处理实现Runnable的线程安全问题

  1. package com.test.java;
  2. /**
  3. * 使用同步方法解决实现Runnable接口的线程安全问题
  4. *
  5. * 关于同步方法的总结:
  6. * 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
  7. * 2. 非静态的同步方法,同步监视器是:this
  8. * 静态的同步方法,同步监视器是:当前类本身
  9. */
  10. class Windows3 implements Runnable {
  11. private int ticket = 100;
  12. @Override
  13. public void run() {
  14. while (true) {
  15. show();
  16. }
  17. }
  18. private synchronized void show() { //同步监视器:this
  19. // synchronized (this){
  20. if (ticket > 0) {
  21. try {
  22. Thread.sleep(100);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket);
  27. ticket--;
  28. }
  29. // }
  30. }
  31. }
  32. public class WindowsTest3 {
  33. public static void main(String[] args) {
  34. Windows3 w3 = new Windows3();
  35. Thread t1 = new Thread(w3);
  36. Thread t2 = new Thread(w3);
  37. Thread t3 = new Thread(w3);
  38. t1.setName("窗口1");
  39. t2.setName("窗口2");
  40. t3.setName("窗口3");
  41. t1.start();
  42. t2.start();
  43. t3.start();
  44. }
  45. }

5.6同步方法处理继承Thread类的线程安全问题

  1. package com.test.java;
  2. /**
  3. * 使用同步方法处理继承Thread类的方式中的线程安全问题
  4. */
  5. class Windows4 extends Thread {
  6. private static int ticket = 100;
  7. @Override
  8. public void run() {
  9. while (true) {
  10. show();
  11. }
  12. }
  13. private static synchronized void show(){//同步监视器:Window4.class
  14. //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的
  15. if (ticket > 0) {
  16. try {
  17. Thread.sleep(100);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
  22. ticket--;
  23. }
  24. }
  25. }
  26. public class WindowsTest4 {
  27. public static void main(String[] args) {
  28. Windows4 t1 = new Windows4();
  29. Windows4 t2 = new Windows4();
  30. Windows4 t3 = new Windows4();
  31. t1.setName("窗口1");
  32. t2.setName("窗口2");
  33. t3.setName("窗口3");
  34. t1.start();
  35. t2.start();
  36. t3.start();
  37. }
  38. }

5.7线程安全的单例模式之懒汉式

  1. package com.test.java;
  2. /**
  3. * 使用同步机制将单例模式中的懒汉式改写为线程安全的
  4. */
  5. public class BankTest {
  6. }
  7. class Bank{
  8. //构造器
  9. private Bank(){}
  10. private static Bank instance = null;
  11. public static Bank getInstance() {
  12. //方式一:效率稍差
  13. // synchronized (Bank.class) {
  14. // if(instance == null){
  15. // instance = new Bank();
  16. // }
  17. // return instance;
  18. // }
  19. //方式二:效率较高
  20. if (instance == null) {
  21. synchronized (Bank.class) {
  22. if (instance == null) {
  23. instance = new Bank();
  24. }
  25. }
  26. }
  27. return instance;
  28. }
  29. }

5.8线程的死锁问题

形象理解:打架都让对方放手,都不放手

  1. package com.test.java;
  2. /**
  3. * 演示线程的死锁问题:
  4. * 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
  5. * 都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  6. * 2.说明:
  7. * >出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
  8. * >我们使用同步时,要避免出现死锁。
  9. */
  10. public class DeadLock {
  11. public static void main(String[] args) {
  12. StringBuffer str1 = new StringBuffer();
  13. StringBuffer str2 = new StringBuffer();
  14. new Thread(){
  15. @Override
  16. public void run() {
  17. synchronized (str1){
  18. str1.append("a");
  19. str2.append("1");
  20. //这个线程等着拿str2这个锁
  21. try {
  22. Thread.sleep(100);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. synchronized (str2){
  27. str1.append("b");
  28. str2.append("2");
  29. System.out.println(str1);
  30. System.out.println(str2);
  31. }
  32. }
  33. }
  34. }.start();
  35. new Thread(new Runnable() {
  36. @Override
  37. public void run() {
  38. synchronized (str2){
  39. str1.append("c");
  40. str2.append("3");
  41. //这个线程等着拿str1这个锁
  42. try {
  43. Thread.sleep(100);
  44. } catch (InterruptedException e) {
  45. e.printStackTrace();
  46. }
  47. synchronized (str1){
  48. str1.append("d");
  49. str2.append("4");
  50. System.out.println(str1);
  51. System.out.println(str2);
  52. }
  53. }
  54. }
  55. }).start();
  56. }
  57. }

5.9Lock锁方式解决线程安全问题

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
>ReentrantLock类实现了Lock ,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
>从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。 ```java package com.test.java;

import java.util.concurrent.locks.ReentrantLock;

/**

  • 解决线程安全问题三—>Lock锁 —-JDK5.0新增
    1. 面试题:synchronized 与 Lock的异同?
  • 相同:二者都可以解决线程安全问题
  • 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
  • Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock()) */ public class LockTest { public static void main(String[] args) { MyThread m = new MyThread(); Thread t1 = new Thread(m); Thread t2 = new Thread(m); Thread t3 = new Thread(m); t1.setName(“窗口1”); t2.setName(“窗口2”); t3.setName(“窗口3”); t1.start(); t2.start(); t3.start(); } } class MyThread implements Runnable{ private int ticket = 100; //1.实例化ReentranLock private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { try {
    1. //2.调用lock()
    2. lock.lock();
    3. if (ticket > 0) {
    4. System.out.println(Thread.currentThread().getName() + "卖票成功,票号:" + ticket);
    5. ticket--;
    6. } else {
    7. break;
    8. }
    } finally {
    1. //解锁方法unlock()
    2. lock.unlock();
    } } } } ```

    5.10线程练习

    ```java package com.test.java;

public class AccountTest { public static void main(String[] args) { Account acct = new Account(0); Customer c1 = new Customer(acct); Customer c2 = new Customer(acct); c1.setName(“甲”); c2.setName(“乙”); c1.start(); c2.start(); } } class Customer extends Thread{ private Account acct; public Customer(Account acct) { this.acct = acct; }

  1. @Override
  2. public void run() {
  3. for (int i = 0;i < 3;i++){
  4. try {
  5. Thread.sleep(1000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. acct.deposit(1000);
  10. }
  11. }

} class Account{ private double balance; public Account(double balance) { this.balance = balance; } public synchronized void deposit(double amt){ balance+=amt; System.out.println(Thread.currentThread().getName() + “存钱成功,余额为:” + balance); } }

  1. <a name="n3LZL"></a>
  2. ## 6.线程的通信
  3. <a name="GyX01"></a>
  4. ### 6.1举例和使用
  5. ```java
  6. package com.test.java2;
  7. /**
  8. * 线程通信的例子:使用两个线程打印1-100。线程1, 线程2 交替打印
  9. *
  10. * 涉及到的三个方法:
  11. * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
  12. * notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  13. * notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
  14. *
  15. * 说明:
  16. * 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
  17. * 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
  18. * 否则,会出现IllegalMonitorStateException异常
  19. * 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
  20. */
  21. public class CommunicationTest {
  22. public static void main(String[] args) {
  23. Num n = new Num();
  24. Thread t1 = new Thread(n);
  25. Thread t2 = new Thread(n);
  26. t1.setName("线程1");
  27. t2.setName("线程2");
  28. t1.start();
  29. t2.start();
  30. }
  31. }
  32. class Num implements Runnable{
  33. private int num = 1;
  34. @Override
  35. public void run() {
  36. while (true){
  37. synchronized (this){
  38. notify();
  39. if (num < 100){
  40. System.out.println(Thread.currentThread().getName() + ":" + num);
  41. num++;
  42. try {
  43. //使线程进入阻塞状态
  44. wait();
  45. } catch (InterruptedException e) {
  46. e.printStackTrace();
  47. }
  48. }else {
  49. break;
  50. }
  51. }
  52. }
  53. }
  54. }

6.2sleep()和wait()的异同

  1. /**
  2. * 面试题:sleep() 和 wait()的异同?
  3. * 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
  4. * 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
  5. * 2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
  6. * 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
  7. */

6.3生产者/消费者问题

  1. /**
  2. * 线程通信的应用:经典例题:生产者/消费者问题
  3. *
  4. * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
  5. * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,
  6. * 店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;
  7. * 如果店中没有产品了,店员会告诉消费者等一下,
  8. * 如果店中有产品了再通知消费者来取走产品。
  9. *
  10. * 分析:
  11. * 1.是否是多线程的问题?是,生产者的线程,消费者的线程
  12. * 2.是否有共享数据的问题?是,店员、产品、产品数
  13. * 3.如何解决线程的安全问题?同步机制,有三种方法
  14. * 4.是否涉及线程的通信?是
  15. */
  16. class Clerk{
  17. private int productCount = 0;
  18. //生产产品
  19. public synchronized void produceProduct() {
  20. if(productCount < 20){
  21. productCount++;
  22. System.out.println(Thread.currentThread().getName() + ": 开始生产第" + productCount + "个产品");
  23. notify();
  24. }else{
  25. //等待
  26. try {
  27. wait();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  33. //消费产品
  34. public synchronized void consumeProduct() {
  35. if(productCount > 0){
  36. System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
  37. productCount--;
  38. notify();
  39. }else{
  40. //等待
  41. try {
  42. wait();
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. }
  46. }
  47. }
  48. }
  49. class Producer extends Thread{//生产者
  50. private Clerk clerk;
  51. public Producer(Clerk clerk){
  52. this.clerk = clerk;
  53. }
  54. @Override
  55. public void run() {
  56. System.out.println(getName() + ": 开始生产产品......");
  57. while(true){
  58. try {
  59. Thread.sleep(10);
  60. } catch (InterruptedException e) {
  61. e.printStackTrace();
  62. }
  63. clerk.produceProduct();
  64. }
  65. }
  66. }
  67. class Consumer extends Thread{ //消费者
  68. private Clerk clerk;
  69. public Consumer(Clerk clerk){
  70. this.clerk = clerk;
  71. }
  72. @Override
  73. public void run() {
  74. System.out.println(getName() + ": 开始消费产品......");
  75. while(true){
  76. try {
  77. Thread.sleep(20);
  78. } catch (InterruptedException e) {
  79. e.printStackTrace();
  80. }
  81. clerk.consumeProduct();
  82. }
  83. }
  84. }
  85. public class ProductTest {
  86. public static void main(String[] args) {
  87. Clerk clerk = new Clerk();
  88. Producer p1 = new Producer(clerk);
  89. p1.setName("生产者1");
  90. Consumer c1 = new Consumer(clerk);
  91. c1.setName("消费者1");
  92. Consumer c2 = new Consumer(clerk);
  93. c2.setName("消费者2");
  94. p1.start();
  95. c1.start();
  96. c2.start();
  97. }
  98. }

7.JDK5.0新增创建线程的方式

7.1线程的创建方式三:实现Callable接口

  1. package com.test.java2;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.FutureTask;
  5. /**
  6. * 创建线程的方式三:实现Callable接口 ---JDK5.0新增
  7. * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
  8. * 1.call()可以有返回值的。
  9. * 2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
  10. * 3.Callable是支持泛型的
  11. * 4.需要借助FutureTask类,比如获取返回结果
  12. */
  13. //1.创建一个Callable实现类
  14. class NumThread implements Callable {
  15. //2.实现call方法,将此线程需要执行的操作放在call中
  16. @Override
  17. public Object call() throws Exception {
  18. int sum = 0;
  19. for (int i = 1; i < 100; i++) {
  20. if (i % 2 ==0){
  21. System.out.println(i);
  22. sum+=i;
  23. }
  24. }
  25. return sum;
  26. }
  27. }
  28. public class ThreadNew{
  29. public static void main(String[] args) {
  30. //3.创建Callable实现类的实例
  31. NumThread numthread = new NumThread();
  32. //4.将实例放入FutureTask的构造器中
  33. FutureTask<Integer> f = new FutureTask<Integer>(numthread);
  34. //5.将FutureTask类的对象放入Thread中
  35. Thread t = new Thread(f);
  36. t.start();
  37. try {
  38. Integer i = f.get();
  39. System.out.println("总和为" + i);
  40. } catch (InterruptedException | ExecutionException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. }

7.2使用线程池的好处

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
>思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
>好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止
    • 7.3线程池

      ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor;

/**

  • 创建多线程的方式四:使用线程池 *
  • 好处:
  • 1.提高响应速度(减少了创建新线程的时间)
  • 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 3.便于线程管理
  • corePoolSize:核心池的大小
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程没有任务时最多保持多长时间后会终止 *
  • 面试题:创建多线程有几种方式?四种! */

class NumberThread implements Runnable{ @Override public void run() { for(int i = 0;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + “:” + i); } } } }

class NumberThread1 implements Runnable{ @Override public void run() { for(int i = 0;i <= 100;i++){ if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + “:” + i); } } } }

public class ThreadPool { public static void main(String[] args) {

  1. //1. 提供指定线程数量的线程池
  2. ExecutorService service = Executors.newFixedThreadPool(10);
  3. ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
  4. //设置线程池的属性

// System.out.println(service.getClass()); // service1.setCorePoolSize(15); // service1.setKeepAliveTime();

  1. //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
  2. service.execute(new NumberThread()); //适合适用于Runable
  3. service.execute(new NumberThread1()); //适合适用于Runable

// service.submit(Callable callable); //适合适用于Callable

  1. //3.关闭连接池
  2. service.shutdown();
  3. }

} ```