1、线程的创建和启动

Java虚拟机的主线程入口是main方法,用户可以自己创建线程,创建方式有两种:

  1. 继承Thread类
  2. 实现Runnable接口(推荐使用Runnable接口)

    1.1、继承Thread类

    Thread类中创建线程最重要的两个方法为:
public void run()
public void start()

采用Thread类创建线程,用户只需要继承Thread,覆盖Thread中的run方法,父类Thread中的run方法没有抛出异常,那么子类也不能抛出异常,最后采用start启动线程即可

  1. public class ThreadTest01 {
  2. public static void main(String[] args) {
  3. Processor p = new Processor();
  4. p.run();
  5. method1();
  6. }
  7. private static void method1() {
  8. System.out.println("--------method1()----------");
  9. }
  10. }
  11. class Processor {
  12. public void run() {
  13. for (int i=0; i<10; i++) {
  14. System.out.println(i);
  15. }
  16. }
  17. }

以上顺序输出相应的结果(属于串行),也就是run方法完全执行完成后,才执行method1方法,也就是method1必须等待前面的方法返回才可以得到执行,这是一种“同步编程模型”

  1. public class ThreadTest02 {
  2. public static void main(String[] args) {
  3. Processor p = new Processor();
  4. //手动调用该方法
  5. //不能采用run来启动一个场景(线程),
  6. //run就是一个普通方法调用
  7. //p.run();
  8. //采用start启动线程,不是直接调用run
  9. //start不是马上执行线程,而是使线程进入就绪
  10. //线程的正真执行是由Java的线程调度机制完成的
  11. p.start();
  12. //只能启动一次
  13. //p.start();
  14. method1();
  15. }
  16. private static void method1() {
  17. System.out.println("--------method1()----------");
  18. }
  19. }
  20. class Processor extends Thread {
  21. //覆盖Thread中的run方法,该方法没有异常
  22. //该方法是由java线程掉机制调用的
  23. //我们不应该手动调用该方法
  24. public void run() {
  25. for (int i=0; i<10; i++) {
  26. System.out.println(i);
  27. }
  28. }
  29. }

通过输出结果大家会看到,没有顺序执行,而在输出数字的同时执行了method1()方法,如果从效率上看,采用多线程的示例要快些,因为我们可以看作他是同时执行的,mthod1()方法没有等待前面的操作完成才执行,这叫“异步编程模型”

1.2、实现Runnable接口

其实Thread对象本身就实现了Runnable接口,但一般建议直接使用Runnable接口来写多线程程序,因为接口会比类带来更多的好处

  1. public class ThreadTest03 {
  2. public static void main(String[] args) {
  3. //Processor r1 = new Processor();
  4. Runnable r1 = new Processor();
  5. //不能直接调用run
  6. //p.run();
  7. Thread t1 = new Thread(r1);
  8. //启动线程
  9. t1.start();
  10. method1();
  11. }
  12. private static void method1() {
  13. System.out.println("--------method1()----------");
  14. }
  15. }
  16. //实现Runnable接口
  17. class Processor implements Runnable {
  18. //实现Runnable中的run方法
  19. public void run() {
  20. for (int i=0; i<10; i++) {
  21. System.out.println(i);
  22. }
  23. }
  24. }

2、线程的生命周期

线程的生命周期存在五个状态:新建、就绪、运行、阻塞、死亡
1648304455(1).png
新建:采用new语句创建完成
就绪:执行start后
运行:占用CPU时间
阻塞:执行了wait语句、执行了sleep语句和等待某个对象锁,等待输入的场合
终止:退出run()方法

有的书籍上对线程的生命周期是做如下定义的。
1648304489(1).png

3、线程的调度与控制

通常我们的计算机只有一个CPU,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。在单CPU的机器上线程不是并行运行的,只有在多个CPU上线程才可以并行运行。Java虚拟机要负责线程的调度,取得CPU的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,Java使用抢占式调度模型。
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(优先级高的线程获取CPU的时间片多一些。)

3.1、线程优先级

线程优先级主要分三种:MAX_PRIORITY(最高级);MIN_PRIORITY(最低级)NOM_PRIORITY(标准)默认

  1. public class ThreadTest04 {
  2. public static void main(String[] args) {
  3. Runnable r1 = new Processor();
  4. Thread t1 = new Thread(r1, "t1");
  5. //设置线程的优先级,线程启动后不能再次设置优先级
  6. //必须在启动前设置优先级
  7. //设置最高优先级
  8. t1.setPriority(Thread.MAX_PRIORITY);
  9. //启动线程
  10. t1.start();
  11. //取得线程名称
  12. //System.out.println(t1.getName());
  13. Thread t2 = new Thread(r1, "t2");
  14. //设置最低优先级
  15. t2.setPriority(Thread.MIN_PRIORITY);
  16. t2.start();
  17. System.out.println(Thread.currentThread().getName());
  18. }
  19. }
  20. class Processor implements Runnable {
  21. public void run() {
  22. for (int i=0; i<100; i++) {
  23. System.out.println(Thread.currentThread().getName() + "," + i);
  24. }
  25. }
  26. }

从以上输出结果应该看可以看出,优先级高的线程(t1)会得到的CPU时间多一些,优先执行完成

3.2、Thread.sleep

