多线程概念

简单理解就是同时做多个事情,而不是一件一件的排队做,比如上厕所的时候玩手机。。。
多线程与平时的方法有啥区别呢?
image.png
可以看出,多线程在执行的效率上是高于单线程

线程与进程

进程简单理解是一个IDEA运行、一个微信运行等,在操作系统中运行的程序就称为进程,但是一个微信中可以包括多个事务的运行,如聊天的同时接收到他人消息、订阅号通知等。可见,线程与进程的关系为:一个进程包括多个线程
进程 :执行程序的一次执行过程,动态概念,是系统资源分配的单位。
线程 :CPU调度和执行的单位,一个进程中至少包含一个线程。

注意:

  • 很多多线程是模拟出来,真正的多线程是指有多个_CPU_(服务器),单个CPU情况下,同一时间点只能执行一个代码,但切换很快,所以出现同时执行的错觉。
  • 程序运行时,即使没有自己创建线程,后台也会有多个线程,main线程、gc线程。
  • 同一个进程中,如果开辟多线程,线程的运行由调度器安排调度,调度器与系统相关,执行的先后顺序不能人为干预。
  • 对同一份资源操作时,存在资源抢夺问题,需要加入并发控制。
  • 线程会带来额外的开销,CPU调度时间等。

线程创建

线程创建方式主要为以下三种方式

继承Thread

