线程

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

程序:

完成某种任务,用某种语言编写的一组指令的集合

进程:

是程序的一次执行过程,或是一个正在运行的一个程序

线程:

进程可以进一步化为多个线程,是一个程序内的一条执行路径
若一个进程同一时间并行多个线程,就是支持多线程
一个Java程序至少有三个线程 main() 主线程 gc 垃圾回收线程 异常处理线程

1、并行和并发

  1. 并行:多个cpu同事执行多个任务
  2. 并发:一个cpu同时执行多个任务

    2、多线程的优点

  3. 提高应用程序的响应

  4. 提高计算cpu的使用效率
  5. 改善程序结构

    2、线程的创建和使用

    jvm允许程序运行多个线程,它通过java.lang.Thread类来体现

    Thread类的特点

  6. 每个线程都是通过特定的Thread类的对象的run()方法来完成操作,经常把run()方法的主体叫线程体

  7. 通过该Thread对象的start()方法来启动这个线程

    1、线程的创建

    方式一:继承Thread类

    1. 步骤

      1. 继承Thread类
      2. 重写Thread类的run()方法
      3. 创建Thread的子类对象
      4. 调用子类对象的start()方法 ```java public class ThreadTest { public static void main(String[] args) { MyThread t1 = new MyThread(); t1.start(); //匿名子类对象 new Thread(){ @Override public void run() {
        1. for (int i = 0; i < 100; i++) {
        2. if (i % 2 != 0) {
        3. System.out.println(Thread.currentThread().getName() + ":" + i);
        4. }
        5. }
        } }.start();

      } }

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

  1. 线程的常用方法
  2. 1. start() 启动当前线程,调用线程的run()方法
  3. 1. run() 通常需要重写Thread类中的run方法 、将创建线程要执行的操作申明在此方法中
  4. 1. getName() 获取线程名称
  5. 1. setName() 设置线程名称
  6. 1. yield() 释放当前cpu的执行权
  7. 1. join() 在线程a中调用线程bjoin()方法,线程a进入阻塞状态,知道线程b执行完毕,线程a才结束阻塞状态
  8. 1. stop() 已过时 强制结束当前线程
  9. 1. sleep() 让当前线程睡眠XXX毫秒,指定时间内线程是阻塞状态
  10. 1. isAlive() 判断当前线程是否存活
  11. <a name="ytn8Q"></a>
  12. #### 方式二:实现 Runnable 接口
  13. 1. 创建实现Runnable接口的类
  14. 1. 实现类去实现Runnable中的抽象方法run()
  15. 1. 创建实现类的对象
  16. 1. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  17. 1. 调用Thread类对象的run()方法
  18. ```java
  19. public class ThreadTest1 {
  20. public static void main(String[] args) {
  21. MyThread1 m1 = new MyThread1();
  22. Thread t1 = new Thread(m1);
  23. t1.start();
  24. Thread t2 = new Thread(m1);
  25. t2.start();
  26. }
  27. }
  28. class MyThread1 implements Runnable {
  29. @Override
  30. public void run() {
  31. for (int i = 0; i < 100; i++) {
  32. if (i % 2 == 0) {
  33. System.out.println(Thread.currentThread().getName()+":"+i);
  34. }
  35. }
  36. }
  37. }

方式三:实现Callable接口

