Java高级特性:多线程

236 阅读8分钟

1.线程的创建、使用

方法1: 继承Thread类的子类

 /**
  * 多线程创建
  * 方式1:继承Thread类的子类
  * 1.创建子类
  * 2.重写Thread的run方法 ——》将此线程执行的操作声明在run()中
  * 3.实例化该子类的对象
  * 4.通过子类调用start方法
  *
  * @author shkstart
  * @create 2022-11-02 下午 10:56
  */
 //1.创建子类
 class MyThread extends Thread {
         //2.重写Thread的run方法
     public void run() {
         for(int i = 0; i < 100; i++){
                 if(i % 2 == 0)
                         System.out.println(i);
         }
     }
 }
 ​
 ​
 public class ThreadTest {
         public static void main(String[] args) {
                 //3.实例化该子类的对象
                 MyThread test = new MyThread();
                 //4.通过子类调用start方法
                 test.start();
         }
 }
  

start()的作用:

1.启动当前线程;2.调用当前线程的run();

注意:

  1. 不能直接通过子线程的对象调用run方法(此时还在main的线程中执行)
  2. 不能让已经调用start方法的线程再次调用start方法

方法2:创建实现Runnable接口的类

 package com.coderleo.java;
 ​
 /**
  * 1.创建实现Runnable接口的类
  * 2.实现类去实现Runnable中的run()
  * 3.创建实现类的对象
  * 4.将此对象作为参数,传递到Thread类的构造器,创建Thread类的对象
  * 5.通过Thread类的对象调用start()
  *
  * @author shkstart
  * @create 2022-11-03 下午 6:27
  */
 ​
 ​
 public class ThreadTest2 {
     public static void main(String[] args) {
         //3.创建实现类的对象
         Mthread test = new Mthread();
         //4.将此对象作为参数,传递到Thread类的构造器,创建Thread类的对象
         Thread test2 = new Thread(test);
         //5.通过Thread类的对象调用start()
         test2.run();
     }
 }
 ​
 //1.创建实现Runnable接口的类
 class Mthread implements Runnable{
     //2.实现类去实现Runnable中的run()
     public void run(){
         System.out.println("这是实现类的run方法。。。");
     }
 }

两种方法的比较

  1. 实现类的方法(方法2)不存在单继承的局限性
  2. 方法2更适合于线程之间存在共享数据的情况

联系:

查看java源码可以发现:Thread类也实现了Runnable

因此Thread中的run()也重写了Runnable的run()

所以两种方法的本质都是重写Runnable接口中的抽象方法run()


Thread类中的常用方法:

  1. start()
  2. run()
  3. currentThread() :static方法,返回执行当前代码的线程
  4. getName() :获取当前线程的名称
  5. setName() :设置当前线程的名称
  6. yield() :释放当前CPU的执行权
  7. join() :在线程a中调用线程b.join() ,线程a进入阻塞状态,直至线程b执行完后线程a才结束阻塞。
  8. stop():强制结束线程声明,不建议使用
  9. sleep(long millitime) :让当前线程“睡眠”millitime毫秒,睡眠期间当前线程处于阻塞。
  10. isAlive() :判断当前线程是否存活

线程的调度:

线程的优先级(定义在Thread类中):

  • MAX_PRIORITY:10
  • MIN_PRIORITY:1️
  • NORM_PRIORITY:5️(默认的优先级)

涉及的方法:

  • getPriority():返回线程的优先级
  • setPriority(int newPriority):改变线程的优先级

线程的优先级高,只是执行的概率更高,并不是高优先级的执行完后才能执行低优先级线程。

2.线程的生命周期

image-20221103194844019

或者查看Thread类中的State枚举类

3.线程同步

同步机制1:同步代码块

 synchronized(同步监视器){
     //需要被同步的代码
 }
 /*
     1.需要被同步的含义:操作共享数据的代码
     2.同步监视器:又称锁。任何一个对象的类都可以充当锁。
                 但是必须保证:多个线程必须公用一把锁
 */