sleep设置休眠的时间,单位毫秒,当一个线程遇到sleep的时候,就会睡眠,进入到阻塞状态,放弃CPU,腾出cpu时间片,给其他线程用,所以在开发中通常我们会这样做,使其他的线程能够取得CPU时间片,当睡眠时间到达了,线程会进入可运行状态(就绪状态),得到CPU时间片继续执行,如果线程在睡眠状态被中断了,将会抛出InterruptedException

  1. public class ThreadTest05 {
  2. public static void main(String[] args) {
  3. Runnable r1 = new Processor();
  4. Thread t1 = new Thread(r1, "t1");
  5. t1.start();
  6. Thread t2 = new Thread(r1, "t2");
  7. t2.start();
  8. }
  9. }
  10. class Processor implements Runnable {
  11. public void run() {
  12. for (int i=0; i<100; i++) {
  13. System.out.println(Thread.currentThread().getName() + "," + i);
  14. if (i % 10 == 0) {
  15. try {
  16. //睡眠100毫秒,主要是放弃CPU的使用,将CPU时间片交给其他线程使用
  17. Thread.sleep(100);
  18. }catch(InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  23. }
  24. }

3.3、Thread中几个方法、属性

1、wait、notify、notifyAll

等待/通知的相关方法描述如下
图片1.png

  1. public class WaitNotifyTest {
  2. public static void main(String[] args) {
  3. /**
  4. * 这里可以任意的对象,每个对象都有一个锁
  5. * 当这个锁被一个线程A获取到,其他线程不能再获取,直到线程A执行释放其他线程才能获取
  6. * 执行wait方法可以释放锁,其他线程可以拿到锁执行通知
  7. */
  8. Object lock = new Object();
  9. new Thread(new Runnable() {
  10. @Override
  11. public void run() {
  12. System.out.println("线程A等待获取lock锁");
  13. synchronized (lock) {
  14. try {
  15. System.out.println("线程A获取了lock锁");
  16. System.out.println("线程A将要运行lock.wait()方法进行等待");
  17. lock.wait();//执行等待,一直等待通知
  18. System.out.println("线程A等待结束");
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. }
  24. }).start();
  25. new Thread(new Runnable() {
  26. @Override
  27. public void run() {
  28. System.out.println("线程B等待获取lock锁");
  29. synchronized (lock) {
  30. System.out.println("线程B获取了lock锁");
  31. System.out.println("线程B将要运行lock.notify()方法进行通知");
  32. lock.notify();
  33. }
  34. }
  35. }).start();
  36. }
  37. }

2、yield

Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉

让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。

yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保

证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

  1. public class YieldTest {
  2. public static void main(String[] args) {
  3. Runnable p1 = new YieldProcessor();
  4. Thread t1 = new Thread(p1);
  5. t1.start();
  6. Runnable p2 = new YieldProcessor();
  7. Thread t2 = new Thread(p1);
  8. t2.start();
  9. }
  10. }
  11. class YieldProcessor implements Runnable{
  12. @Override
  13. public void run() {
  14. String threadName = Thread.currentThread().getName();
  15. System.out.println("当前线程名称:" + threadName);
  16. if("Thread-0".equals(threadName)){
  17. /**
  18. * 让步:但是CPU实际调用也是按照自身去调用,不会完全让步
  19. */
  20. Thread.yield();
  21. }
  22. }
  23. }

3、join

join()方法是Thread类中的一个方法,该方法的定义是等待该线程终止。其实就是join()方法将挂起调用线程的执行,直到被调用的对象完成它的执行。
注:让当前线程执行完,后面的线程再去执行

  1. /**
  2. * 现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
  3. */
  4. public class JoinDemo {
  5. public static void main(String[] args) {
  6. //初始化线程t1,由于后续有匿名内部类调用这个对象,需要用final修饰
  7. final Thread t1 = new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. System.out.println("t1 is running");
  11. }
  12. });
  13. //初始化线程t2,由于后续有匿名内部类调用这个对象,需要用final修饰
  14. final Thread t2 = new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. try {
  18. //t1调用join方法,t2会等待t1运行完之后才会开始执行后续代码
  19. t1.join();
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. } finally {
  23. System.out.println("t2 is running");
  24. }
  25. }
  26. });
  27. //初始化线程t3
  28. Thread t3 = new Thread(new Runnable() {
  29. @Override
  30. public void run() {
  31. try {
  32. //t2调用join方法,t3会等待t2运行完之后才会开始执行后续代码
  33. t2.join();
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. } finally {
  37. System.out.println("t3 is running");
  38. }
  39. }
  40. });
  41. //依次启动3个线程
  42. t1.start();
  43. t2.start();
  44. t3.start();
  45. }
  46. }

4、interrupt、isInterrupted

一看到线程的interrupt()方法,根据字面意思,很容易将该方法理解为中断线程。其实Thread.interrupt()并不会中断线程的运行,它的作用仅仅是为线程设定一个状态而已,即标明线程是中断状态,这样线程的调度机制或我们的代码逻辑就可以通过判断这个状态做一些处理,比如sleep()方法会抛出异常,或是我们根据isInterrupted()方法判断线程是否处于中断状态,然后做相关的逻辑处理。
对于底层代码native可以查看OpenJDK源代码

public boolean isInterrupted() 测试线程是否已经中断。线程的中断状态不受该方法的影响。
public void interrupt() 中断线程。
  1. public class InterruptTest {
  2. public static void main(String[] args) {
  3. Processor3 processor3 = new Processor3();
  4. Thread t = new Thread(processor3);
  5. t.start();
  6. t.interrupt();
  7. }
  8. }
  9. class Processor3 extends Thread{
  10. @Override
  11. public void run() {
  12. System.out.println(this.isInterrupted());
  13. try {
  14. Thread.sleep(5 * 1000);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }