# java:多线程的创建

108 阅读5分钟

1. 继承Thread类

  • 实例:

    package 使用继承Thread方法创建线程;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @Author -..----.--...../-.--..-.-----.-/----.-.....-.--/-.----.-...----/-.-.-...-.--...
     * @Date 2021/10/23 23:00
     */
    
    // 1. 创建继承Thread类的类
    class WindowTest extends Thread {
        private static int num = 0;
        private static Lock lock = new ReentrantLock(true);
        // 2. 重写run()方法
        @Override
        public void run() {
            // 线程操作代码
                while (true) {
                    try {
                        lock.lock();
                        if (num <= 20) {
                            System.out.println(getName() + ":" + num);
                            num++;
                        }
                    }finally {
                        lock.unlock();
                        if (num > 20) {
                            break;
                        }
                    }
                }
        }
    }
    
    public class ThreadTest {
        public static void main(String[] args) {
            // 3. 创造创建继承Thread类的类的对象
            WindowTest w1 = new WindowTest();
            WindowTest w2 = new WindowTest();
            w1.setName("线程1");
            w2.setName("线程2");
            // 4. 对象调用start()方法
            w1.start();
            w2.start();
        }
    }
    
  • 提示:

    • start()方法调用

      1. 启动当前对象的线程
      2. 调用对象线程的 run() 方法
    • 不能直接使用 run() 方法启动线程

    • 已经 start() 启用的线程不能再被 start() 方法启用,如果重复start会报错

          package October_14;
          
          class MyThread extends Thread {
              @Override
              public void run() {
          
              }
          }
          
          public class uesThread {
              public static void main(String[] args) {
          
                  MyThread myThread1 = new MyThread();
                  // 测试在已被启用的线程能否再次被启用
                  myThread1.start();
                  myThread1.start();
              }
          }
      
      结果:
      Exception in thread "main" java.lang.IllegalThreadStateException
      会报错
      
      
    • 因为继承类创建线程需要创建多个线程类的对象,所以线程共享数据用static修饰。

2. 实现Runnable接口

  • 示例

    package 使用Runnable方法创建线程;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @Author -..----.--...../-.--..-.-----.-/----.-.....-.--/-.----.-...----/-.-.-...-.--...
     * @Date 2021/10/23 23:40
     */
    // 1. 创建接口Runnable的实现类
    class Window implements Runnable {
        private int num = 0;
        private Lock lock = new ReentrantLock(true);
    
        // 2. 实现run()方法
        @Override
        public void run() {
            // 线程操作代码
            while (true) {
                try {
                    lock.lock();
                    if (num <= 20) {
                        System.out.println(Thread.currentThread().getName() + ":" + num);
                        num++;
                    }
                } finally {
                    lock.unlock();
                    if (num > 20) {
                        break;
                    }
                }
            }
        }
    }
    
    public class RunnableTest {
        public static void main(String[] args) {
            // 3. 创建实现类对象
            Window window = new Window();
            
            // 4. 实现类对象作为参数传入Thread类中,创建Thread类对象。需要多少个线程就创建多少个Thread类对象。
            Thread t1 = new Thread(window, "线程1");
            Thread t2 = new Thread(window, "线程2");
            
            // 5. Thread类对象调用start()方法
            t1.start();
            t2.start();
        }
    }
    
  • 提示

    • 可以多个 Thread 类对象共用同一个 Rannable() 的实现类对象。Rannable() 的实现类对象的数据是共用的。
    • 实现一个实现类对象,通过不断创造Thread类的对象来创造线程。
    • 因为实现类对象只有一个,所以线程共享数据不需要用static修饰。
    • 因为java的单继承性会对继承方法有影响和继承Thread类创造线程方法的共享数据需要static做修饰会对类池有所影响。所以对比继承Thread类创造线程的方法,实现Runnable类创造线程的方法更好。

3. 实现Callable接口

  • 注:此方法是JDK 5.0 新增

  • 示例:

    package test3;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * @Author -..----.--...../-.--..-.-----.-/----.-.....-.--/-.----.-...----/-.-.-...-.--...
     * @Date 2021/10/23 16:09
     */
    
    // 1. 创建实现Callable接口的类
    class NumThead implements Callable {
        
        // 2. 实现call方法,将此线程需要执行的操作声明在call()方法中
        @Override
        public Object call() throws Exception {
            
            return null;
        }
    }
    
    public class ThreadNew {
        public static void main(String[] args) {
            // 3. 创建Callable接口实现类的对象numThead
            NumThead numThead = new NumThead();
    
            // 4. 将numThead传递到FutureTask的构造器中。
            //    创建FutureTask对象futureTask
            FutureTask futureTask = new FutureTask<>(numThead);
    
            // 5. 将futureTask作为参数传到Thread类的构造器中。
            //    创建Thread的对象thread,并调用thread的start()方法
            new Thread(futureTask, "线程名").start();
            new Thread(futureTask, "线程名").start();
    
    
            // (选做)6. 通过futureTask的get()方法获得类型为Object的返回值
            try {
                // get()返回值即FutureTask构造器参数Callable实现类重写的call()的返回值
                Object j = futureTask.get();
                System.out.println(j);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 特点

    1. call() 可以有返回值
    2. call()可以抛出异常,被外面的操作捕获,获取异常信息
    3. Callable支持泛型
    4. FutureTack支持泛型
    5. 现在常用的创造多线程的方法
  • 个人理解

    • Callable作用是:实现call接口对数据进行操作并得到结果
    • Future作用是:接受线程返回的结果
    • Thread作用是:启动线程

4. 使用线程池

  • 注:此方法是JDK 5.0 新增

  • 示例:

    package 使用线程池;
    
    import java.util.concurrent.*;
    
    /**
     * @Author -..----.--...../-.--..-.-----.-/----.-.....-.--/-.----.-...----/-.-.-...-.--...
     * @Date 2021/10/23 17:35
     */
    
    /*class NumberThread1 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 NumberThread implements Callable {
        @Override
        public Object call() {
            int num = 0;
            while (true) {
                if (num <= 100) {
                    num++;
                    System.out.println(Thread.currentThread().getName() + ":" + num);
    
                } else {
                    break;
                }
            }
            return num;
        }
    }
    
    public class ThreadPool {
        public static void main(String[] args) {
            // 1. 提供指定线程数量的线程池
            ExecutorService service = Executors.newFixedThreadPool(10);
    
            // 2. 执行指定实现方法的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
            //service.execute(new NumberThread1());// 适用于Runnable
            NumberThread numberThread = new NumberThread();
            FutureTask<Object> futureTask = new FutureTask<Object>(numberThread);
            service.submit(futureTask);// 适用于FutureTask
            try {
                System.out.println(futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
            // 3. 关闭连接池
            service.shutdown();
        }
    }
    
    
  • 优点:

    1. 提高响应速度(减少创建新线程的时间)
    2. 降低资源消耗(可以重复利用线程池里的线程,不需要每次都创建)
    3. 便于线程管理
  • 方法介绍:

    • Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)
      • 一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。缓存线程池,先查看线程池中是否有当前执行线程的缓存,如果有就resue(复用),如果没有,那么需要创建一个线程来完成当前的调用.并且这类线程池只能完成一些生存期很短的一些任务.并且这类线程池内部规定能resue(复用)的线程,空闲的时间不能超过60s,一旦超过了60s,就会被移出线程池
    • Executors.newFixedThreadPool(int)(固定大小线程池)
      • 一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。固定型线程池,和newCacheThreadPool()差不多,也能够实现resue(复用),但是这个池子规定了线程的最大数量,也就是说当池子有空闲时,那么新的任务将会在空闲线程中被执行,一旦线程池内的线程都在进行工作,那么新的任务就必须等待线程池有空闲的时候才能够进入线程池,其他的任务继续排队等待.这类池子没有规定其空闲的时间到底有多长.这一类的池子更适用于服务器.
    • Executors.newSingleThreadExecutor()(单个后台线程)
      • 一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。表示在任意的时间段内,线程池中只有一个线程在工作
    • Executors.newScheduledThreadPool() (支持计划任务的线程池)
      • 可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。调度型线程池,调度型线程池会根据Scheduled(任务列表)进行延迟执行,或者是进行周期性的执行.适用于一些周期性的工作.

本文组成部分

  • 线程池方法介绍部分

    版权声明:本文为CSDN博主「Charles Ren」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/chongbin007…