Java多线程
进程,线程以及多线程
想学习线程首先要搞懂进程,进程指的就是我们正在运行的某个应用程序,例如 QQ,微信以及自己编写的 java 程序运行起来之后都属于一个进程
线程是最小的运算调度单位,一个进程要求至少拥有一个线程,在单核处理器的情况下多线程是以交替形式运行的,并不是同时运行,只是切换的速度比较快而已,多核处理器也是同理,每个处理器都可以调用线程,可以实现同时运行
我们用 java 书写的控制台程序,它本身就是一个进程,mian 函数就是主线程,我们所有的代码都是基于 mian 线程按照顺序往下跑的,有些时候有那种计算量比较大的函数就会很浪费时间,这个时候我们就可以在创建一个线程将这个函数丢给他调用,就不影响主函数的执行时间了,例如发送邮件的时间延迟,我们就可以通过多线程的异步特点来实现
Thread 线程类常用方法
| 类型 | 
方法 | 
作用 | 
| 实例 | 
start(); | 
激活线程开始运行 | 
| 实例 | 
run(); | 
线程类必须复写的函数,包含该线程需要完成的逻辑代码等 | 
| 实例 | 
setName(String name) | 
修改当前线程名称 | 
| 实例 | 
setPriority(int priority) | 
设置线程的优先级 | 
| 实例 | 
setDaemon(boolean on) | 
设置守护守护线程 ( true ) 和用户线程 ( fasse ) | 
| 实例 | 
String getName() | 
获取当前线程名称 | 
| 实例 | 
int getPriority() | 
获取线程的优先级 | 
| 实例 | 
join() | 
暂定当前线程,由调用者开始下一次执行 | 
| 实例 | 
isAlive() | 
检测线程是否处于活动状态 | 
| —— | 
———————————— | 
—————————————————————————— | 
| 静态 | 
yield() | 
礼让:停止当前正在运行的线程,调用者插队执行 | 
| 静态 | 
sleep(long time) | 
睡眠:当前线程暂停运行,time 毫秒后继续执行 | 
| 静态 | 
boolean holdsLock(Object x) | 
当前线程在指定的对象上保持监视锁时反会 true | 
| 静态 | 
Thread currentThread() | 
返回当前正在执行的线程 | 
线程的创建方式
线程的创建有很多种,这里简单介绍常用的几种
继承 Thread 类
四部曲:继承 Thread 类 → 复写 run 方法 → 创建当前类对象 ( 多态:Thread 类型 ) → start 启动线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  public class MyThread1 extends Thread{     public static int count = 300;     public static void main(String[] args) {         new MyThread1().start();         for (int i = 0; i < count; i++) {             System.out.println("main 执行了" + (i + 1) + "次");         }
      }     @Override     public void run() {         for (int i = 0; i < count; i++) {             System.out.println("【多线程执行】" + (i + 1) + "次");         }     } }
 
  | 
 
实现 Runnable 接口
四部曲:实现 Runnable 接口 → 复写 run 方法 → 创建线程类对象 ( 将当前类示例交给他 ) → start 启动线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | public class MyThread2 implements Runnable {     @Override     public void run() {         for (int i = 0; i < 200; i++) {             System.out.println("【多线程执行】" + (i + 1) + "次");         }     }     public static void main(String[] args) {         new Thread( new MyThread2() ).start();         for (int i = 0; i < 200; i++) {             System.out.println("main 执行了" + (i + 1) + "次");         }     } }
 
  | 
 
上述代码可以使用 Lambda 表达式快速创建线程:
1 2 3 4 5 6 7 8 9 10 11 12 13
   | public class MyThread2 {     public static void main(String[] args) {         int count = 300;         new Thread( ()->{             for (int i = 0; i < count; i++) {                 System.out.println("【多线程执行】执行了" + (i + 1) + "次");             }         }).start();         for (int i = 0; i < count; i++) {             System.out.println("main 执行了" + (i + 1) + "次");         }     } }
 
  | 
 
继承 Thread 类和实现 Runnable 接口的区别
我们来写一个卖票的小练习来了解一下两种线程创建的区别
Runnable 接口实现的多线程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   |  public class MyBuyTicket implements Runnable{
      public int ticketNum = 20;
      public static void main(String[] args) {         MyBuyTicket mb = new MyBuyTicket();         Thread t1 = new Thread(mb);         Thread t2 = new Thread(mb);         Thread t3 = new Thread(mb);         t1.setName("老八");         t2.setName("卢本伟");         t3.setName("菜虚鲲");         t1.start();         t2.start();         t3.start();     }
      @Override     public void run() {         while ( ticketNum > 0 ){             ticketNum--;                          System.out.println("【" +                                 Thread.currentThread().getName() + "】抢到了一张票");         }     }
  }
 
  | 
 
Thread 类实现的多线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | public class MyBuyTicket extends Thread{
      public int ticketNum = 20;
      public static void main(String[] args) {         Thread t1 = new MyBuyTicket();         Thread t2 = new MyBuyTicket();         Thread t3 = new MyBuyTicket();         t1.setName("老八");         t2.setName("卢本伟");         t3.setName("菜虚鲲");         t1.start();         t2.start();         t3.start();     }
      @Override     public void run() {         while ( ticketNum > 0 ){             ticketNum--;             System.out.println("【" +                                 Thread.currentThread().getName() + "】抢到了一张票");         }     }
  }
 
  | 
 
通过这个小练习我们可以看出,Thread 每创建一个线程相应的就必须创建一个对象,而 Runnable 可以实现多个线程操作同一个对象,共享对象内的数据,使用多线程的情况下,推荐优先使用 Runnable 接口
线程的生命周期
线程从创建到结束共分为大概五个生命周期:
- 【新建】:线程被创建出来,也就是 
new Thread() 
- 【就绪】:线程已经创建完毕并且启动了,也就是已经调用 
start() 方法了,随时等待 CPU 的调度 
- 【运行】:线程被 CPU 调度,正在执行 run 方法内的逻辑
 
- 【阻塞】:没有执行的资格和执行权利,例如 
sleep(long time) 
- 【销毁】:线程任务已经完成,没有再次使用的地方,线程的对象变成垃圾,释放资源。
 
正常线程的流程就是【新建】→【就绪】→【运行】→【可能会有阻塞】→【就绪】→【运行】…→【销毁】
线程睡眠
线程睡眠是指暂时让线程停止运行,在约定时间内不接收 CPU 的调度,用法:Thread.sleep(long time)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   |  public class MyThread3 implements Runnable{     @Override     public void run() {         for ( int time = 1; time <= 10; time++ ){             System.out.println( "本站已不间断运行" + time + "秒");             try {                 Thread.sleep(1000);             } catch (InterruptedException e) {                 System.out.println("线程停止失败!");                 e.printStackTrace();             }         }     }     public static void main(String[] args) {         new Thread(new MyThread3()).start();     } }
 
  | 
 
结束运行
让线程停止运行,JDK 提供了 stop 实例函数进行停止:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   |  public class MyThread3 implements Runnable{     @Override     public void run() {         for ( int count = 1; true; count++ )             System.out.println("线程跑了" + count + "次");     }     public static void main(String[] args) throws InterruptedException {         MyThread3 mt = new MyThread3();         Thread t1 = new Thread(mt);         t1.start();         Thread.sleep(1000);         t1.stop();     } }
 
  | 
 
经测试这样是可以停止线程的,但是官方并不建议我们这么使用,推荐自己定义一个标识在外部控制停止:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | public class MyThread3 implements Runnable{     public boolean flag = true;     @Override     public void run() {         for ( int count = 1; flag; count++ )             System.out.println("线程跑了" + count + "次");     }     public void stop(){         this.flag = false;         System.out.println("线程停止成功");     }     public static void main(String[] args) throws InterruptedException {         MyThread3 mt3 = new MyThread3();         new Thread(mt3).start();         Thread.sleep(1000);         mt3.stop();     } }
 
  | 
 
线程礼让
我们都知道线程是被 CPU 随机调度的,两个线程在一起的时候,如果想让另一个执行的多一点可以使用线程礼让,线程礼让属于阻塞的函数,需要注意的是,礼让仅仅是放弃当此调度,CPU 会重新从两个线程中再选出一个进行调度,选中的可能还是之前的线程,也就是说,线程礼让并不会 100% 成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   |  public class MyThread3 implements Runnable{     @Override     public void run() {         for ( int i = 1; i <= 100; i++ ){                          if( i >= 90 && Thread.currentThread().getName().equals("老八") ){                 System.out.println("【老八选择礼让】");                 Thread.yield();             }             System.out.println( "【" + Thread.currentThread().getName()                                 + "】运行了" + i + "次");         }     }     public static void main(String[] args) {         MyThread3 mt3 = new MyThread3();         Thread t1 = new Thread(mt3);         Thread t2 = new Thread(mt3);         t1.setName("老八");         t2.setName("菜虚鲲");         t1.start();         t2.start();     }
 
  | 
 
线程等待
正在运行的线程中如果调用了 join 函数就会停止,下一个运行的函数就是 join 的调用者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   | public class MyThread3 implements Runnable{     @Override     public void run() {         for ( int i = 1; i <= 1000; i++ ){             try {                 Thread.sleep(1);             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println( "【" + Thread.currentThread().getName()                                + "】运行了" + i + "次");         }     }     public static void main(String[] args) throws InterruptedException {         Thread t1 = new Thread(new MyThread3());         Thread t2 = new Thread(new MyThread3());         t1.setName("老八");         t2.setName("菜虚鲲");         t1.start();         t2.start();         for (int i = 0; i < 1000; i++) {             Thread.sleep(1);             System.out.println( "【main线程】运行了" + i + "次");             if ( i == 500 ){                 System.out.println("main已经跑到500,T来插个队");                                  t1.join();             }         }     } }
 
  | 
 
线程优先级
当我们创建了多个线程同时启动的时候,他们会被 CPU 随机调度,每次执行的顺序都不同,这里我们可以通过设置优先级来决定执行的先后顺序
setPriority(int priority):设置线程的优先级,需要 int 类型的值作为优先级参数,默认优先级为 5,取值范围为 1-10,数值越大对应的优先级越高,可以通过 setPriority() 来获取线程优先级,需要注意的是优先级高的线程会在调度时被给予优先,但这不代表启动后就会运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   | public class MyThread3 extends Thread{
      public String msg;
      public MyThread3(String msg) { this.msg = msg; }
      @Override     public void run() {         System.out.println(this.msg);     }
      public static void main(String[] args) {         Thread t1 = new MyThread3("我想第1个执行");         Thread t2 = new MyThread3("我想第2个执行");         Thread t3 = new MyThread3("我想第3个执行");         Thread t4 = new MyThread3("我想第4个执行");         Thread t5 = new MyThread3("我想第5个执行");         t1.setPriority(5);         t2.setPriority(4);         t3.setPriority(3);         t4.setPriority(2);         t5.setPriority(1);         t1.start();         t2.start();         t3.start();         t4.start();         t5.start();     }
  }
 
  | 
 
误区:并不是优先级高执行结束后在执行优先级低的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | public class MyThread3 extends Thread{
      public MyThread3(String name) { super(name); }
      @Override     public void run() {         for (int count = 0; count < 1000; count++)             System.out.println("【" + this.getName() + "】执行了" + count + "次");     }
      public static void main(String[] args) {         Thread t1 = new MyThread3("老八");         Thread t2 = new MyThread3("菜虚鲲");         t1.setPriority(6);         t1.start();         t2.start();     }
  }
 
  | 
 
用户线程和守护线程
线程中包含了两种类型,为用户线程和守护线程,用户线程可以在任务未完成的情况下可以无休止的运行,而守护线程则是主线程 ( mian ) 运行结束后关机 JVM 虚拟机时就跟着停止运行了,我们创建的线程没有修改过的话默认都是用户线程,修改线程类型的函数为 setDaemon(),用户线程为 false,守护线程为 true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  public class MyThread4 extends Thread{
      @Override     public void run() {         for (int i = 1; i <= 100000; i++)             System.out.println("线程运行了" + i + "次");     }
      public static void main(String[] args) throws InterruptedException {                  Thread t1 = new MyThread4();         t1.start();     }
  }
 
 
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   |  public class MyThread4 extends Thread{
      @Override     public void run() {         for (int i = 1; i <= 100000; i++)             System.out.println("线程运行了" + i + "次");     }
      public static void main(String[] args) throws InterruptedException {         Thread t1 = new MyThread4();                  t1.setDaemon(true);         t1.start();                  Thread.sleep(500);     }
  }
 
  | 
 
结果:用户线程等待 10w 条数据打印完成才结束程序,而守护线程打印到 4w 多的时候就结束了程序