与Runnable 相比 Callable更强大

  1. 相比run方法 可以有返回值
  2. 方法可以抛出异常
  3. 支持泛行的返回值
  4. 需要借助FutureTask类,比如获取返回结果
  1. import java.util.concurrent.Callable;
  2. import java.util.concurrent.ExecutionException;
  3. import java.util.concurrent.FutureTask;
  4. /**
  5. 1.创建Callable的实现类
  6. 2.实现call方法,并将线程需要执行的操作放在call方法内
  7. 3.创建Callable接口实现类的对象
  8. 4.将Callable接口实现类的对象作为参数传递到 FutureTask 的构造器当中,创建FutureTask的对象
  9. 5.将FutureTask的对象作为参数传递到Thread的构造器当中创建Thread对象并掉用start()方法
  10. 6.获取Callable call 方法的返回你
  11. */
  12. public class CallableTest {
  13. public static void main(String[] args) {
  14. ThreadNew tn = new ThreadNew();
  15. FutureTask task = new FutureTask(tn);
  16. new Thread(task).start();
  17. try {
  18. Object o = task.get();
  19. System.out.println(o);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. } catch (ExecutionException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. class ThreadNew implements Callable {
  28. @Override
  29. public Object call() throws Exception {
  30. int sum = 0;
  31. for (int i = 0; i < 100; i++) {
  32. if (i % 2 == 0) {
  33. sum += i;
  34. }
  35. }
  36. return sum;
  37. }
  38. }

方式四:线程池

  1. 提高响应速度
  2. 降低资源消耗
  3. 便于线程管理 ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPool { public static void main(String[] args) { //1.提供执行数量的线程池 ExecutorService eService = Executors.newFixedThreadPool(10); ThreadPoolExecutor service = (ThreadPoolExecutor) eService; service.setCorePoolSize(15); //2.执行指定线程的操作,需要实现Runnable或者Callable接口 service.execute(new MyThreadNew());//适用于 Runnable //service.submit();//适用于 Callable //关闭线程池 service.shutdown(); } }

class MyThreadNew implements Runnable {

  1. @Override
  2. public void run() {
  3. for (int i = 0; i < 100; i++) {
  4. if (i % 2 == 0) {
  5. System.out.println(i);
  6. }
  7. }
  8. }

}

  1. <a name="cVyJG"></a>
  2. #### 如何理解实现Callable接口创建多线程比实现Runnable接口创建多线程功能强大
  3. 1. call() 有返回值
  4. 1. call() 可以抛出异常
  5. 1. Callable支持泛型
  6. <a name="NQTva"></a>
  7. #### 线程优先级
  8. MIN_PRIORITY 1<br />MAX_PRIORITY 10<br />NORM_PRIORITY 5
  9. > 高优先级线程要抢占低优先级线程cpu的执行权,高优先级的线程高概率优先执行,并不意味着高优先级执行完以后才执行低优先级
  10. <br />
  11. <a name="8f6dede4"></a>
  12. #### 比较线程创建的两种方式
  13. 1. 开发中优先使用 实现 Runnable 接口 没有单继承的限制
  14. 1. 实现方式更适合来处理多个线程有共享数据的情况
  15. <a name="h2p6M"></a>
  16. #### 二者之间的联系
  17. 1. Thread 类也实现了Runnable 接口
  18. 1. 都需要重写 run()方法,将线程要执行的逻辑写在run()方法中
  19. <a name="128fl"></a>
  20. ## 3、线程的生命周期
  21. <a name="Fjs2r"></a>
  22. ### 新建
  23. <a name="HCvui"></a>
  24. ### 就绪
  25. <a name="dXf6x"></a>
  26. ### 运行
  27. <a name="jiENc"></a>
  28. ### 阻塞
  29. <a name="iXCtM"></a>
  30. ### 死亡
  31. <a name="rIK9g"></a>
  32. ## 4、线程的同步
  33. > 好处:解决线程安全的问题。
  34. > 局限性:操作同步代码时只能有一个线程参与,相当于但线程,效率低
  35. <a name="PPTsN"></a>
  36. ### 方式一:同步代码块
  37. synchronized (同步监视器){<br />//需要被同步的代码<br />}<br />说明:需要操作共享数据的代码,极为需要被同步的代码块<br />同步监视器:俗称锁,任何一个类的对象,都可以充当锁<br />要求所有线程都使用同一把锁。
  38. ```java
  39. public class WindowTest {
  40. public static void main(String[] args) {
  41. Window w = new Window();
  42. Thread t1 = new Thread(w);
  43. t1.setName("窗口一");
  44. t1.start();
  45. Thread t2 = new Thread(w);
  46. t2.setName("窗口二");
  47. t2.start();
  48. Thread t3 = new Thread(w);
  49. t3.setName("窗口三");
  50. t3.start();
  51. }
  52. }
  53. class Window implements Runnable {
  54. private int ticket = 100;
  55. @Override
  56. public void run() {
  57. while (true) {
  58. //这里可以是用this或者当前类的对象 Window.class
  59. synchronized (this) {
  60. if (ticket > 0) {
  61. System.out.println(Thread.currentThread().getName() + ":" + ticket);
  62. ticket--;
  63. } else {
  64. break;
  65. }
  66. }
  67. }
  68. }
  69. }

方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步方法

  1. public class SingletonTest {
  2. public static void main(String[] args) {
  3. System.out.println(MySingle.getInstance());
  4. System.out.println(MySingle.getInstance());
  5. }
  6. }
  7. class MySingle{
  8. private static MySingle instance = null;
  9. private MySingle() {
  10. }
  11. public static synchronized MySingle getInstance() {
  12. if (instance == null ){
  13. instance = new MySingle();
  14. }
  15. return instance;
  16. }
  17. }

死锁的问题: 不同的线程分别占用对方需要的资源不放弃,都在等待对方放弃自己所的资源,就形成了死锁。 出现死锁后不会出现异常,不会出现提示,只是所有的线程都处在阻塞状态,无法继续。

解决方法:

  1. 专门的算法、原则。
  2. 尽量减少同步资源的定义
  3. 尽量避免嵌套同步

    方式三:使用lock

    ```java import java.util.concurrent.locks.ReentrantLock;

public class WindowTest3 { public static void main(String[] args) { Window3 w = new Window3(); Thread t1 = new Thread(w); t1.setName(“窗口一”); t1.start(); Thread t2 = new Thread(w); t2.setName(“窗口二”); t2.start(); Thread t3 = new Thread(w); t3.setName(“窗口三”); t3.start(); } }

class Window3 implements Runnable { private int ticket = 100; private ReentrantLock lock = new ReentrantLock(true); @Override public void run() { while (true) { try { lock.lock(); if (ticket > 0) { System.out.println(Thread.currentThread().getName() + “:” + ticket); ticket—; } else { break; } } finally { lock.unlock(); } } } }

  1. <a name="bHTyE"></a>
  2. ### synchronized 和lock的异同?
  3. 相同: 都可以解决线程安全问题<br />不同: synchronized在执行响应的同步代码块以后,自动的释放同步监视器,lock需要手动启动同步,同时结束也需要手动实现,<br />lock 只有代码块锁,synchronized既有代码块锁,也有方法锁。
  4. <a name="DFYqV"></a>
  5. ## 5、线程的通信
  6. <a name="bR87D"></a>
  7. ### 练习:交替打印
  8. ```java
  9. public class NumberTest {
  10. public static void main(String[] args) {
  11. Number n = new Number();
  12. Thread t1 = new Thread(n);
  13. t1.setName("线程一:");
  14. Thread t2 = new Thread(n);
  15. t2.setName("线程二:");
  16. t1.start();
  17. t2.start();
  18. }
  19. }
  20. class Number implements Runnable {
  21. private int num = 1;
  22. @Override
  23. public void run() {
  24. while (true) {
  25. synchronized (this) {
  26. notify();
  27. if (num <= 100) {
  28. System.out.println(Thread.currentThread().getName() + ":" + num);
  29. num++;
  30. try {
  31. wait();
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }
  37. }
  38. }
  39. }

涉及到三个方法:

  1. wait() 一旦执行此方法,线程就会进入阻塞状态,同时释放锁
  2. notify() 执行此方法,就会唤醒一个被wati的线程 ,优先级高的优先被唤醒
  3. notifyAll() 执行此方法,就会唤醒所有被wait的线程

    说明: 1. wait() notify() notifyAll() 三个方法只能使用在同步方法、和同步代码块当中。 2.三个方法的调用者必须是同步代码块或 3.三个方法定义在Object类当中

面试题1:sleep 和wait的异同

相同点:一旦执行方法,都可以是当前线程进入阻塞状态。
不同点:1. 声明的位置不同
2.调用的范围要求不一样,sleep 可以在任何需要的场景调用,wait方法必须使用在同步方法和同步代码块中
3.两个方法都是用在同步方法块和同步方法中,sleep不会释放锁,wait会释放锁。