• Java学习之路 —— 多线程


    1. 线程创建方式

    1.1 继承Thread

    定义子类,继承Thread,创建对象,并调用start启动线程

    • 优点:编码简单
    • 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展
    public class Main {
        // main方法是有一条默认的主线程执行的
        public static void main(String[] args) {
            // 1. 创建线程类的对象,代表一个线程
            Thread t = new MyThread();
            // 2. 启动线程
            t.start();
            for (int i = 0; i < 10000; i++) {
                System.out.println("主线程输出:" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注意,是调用Thread的start方法,而不是run方法!!!

    public class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                System.out.println("子线程输出:" + i);
            }
            super.run();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.2 声明一个实现Runnable接口的类

    • 优点:只是实现了一个接口,还可以继承一个类,实现其他接口,扩展性强
    • 缺点:需要多创建一个Runnable对象
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                System.out.println("子线程输出:" + i);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    static void test_Runnable() {
    //        Runnable target = new MyRunnable(); // 任务对象(不是线程对象)
    //        new Thread(target).start();
            // lambda
            new Thread(() -> {
                for (int i = 0; i < 10000; i++) {
                    System.out.println("子线程输出:" + i);
                }
            for (int i = 0; i < 10; i++) {
                System.out.println("主线程输出:" + i);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    1.3 利用Callable接口、FutureTask类来实现

    • 优点:可以返回线程执行完后的结果
    • 缺点:编码复杂
      在这里插入图片描述
    class MyCallable implements Callable<String> {
        int n;
        public MyCallable(int n) {
            this.n = n;
        }
        @Override
        public String call() throws Exception {
            // 描述线程的任务,返回线程执行后的结果
            // 求1-n的和
            int sum = 0;
            for(int i = 1; i <= n; i++)
                sum += n;
            return "1~n的总和是:" + sum;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    static void test_Callble() {
            // 1. 创建Callable对象
            MyCallable call = new MyCallable(50);
            // 2. 把Callable对象封装成FutureTask对象
                // 1. 是一个任务对象,实现了Runnable对象
                // 2. 可以在线程执行完后,调用get方法获取
            FutureTask<String> f1 = new FutureTask<>(call);
            new Thread(f1).start();
            for (int i = 0; i < 10; i++) {
                System.out.println("主线程输出:" + i);
            }
            // 获取线程执行完毕后的结果
            String s = null;
            try {
                // 会等到线程执行完毕,这行代码再执行
                s = f1.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(s);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我发现啊,Java的多线程和C++的多线程不一样的点是,Java中创建子线程,如果主线程先执行完了,子线程没有执行完,子线程会继续执行;但是C++会被终止。

    不过join方法都有着相同的用处,那就是阻塞主线程,等待线程执行完毕后,再执行主线程后面的代码。

    2. 线程同步

    2.1 同步代码块

    synchronized(同步锁) {
    	访问共享资源的核心代码
    }
    
    • 1
    • 2
    • 3

    这个同步锁是一个字符串也可以(双引号在内存中存在常量区,只有一份),只要是一个唯一对象即可 。
    但最好是用共享资源作为锁,比如说this

    如果要调用静态方法,同步锁采用类名.class,锁住整个class。

    2.2 同步方法

    把访问共享资源的核心方法上锁,保证线程安全。这样能保证一个对象中,只有一个方法在执行,其他方法都无法执行。

    修饰符 synchronized 返回值类型 方法名(形参列表){
    	操作共享资源得到代码
    }
    
    • 1
    • 2
    • 3

    2.3 Lock锁

    Lock是一个接口类,可以用实现类ReentrantLock来实例化一个锁,来使用。

    Lock lk = new ReentrantLock();
    lk.lock();
    lk.unlock();
    
    • 1
    • 2
    • 3

    3. 线程同步

    来了来了,条件遍历来了。注意,一定要和锁搭配使用。
    在这里插入图片描述
    来一个经典的例子吧,2个线程交替打印A和B

    package TestDemo;
    
    // 2个线程交替打印A和B10次
    public class Test2 {
        public static int count;
        static final Object lock = new Object(); // 锁
        public static void main(String[] args){
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    synchronized (lock) {
                        System.out.println(Thread.currentThread().getName() + "打印A");
                        lock.notify();
                        try {
                            if(i < 9)
                                lock.wait();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, "A线程");
    
            Thread t2 = new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    synchronized (lock) {
                        System.out.println(Thread.currentThread().getName() + "打印B");
                        lock.notify();
                        try {
                            if(i < 9)
                                lock.wait();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, "B线程");
            t1.start();
            t2.start();
        }
    }
    
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    4. 线程池

    JDK5提供了代表线程池的接口:ExecutorService。比较常用的实现类是ThreadPoolExecutor

    • public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
      • corePoolSize:指定线程池的核心线程数量
      • maximumPoolSize:指定线程池的最大线程数量
      • keepAliveTime:指定临时线程的存活时间
      • unit:指定临时线程存货的时间单位
      • workQueue:指定线程池的任务队列
      • threadFactory:指定线程池的任务工厂
      • handler:指定线程池的拒绝策略(任务队列满了后,新任务来了怎么处理)

    在这里插入图片描述线程池默认是不会死亡的,除非调用shutdown(),或者shutdownNow()

    1. 临时线程什么时候创建?
      新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程,去执行新任务,而不是任务队列的任务(插队)。
    2. 什么时候会开始拒绝新任务?
      核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

    Executors
    是线程池的一个工具类,提供了很多静态方法用于返回不同特点的线程池对象。
    在这里插入图片描述

  • 相关阅读:
    大模型+检索增强(RAG、Atlas 和 REPLUG)
    域名解析中的A记录和CNAME什么意思
    创邻科技获评环紫金港创新生态圈智源创新企业
    为什么线程崩溃,崩溃不会导致 JVM 崩溃
    RabbitMQ 常用模式
    Vue的自定义指令
    SpringBoot自动装配
    【C++】异常
    使用C# Net6连接国产达梦数据库记录
    牛客竞赛每日俩题 - Day6
  • 原文地址:https://blog.csdn.net/weixin_51322383/article/details/134451640