比如三个线程模拟卖票:

 package com.coderLeo.java;
 ​
 /**同步代码块的测试:三个窗口线程的卖票问题
  * @author shkstart
  * @create 2022-11-03 下午 8:14
  */
 public class SynchronizationTest1 {
     public static void main(String[] args) {
         Window test = new Window();
 ​
         Thread t1 = new Thread(test);
         Thread t2 = new Thread(test);
         Thread t3 = new Thread(test);
 ​
         t1.setName("窗口1");
         t2.setName("窗口2");
         t3.setName("窗口3");
 ​
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 class Window implements Runnable{
     private int TICKETS = 100;
     Clock clock = new Clock();
 ​
     public void run() {
         while (true){
             synchronized (clock){
                 //加锁,任何一个类的对象都可以,但是要保证多个线程公用一把
                 //这里可以也可以考虑this充当锁。因为三个线程是共用Window类的test对象
                 if(TICKETS > 0){
                     try {
                         Thread.sleep(100);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
 ​
                     System.out.println(Thread.currentThread().getName() + "出票:" + TICKETS);
                     TICKETS--;
                 }
                 else
                     break;
 ​
             }
         }
     }
 }
 ​
 //锁类
 class Clock{
 ​
 }

同样的方法,使用在继承类中:

 package com.coderLeo.java;
 ​
 /**
  * @author shkstart
  * @create 2022-11-03 下午 9:23
  */
 ​
 public class SynchronizationTest2 {
     public static void main(String[] args) {
         Window2 t1 = new Window2();
         Window2 t2 = new Window2();
         Window2 t3 = new Window2();
 ​
         t1.setName("窗口1");
         t2.setName("窗口2");
         t3.setName("窗口3");
 ​
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 class Window2 extends Thread{
     private static int TICKETS = 100;
 ​
     private static Object obj = new Object();
 ​
     public void run() {
         while (true) {
             synchronized (obj){
                 //这里的锁也可考虑Window2.class(类的class也是对象,并且只加载一次,保证了唯一性)
                 if(TICKETS > 0){
                     try {
                         System.out.println(Thread.currentThread().getName() + "售票:" + TICKETS);
                         TICKETS--;
                         this.sleep(20);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
 ​
                 }
                 else
                     break;
             }
         }
     }
 }

同步机制2:同步方法

 //解决实现Runnable的同步问题
 public class SynchronizationTest3 {
     public static void main(String[] args) {
         Window3 test = new Window3();
 ​
         Thread t1 = new Thread(test);
         Thread t2 = new Thread(test);
         Thread t3 = new Thread(test);
 ​
         t1.setName("窗口1");
         t2.setName("窗口2");
         t3.setName("窗口3");
 ​
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 class Window3 implements Runnable {
     private int TICKETS = 100;
 ​
 ​
     public void run() {
         while (true) {
             //同步方法的执行
             show();
             if (TICKETS == 0)
                 break;
         }
 ​
     }
     
     //直接将涉及到共享数据的代码定义成一个方法,将此方法上锁
     public synchronized void show(){//此时的同步监视器:this
         if (TICKETS > 0) {
             try {
                 Thread.sleep(10);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
             System.out.println(Thread.currentThread().getName() + "出票:" + TICKETS);
             TICKETS--;
         }
     }
 }
 ​
     
 ​
 //解决继承Thread的同步问题
 public class SynchronizationTest4 {
     public static void main(String[] args) {
         Window4 t1 = new Window4();
         Window4 t2 = new Window4();
         Window4 t3 = new Window4();
 ​
         t1.setName("窗口1");
         t2.setName("窗口2");
         t3.setName("窗口3");
 ​
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 class Window4 extends Thread{
     private static int TICKETS = 100;
 ​
     private static Object obj = new Object();
 ​
     public void run() {
         while (true) {
             show();
             if(TICKETS == 0)
                 break;
         }
     }
     
     //注意此时一定要限制方法为static(看下面注释)
     public static synchronized void show() {//此时的同步监视器:Window4.class
         /*public synchronized void show()
         如果模仿解决实现Runnable的方法直接定义show()是错的,因为此时t1\t2\t3有各自的show(),同步监视器是this(t1\t2\t3)
          */
         if(TICKETS > 0){
             try {
                 System.out.println(Thread.currentThread().getName() + "售票:" + TICKETS);
                 TICKETS--;
                 Thread.currentThread().sleep(20);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
 ​
         }
     }
 }
 ​

同步方法的声明仍用到同步监视器,只是不需要显式地声明:

  • 非static同步方法的同步监视器:this
  • static同步方法的同步监视器:类本身(如上面的Window4.class)

同步机制3:Lock

jdk5新增的特性:显式锁

 import java.util.concurrent.locks.ReentrantLock;
 ​
 /**
  * @author shkstart
  * @create 2022-11-03 下午 11:23
  */
 public class LockTest {
     public static void main(String[] args) {
         Window5 w1 = new Window5();
 ​
         Thread t1 = new Thread(w1);
         Thread t2 = new Thread(w1);
         Thread t3 = new Thread(w1);
 ​
         t1.start();
         t2.start();
         t3.start();
 ​
 ​
     }
 }
 ​
 class Window5 implements Runnable {
     private static int TICKETS = 100;
 ​
     //实例化ReentrantLock
     private ReentrantLock lock = new ReentrantLock();//参数true保证公平性(按阻塞的先后顺序?)
 ​
     public void run() {
         while (true) {
             try {
                 //调用ReentrantLock对象的lock方法:上锁
                 lock.lock();
 ​
                 if(TICKETS > 0){
                     try {
                         Thread.sleep(20);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println(Thread.currentThread().getName() + "售票:" + TICKETS + "号");
                     TICKETS--;
                 }
                 else
                     break;
             }finally {
                 //解锁
                 lock.unlock();
             }
         }
     }
 }
  1. synchronized与lock:

    1. synchronized方法是执行相应的同步代码后自动释放锁(隐式锁);有代码块锁和方法锁
    2. lock是手动地添加(lock())、释放锁(unlock());只有代码块锁。但是使用Lock锁,JVM花费更少的时间调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  2. 对于线程同步方法的选择:

    Lock——>同步代码块——>同步方法

4.线程通信

 /**线程通信:
  * 涉及到的方法
  * 1.wait():执行wait后,当前线程被阻塞,同时释放同步监视器
  * 2.notify():执行notify后,唤醒被wait的进程。有多个进程被wait时,唤醒优先级高的线程
  * 3.notifyAll():唤醒所有被wait的进程
  *
  * @author shkstart
  * @create 2022-11-04 下午 12:36
  */
 public class CommunicationTest {
     public static void main(String[] args) {
         PrintNum test = new PrintNum();
 ​
         Thread thread1 = new Thread(test);
         Thread thread2 = new Thread(test);
 ​
         thread1.setName("线程1");
         thread2.setName("线程2");
 ​
         thread1.start();
         thread2.start();
 ​
     }
 }
 ​
 class PrintNum implements Runnable{
     private int Num;
 ​
     @Override
     public void run() {
         while (true) {
             synchronized (this) {
                 notify();//唤醒被wait的线程
                 if (Num <= 100){
                     System.out.println(Thread.currentThread().getName() + "打印:" + Num);
                     Num++;
 ​
                     try {
                         wait();//执行完打印后wait,同时释放同步监视器
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 else
                     break;
             }
         }
     }
 }

关于notify、notifyAll、wait:

  • 必须使用在同步代码块/同步方法中
  • 上述三个方法的调用者必须是同步代码块/同步方法的同步监视器
  • 上述三个方法定义在java.lang.Object中(因为同步监视器可以是任意一个类的对象,而调用这三个方法是通过同步监视器来调用)

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

实现Callable接口

 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
 ​
 /**
  * @author shkstart
  * @create 2022-11-04 下午 9:58
  */
 ​
 //1.创建实现Callable的实现类
 class NumThread implements Callable{
     //2.重写call方法,里面包含线程的具体操作。call方法有返回值
     @Override
     public Object call() throws Exception {
         int sum = 0;
         for(int i = 0; i <= 100; i++) {
             if(i % 2 == 0){
                 System.out.println(i);
                 sum += i;
             }
         }
         //这里其实是一个自动装箱(将int转为Integer,Object的子类)
         return sum;//不关心返回值可以return null
     }
 }
 ​
 public class CallableTest {
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         //3.创建Callable接口实现类的对象
         NumThread test = new NumThread();
 ​
         //4.将3中的实现类对象作为参数,传入FutureTask的构造器中,创建FutureTask对象
         FutureTask futureTask = new FutureTask(test);
 ​
         //5.将4中FutureTask对象作为参数,传入Thread构造器中,创建Thread类的对象,并执行start()
         new Thread(futureTask).start();
 ​
         /*
         6.如果希望获得实现Callable的返回值:
           get()返回值是FutureTask构造器中实现Callable接口的实现类(这里是test所属NumThread类)中重写的call方法的返回值
          */
         Object sum = futureTask.get();
         System.out.println(sum);
     }
 }

Callable比Runnable更强大:

  1. Callable有返回值
  2. Callable能抛出异常
  3. 支持泛型

线程池

开发中大部分情况都是使用线程池,有如下有点:

  1. 提高响应速度(减少创建线程的时间)

  2. 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)

  3. 便于线程管理,有如下参数:

    corePoolSize:核心池的大小

    MaximumPoolSize:最大线程数

    keepAliveTime:线程没有任务时最多保持多长时间会终止

 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadPoolExecutor;
 ​
 /**
  * @author shkstart
  * @create 2022-11-04 下午 10:35
  */
 ​
 class NumPrint 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 NumPrint2 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 NumPrint3 implements Callable {
 ​
     @Override
     public Object call() {
         int sum = 0;
         for (int i = 0; i < 100; i++) {
             sum += i;
             System.out.println(Thread.currentThread().getName() + ":" + i);
         }
         return sum;
     }
 }
 public class ThreadPoolTest {
     public static void main(String[] args) {
         //1.提供指定线程数量的线程池
         ExecutorService service = Executors.newFixedThreadPool(10);
 ​
         //ExecutorService是接口,其实现类为:ThreadPoolExecutor
         ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
 ​
         /*设置一些线程池的属性
         service1.setCorePoolSize(15);
         service1.setKeepAliveTime();
          */
 ​
         //2.执行指定线程的操作
         //service.submit(Callable callable);//适用于Callable
         service.execute(new NumPrint());//适用于Runnable
         //可以再造另一个线程
         service.execute(new NumPrint2());
 ​
         //3.关闭线程池
         service.shutdown();
     }
 }