创建步骤

  • 自定义线程类继承Thread
  • 重写run()方法,线程执行体编写
  • 创建线程对象,调用start()方法执行

    举个栗子

    本栗实现简单的线程创建和执行,线程执行顺序由调度器安排,无法人为控制 ```java /**

    • @author 相彪 */ public class OneThread extends Thread{

      private String name;

      public OneThread(String name) {

      1. this.name = name;

      }

      @Override public void run() {

      1. for (int i = 0; i < 20; i++) {
      2. try {
      3. Thread.sleep(1000);
      4. System.out.println(name + " running : " + i);
      5. } catch (InterruptedException e) {
      6. e.printStackTrace();
      7. }
      8. }

      }

      public static void main(String[] args) {

      1. OneThread oneThread = new OneThread("Thread 1 ");
      2. OneThread twoThread = new OneThread("Thread 2 ");
      3. OneThread threeThread = new OneThread("Thread 3 ");
      4. OneThread fourThread = new OneThread("Thread 4 ");
      5. OneThread fiveThread = new OneThread("Thread 5 ");
      6. oneThread.start();
      7. twoThread.start();
      8. threeThread.start();
      9. fourThread.start();
      10. fiveThread.start();

      } }

// 部分结果 Thread 5 running : 17 Thread 3 running : 17 Thread 1 running : 17 Thread 2 running : 17 Thread 4 running : 17 Thread 4 running : 18 Thread 2 running : 18 Thread 1 running : 18 Thread 3 running : 18 Thread 5 running : 18

从结果可见,线程不能按照代码编写的顺序执行

  1. <a name="Xzzov"></a>
  2. ## 实现Runnable
  3. <a name="g37C7"></a>
  4. ### 创建步骤
  5. - 定义一个类实现`Runnable`接口
  6. - 实现`run()`方法,内容编写
  7. - 创建线程对象,调用`start()`方法启动线程
  8. <a name="I3DEB"></a>
  9. ### 举个栗子
  10. ```java
  11. class RunnableThread implements Runnable{
  12. private String name;
  13. public RunnableThread(String name) {
  14. this.name = name;
  15. }
  16. @Override
  17. public void run() {
  18. for (int i = 0; i < 20; i++) {
  19. try {
  20. Thread.sleep(1000);
  21. System.out.println(name + " running : " + i);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. public static void main(String[] args) {
  28. RunnableThread oneThread = new RunnableThread("Thread 1 ");
  29. RunnableThread twoThread = new RunnableThread("Thread 2 ");
  30. RunnableThread threeThread = new RunnableThread("Thread 3 ");
  31. RunnableThread fourThread = new RunnableThread("Thread 4 ");
  32. RunnableThread fiveThread = new RunnableThread("Thread 5 ");
  33. new Thread(oneThread).start();
  34. new Thread(twoThread).start();
  35. new Thread(threeThread).start();
  36. new Thread(fourThread).start();
  37. new Thread(fiveThread).start();
  38. }
  39. }

该栗子在Thread基础上修改,同样的,得出结果可见线程执行不会按照编写顺序。

对比

两种方式都具备多线程能力,但启动方式不同
继承Thread:子类对象.start()
实现Runnable:传入目标对象+Thread对象.start()
但实现Runnable更推荐,可以避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用。

实例变量与线程安全

自定义线程类中的实例变量针对其他线程存在共享与不共享的区别。

不共享

各自线程都拿到各自的变量进行操作,不互相影响

  1. public class MyThread extends Thread {
  2. private int count = 5;
  3. public MyThread(String name) {
  4. super();
  5. this.setName(name);
  6. }
  7. @Override
  8. public void run() {
  9. super.run();
  10. while (count > 0) {
  11. count--;
  12. System.out.println("�� " + this.currentThread().getName()
  13. + " ���的count=" + count);
  14. }
  15. }
  16. }
  17. public class Run {
  18. public static void main(String[] args) {
  19. MyThread a = new MyThread("A");
  20. MyThread b = new MyThread("B");
  21. MyThread c = new MyThread("C");
  22. a.start();
  23. b.start();
  24. c.start();
  25. }
  26. }

从结果可见,三个线程都有各自的变量,自己减少自己的变量值。
image.png

共享

也就是多个线程公用一个变量,容易想到的是购买商品时的库存数量变化
image.png
如果还执行上面的代码,那么count数量会凌乱。在某些JVM中进行i--分为三步

  1. 获取原有i值
  2. 计算i-1
  3. 对i进行赋值

在以上3个步骤中,若多个线程同时访问,一定会出现线程安全问题。可对上述代码进行如下更改:

  1. public class MyThread extends Thread {
  2. private int count = 5;
  3. @Override
  4. synchronized public void run() {
  5. super.run();
  6. count--;
  7. System.out.println("�� " + this.currentThread().getName()
  8. + " ���的count=" + count);
  9. }
  10. }

但此时还需要注意另一个问题:i--System.out.println
println源码如下:

  1. public void println(String x) {
  2. synchronized (this) {
  3. print(x);
  4. newLine();
  5. }
  6. }

可见它是线程全的,但是i--并非线程安全,需要进一步优化,如改成AtomicInteger

方法

currentThread()

返回代码段正在被哪个线程调用的信息,其下也有众多常用方法:

isAllive

判断当前线程是否处于活跃状态

  1. class MyThread extends Thread {
  2. @Override
  3. public void run() {
  4. System.out.println("run=" + this.isAlive());
  5. }
  6. }
  7. public static void main(String[] args) {
  8. MyThread mythread = new MyThread();
  9. System.out.println("begin ==" + mythread.isAlive());
  10. mythread.start();
  11. System.out.println("end ==" + mythread.isAlive());
  12. }

sleep

在指定的毫秒数内让当前正在执行的线程休眠(暂停运行),该线程指 this.currentThread()返回的线程

  1. public class MyThread1 extends Thread {
  2. @Override
  3. public void run() {
  4. try {
  5. System.out.println("run threadName="
  6. + this.currentThread().getName() + " begin");
  7. Thread.sleep(2000);
  8. System.out.println("run threadName="
  9. + this.currentThread().getName() + " end");
  10. } catch (InterruptedException e) {
  11. // TODO Auto-generated catch block
  12. e.printStackTrace();
  13. }
  14. }
  15. }

getId

返回线程的唯一标识

停止线程

java中有以下3种方法可以终止正在运行的线程

  1. 使用退出标志,让线程正常退出,即run方法完成后退出
  2. 使用stop强行终止(不推荐、已过期,会产生不可预料结果)
  3. 使用interrupt方法中断线程

    判断线程是否停止

    Thread中提供了2种方法来判断

  4. this.interrupted():当前线程是否已经中断(当前线程:指运行this.interrupted的线程)

    1. public static boolean interrupted() {
    2. return currentThread().isInterrupted(true);
    3. }

    示例 ```java public class MyThread extends Thread { @Override public void run() {

    1. super.run();
    2. for (int i = 0; i < 500000; i++) {
    3. System.out.println("i=" + (i + 1));
    4. }

    } }

public class Run2 { public static void main(String[] args) { Thread.currentThread().interrupt(); System.out.println(“是否停止1=” + Thread.interrupted()); System.out.println(“是否停止2=” + Thread.interrupted()); System.out.println(“end!”); } }

// 结果 是否停止1=true 是否停止2=false end!

  1. 从结果来看,方法interrupted确实判断出当前线程是否为停止状态,但为什么第2个布尔值为false:测试当前线程是否已经中断,线程中的中断状态由该方法清除。也就是说,如果连续两次调用该方法,则第二次返回false。<br />interrupted方法具有清除状态的功能,所以第二次调用返回false
  2. 2. `this.isInterrupted()`:线程是否已经中断
  3. ```java
  4. public boolean isInterrupted() {
  5. return isInterrupted(false);
  6. }

与前者不同的时,该方法不会清除状态,也就是两个打印都为true

停止线程-异常法

  1. 在run方法中使用for+break退出

示例:

  1. public class MyThread extends Thread {
  2. @Override
  3. public void run() {
  4. super.run();
  5. for (int i = 0; i < 500000; i++) {
  6. if (this.interrupted()) {
  7. System.out.println("停止状态!退出!");
  8. break;
  9. }
  10. System.out.println("i=" + (i + 1));
  11. }
  12. }
  13. }
  14. public static void main(String[] args) {
  15. try {
  16. MyThread thread = new MyThread();
  17. thread.start();
  18. Thread.sleep(2000);
  19. thread.interrupt();
  20. } catch (InterruptedException e) {
  21. System.out.println("main catch");
  22. e.printStackTrace();
  23. }
  24. System.out.println("end!");
  25. }

上述方法虽然可以停止线程,但是如果for下面还有语句,还是会继续运行,最好的解决办法是在判断中断内抛出异常,交由上一层处理

  1. @Override
  2. public void run() {
  3. super.run();
  4. try {
  5. for (int i = 0; i < 500000; i++) {
  6. if (interrupted()) {
  7. System.out.println("停止状态!退出!");
  8. throw new InterruptedException();
  9. }
  10. System.out.println("i=" + (i + 1));
  11. }
  12. System.out.println("继续");
  13. } catch (InterruptedException e) {
  14. System.out.println("进入异常范围");
  15. e.printStackTrace();
  16. }
  17. }

停止线程-暴力停止

stop方法停止线程,但是该方法不推荐,因为会产生额外的问题,已作废。
如下简单示例

  1. public class MyThread extends Thread {
  2. @Override
  3. public void run() {
  4. try {
  5. this.stop();
  6. } catch (ThreadDeath e) {
  7. System.out.println("catch()");
  8. e.printStackTrace();
  9. }
  10. }
  11. }
  12. public static void main(String[] args) {
  13. MyThread thread = new MyThread();
  14. thread.start();
  15. }

结果:
image.png
该方法已被作废,如果强制让线程停止,则有可能让一些清理性的工作无法完成。另一种情况是对加锁的对象进行“解锁”,导致数据得不到同步处理。其在功能上具有缺陷,所以不建议在程序中使用该方法。

暂停线程

暂停就意味着线程还可以恢复运行,可以使用suspend和resume来进行暂停、恢复线程
使用

  1. public class MyThread extends Thread {
  2. private long i = 0;
  3. public long getI() {
  4. return i;
  5. }
  6. public void setI(long i) {
  7. this.i = i;
  8. }
  9. @Override
  10. public void run() {
  11. while (true) {
  12. i++;
  13. }
  14. }
  15. }
  16. public class Run {
  17. public static void main(String[] args) {
  18. try {
  19. MyThread thread = new MyThread();
  20. thread.start();
  21. Thread.sleep(5000);
  22. // A
  23. thread.suspend();
  24. System.out.println("A= " + System.currentTimeMillis() + " i="
  25. + thread.getI());
  26. Thread.sleep(5000);
  27. System.out.println("A= " + System.currentTimeMillis() + " i="
  28. + thread.getI());
  29. // B
  30. thread.resume();
  31. Thread.sleep(5000);
  32. // C
  33. thread.suspend();
  34. System.out.println("B= " + System.currentTimeMillis() + " i="
  35. + thread.getI());
  36. Thread.sleep(5000);
  37. System.out.println("B= " + System.currentTimeMillis() + " i="
  38. + thread.getI());
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. }

image.png
从结果可见,线程的确被暂停,而且还可以恢复成运行状态,但是有弊端

独占

如果使用不当,容易造成公共的同步对象独占,导致其他线程无法访问到同步对象。

不同步

使用时容易出现因为线程的暂停导致数据不同步的情况。
目前,suspend和resume已经被标为作废。

yield

放弃当前的CPU资源,让给其他线程占用,但放弃的时间不确定,有可能刚刚放弃,又会立刻获得CPU时间片。

  1. public class MyThread extends Thread {
  2. @Override
  3. public void run() {
  4. long beginTime = System.currentTimeMillis();
  5. int count = 0;
  6. for (int i = 0; i < 50000000; i++) {
  7. // Thread.yield();
  8. count = count + (i + 1);
  9. }
  10. long endTime = System.currentTimeMillis();
  11. System.out.println("用时:" + (endTime - beginTime));
  12. }
  13. }
  14. public static void main(String[] args) {
  15. MyThread thread = new MyThread();
  16. thread.start();
  17. }

未放开注释:
image.png
放开注释:
image.png

优先级

线程的优先级分为1-10这10个等级,设置优先级源码

  1. public final void setPriority(int newPriority) {
  2. ThreadGroup g;
  3. checkAccess();
  4. if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
  5. throw new IllegalArgumentException();
  6. }
  7. if((g = getThreadGroup()) != null) {
  8. if (newPriority > g.getMaxPriority()) {
  9. newPriority = g.getMaxPriority();
  10. }
  11. setPriority0(priority = newPriority);
  12. }
  13. }

注意:

  1. 线程的优先级具有继承性,如A线程启动B线程,则B线程与A线程优先级一样。
  2. 但当优先级的等级差距很大时,谁先执行完和代码的调用顺序无关,CPU尽量将执行资源让给优先级较高的线程执行,但不是绝对,优先级具有一定的规则性。
  3. 优先级高的线程并不一定要执行完再执行优先级低的线程,
  4. 优先级高的线程运行的较快

守护线程

任何一个守护线程都是整个JVM中非守护线程的保姆,当所有其他线程都结束工作时,它才结束。