【原创】Java并发编程系列2:线程概念与基础操作

本文深入探讨Java中线程的概念、创建方式及基础操作,包括继承Thread类、实现Runnable接口、使用FutureTask,以及线程的睡眠、让出CPU、中断等控制方法。

【原创】Java并发编程系列2:线程概念与基础操作

伟大的理想只有经过忘我的斗争和牺牲才能胜利实现。

本篇为【Dali王的技术博客】Java并发编程系列第二篇,讲讲有关线程的那些事儿。主要内容是如下这些:

  • 线程概念
  • 线程基础操作

线程概念

进程代表了运行中的程序,一个运行的Java程序就是一个进程。在Java中,当我们启动main函数时就启动了一个JVM的进程,而main函数所在的线程就是这个进程中的一个线程,称为主线程。 进程和线程的关系如下图所示: file

由上图可以看出来,一个进程中有多个线程,多个线程共享进程的堆的方法区资源,但是每个线程有自己的程序计数器和栈区域。

线程基础操作

线程创建与运行

Java中有三种线程创建方式,分别为:继承Thread类并重写run方法,实现Runnable接口的run方法,使用FutureTask方式。 先看继承Thread方式的实现,代码示例如下:

public class ThreadDemo {
    public static class DemoThread extends Thread {
        @Override
        public void run() {
            System.out.println("this is a child thread.");
        }
    }
    public static void main(String[] args) {
        System.out.println("this is main thread.")
        DemoThread thread = new DemoThread();
        thread.start();
    }
}

上面代码中DemoThread类继承了Thread类,并重写了run方法。在main函数里创建了一个DemoThread的实例,然后调用其start方法启动了线程。

tips:调用start方法后线程并没有马上执行,而是处于就绪状态,也就是这个线程已经获取了除CPU资源外的其他资源,等待获取CPU资源后才会真正处于运行状态。 使用继承方式,好处在于通过this就可以获取当前线程,缺点在于Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。而且任务与代码耦合严重,一个线程类只能执行一个任务,使用Runnable则没有这个限制。 来看实现Runnable接口的run方法的方式,代码示例如下:

public class RunnableDemo {
    public static class DemoRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("this is a child thread.");
        }
    }
    public static void main(String[] args) {
        System.out.println("this is main thread.");
        DemoRunnable runnable = new DemoRunnable();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}

上面代码两个线程共用一个Runnable逻辑,如果需要,可以给RunnableTask添加参数进行任务区分。在Java8中,可以使用Lambda表达式对上述代码进行简化:

 public static void main(String[] args) {
    System.out.println("this is main thread.");
    Thread t = new Thread(() -> System.out.println("this is child thread"));
    t.start();
}

上面两种方式都有一个缺点,就是任务没有返回值,下面看第三种,使用FutureTask的方式。代码示例如下:

public class CallableDemo implements Callable<JsonObject> {
    @Override
    public JsonObject call() throws Exception {
        return new JsonObject();
    }
    public static void main(String[] args) {
        System.out.println("this is main thread.");
        FutureTask<JsonObject> futureTask = new FutureTask<>(new CallableDemo());   // 1. 可复用的FutureTask
        new Thread(futureTask).start();
        try {
            JsonObject result = futureTask.get();
            System.out.println(result.toString());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        // 2. 一次性的FutureTask
        FutureTask<JsonObject> innerFutureTask = new FutureTask<>(() -> {
            JsonObject jsonObject = new JsonObject();
            jsonObject.addProperty("name", "Dali");
            return jsonObject;
        });
        new Thread(innerFutureTask).start();

        try {
            JsonObject innerResult = innerFutureTask.get();
            System.out.println(innerResult.toString());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

如上代码,CallableDemo实现了Callable接口的call方法,在main函数中使用CallableDemo的实例创建了一个FutureTask,然后使用创建的FutureTask对象作为任务创建了一个线程并启动它,最后通过FutureTask等待任务执行完毕并返回结果。 同样的,上面的操作过程适合于需要复用的任务,如果对于一次性的任务,大可以通过Lambda来简化代码,如注释2处。

等待线程终止

在项目中经常会遇到一个场景,就是需要等待某几件事情完成后才能继续往下执行。Thread类中有一个join方法就可以用来处理这种场景。直接上代码示例:

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main thread starts");
        Thread t1 = new Thread(() -> System.out.println("this is thread 1"));
        Thread t2 = new Thread(() -> System.out.println("this is thread 2"));
        t1.start();
        t2.start();
        System.out.println("main thread waits child threads to be over");
        t1.join();
        t2.join();
        System.out.println("child threads are over");
    }

上面代码在主线程里启动了两个线程,然后分别调用了它们的join方法,主线程会在调用t1.join()后被阻塞,等待其执行完毕后返回;然后主线程调用t2.join()后再次被阻塞,等待t2执行完毕后返回。上面代码的执行结果如下:

main thread starts
main thread waits child threads to be over
this is thread 1
this is thread 2
child threads are over

需要注意的是,线程1调用线程2的join方法后会被阻塞,当其他线程调用了线程1的interrupt方法中断了线程1时,线程1会抛出一个InterruptedException异常而返回。

让线程睡眠

Thread类中有一个static的sleep方法,当一个执行中的线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是不让出的。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后等待CPU的调度执行。

tips:面试当中wait和sleep经常会被用来比较,需要多加体会二者的区别。 调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁;notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。 调用某个对象的wait()方法和notify()方法,当前线程必须拥有这个对象的monitor,因此调用wait()方法和notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

看一个线程睡眠的代码示例:

private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
       // 获取独占锁
       lock.lock();
       System.out.println("thread1 get to sleep");
        try {
            Thread.sleep(1000);
            System.out.println("thread1 is awake");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    });
    Thread t2 = new Thread(() -> {
        // 获取独占锁
        lock.lock();
        System.out.println("thread2 get to sleep");
        try {
            Thread.sleep(1000);
            System.out.println("thread2 is awake");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    });

    t1.start();
    t2.start();
}

上面的代码创建了一个独占锁,然后创建了两个线程,每个线程在内部先获取锁,然后睡眠,睡眠结束后会释放锁。执行结果如下:

thread1 get to sleep
thread1 is awake
thread2 get to sleep
thread2 is awake

从执行结果来看,线程1先获取锁,然后睡眠,再被唤醒,之后才轮到线程2获取到锁,也即在线程1sleep期间,线程1并没有释放锁。 需要注意的是,如果子线程在睡眠期间,主线程中断了它,子线程就会在调用sleep方法处抛出了InterruptedException异常。

线程让出CPU

Thread类中有一个static的yield方法,当一个线程调用yield方法时,实际就是暗示线程调度器当前线程请求让出自己的CPU使用,如果该线程还有没用完的时间片也会放弃,这意味着线程调度器可以进行下一轮的线程调度了。 当一个线程调用yield方法时,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU执行权。 请看代码示例:

public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            if (i == 8) {
                System.out.println("current thread: " + Thread.currentThread() + " yield cpu");
            }
            Thread.yield(); // 2
        }
        System.out.println("current thread: " + Thread.currentThread() + " is over");
    });

    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            if (i == 8) {
                System.out.println("current thread: " + Thread.currentThread() + " yield cpu");
            }
            Thread.yield(); // 1
        }
        System.out.println("current thread: " + Thread.currentThread() + " is over");
    });
    t1.start();
    t2.start();
}

在如上的代码中,两个线程的功能一样,运行多次,同一线程的两行输出是顺序的,但是整体顺序是不确定的,取决于线程调度器的调度情况。 当把上面代码中1和2处代码注释掉,会发现结果只有一个,如下:

current thread: Thread[Thread-1,5,main] yield cpu
current thread: Thread[Thread-0,5,main] yield cpu
current thread: Thread[Thread-1,5,main] is over
current thread: Thread[Thread-0,5,main] is over

从结果可知,Thread.yiled方法生效使得两个线程分别在执行过程中放弃CPU,然后在调度另一个线程,这里的两个线程有点互相谦让的感觉,最终是由于只有两个线程,最终还是执行完了两个任务。

tips:sleep和yield的区别: 当线程调用sleep方法时,调用线程会阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是出于就绪状态,线程调度器下一次调度时就可能调度到当前线程执行。

线程中断

Java中的线程中断是一种线程间的协作模式。每个线程对象里都有一个boolean类型的标识(通过isInterrupted()方法返回),代表着是否有中断请求(interrupt()方法)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。 在上面章节中也讲到了线程中断的一些内容,此处就不再用代码来展开了。

Java并发编程大纲

继续附上Java编程的系统学习大纲以供参考: Java并发编程.pngfile

【参考资料】

  1. 《Java并发编程之美》

本文由微型公众号【Dali王的技术博客】原创,扫码关注获取更多原创技术文章。 Dali王的技术博客

内容概要:本文围绕“基于分布式模型预测控制的多个固定翼无人机一致性控制”展开,利用Matlab代码实现相关算法的仿真,旨在通过分布式控制策略实现多架固定翼无人机在复杂动态环境中的协同飞行一致性控制。研究结合模型预测控制(MPC)方法,构建适用于多无人机系统的分布式优化框架,重点解决了通信受限、信息延迟及无中心化指挥条件下的协同稳定性问题。内容涵盖固定翼无人机的动力学建模、分布式MPC优化求解机制、一致性协议设计、通信拓扑结构分析以及仿真验证全过程,确保多机系统在保持队形一致的同时完成协同任务。; 适合人群:具备自动控制理论、无人机系统建模或多智能体协同控制基础,从事智能无人系统、集群控制、自动化机器人等领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于多无人机协同编队飞行、集群侦察、分布式任务执行等实际工程场景;②为分布式MPC算法在多智能体系统中的一致性控制提供可复现的Matlab仿真案例,推动先进控制理论向工程实践转化;③服务于科研论文复现、算法验证、控制系统课程设计毕业课题参考。; 阅读建议:建议读者结合文中提供的Matlab代码逐模块运行调试,重点关注分布式MPC在不同通信拓扑下对一致性收敛性能的影响,并可通过调整预测时域、权重矩阵噪声参数等方式深化对算法鲁棒性适应性的理解。
内容概要:本文提出了一种基于变分模态分解(VMD)、麻雀搜索算法(SSA)优化长短期记忆网络(LSTM)相结合的光伏功率预测模型(VMD-SSA-LSTM),旨在提升光伏发电预测的精度鲁棒性。该方法首先利用VMD对原始非平稳光伏功率序列进行自适应分解,获得一系列具有更稳定特征的本征模态分量(IMFs),有效降低数据复杂性噪声干扰;随后引入麻雀搜索算法(SSA)对LSTM网络的关键超参数(如学习率、隐层节点数等)进行全局寻优,克服传统试凑法效率低、易陷入局部最优的问题,显著提升模型收敛速度泛化能力;最后,构建多个LSTM子模型分别预测各模态分量,并将结果重构得到最终的光伏功率预测值。该混合模型充分融合了VMD在信号预处理中的优异分解性能、SSA在参数优化中的高效搜索能力以及LSTM在捕捉时间序列长期依赖关系上的强大建模优势,实现了对复杂气象因素影响下光伏出力波动的高精度拟合预测。; 适合人群:具备一定电力系统、新能源发电或时间序列预测基础知识,熟悉MATLAB编程环境,从事光伏功率预测、智能电网调度、可再生能源集成、负荷预测等领域研究的科研人员、工程技术人员及高校研究生。; 使用场景及目标:①应用于光伏电站的短期超短期功率预测,为电网安全调度、电力市场交易、储能系统配置及需求侧响应提供精准数据支撑;②解决传统单一预测模型(如ARIMA、BPNN、单一LSTM)在处理非平稳、强波动性光伏数据时存在的精度不足、稳定性差等问题;③为风电、负荷等其他非平稳时序预测问题提供一种有效的“分解-优化-预测”混合建模范式技术实现路径。; 阅读建议:建议读者结合文中提供的完整MATLAB代码,深入理解VMD信号分解、SSA优化算法流程及LSTM网络构建的每一个技术环节,通过实际历史数据进行模型复现对比实验(如VMD-LSTM、SSA-LSTM等模型比较),掌握参数调优技巧模型性能评估方法,从而真正掌握该先进混合预测模型的核心思想应用精髓。
代码转载自:https://pan.quark.cn/s/a4b39357ea24 在当代信息技术行业,深度学习技术的应用已经全面渗透到众多实际情境中,其中生成对抗网络(GAN)以及深度卷积生成对抗网络(DCGAN)被视为促进人工智能领域发展的核心技术之一。接下来将具体阐述如何借助Pytorch框架MNIST数据集来完成基础GAN和DCGAN的开发,并解析过程中涉及的关键概念。 ### Pytorch框架MNIST数据集概述 **Pytorch** 被视为一种广受欢迎的开源机器学习平台,其具备动态计算图高度适应性等特性,因此在学术研究领域备受推崇,能够支持多种类型的深度学习架构。 **MNIST数据集** 是一个包含手写数字的图像集合,常用于训练各类图像识别系统,涵盖从0至9的数字图像,每张图像的尺寸为28x28像素。由于其入门级难度,MNIST数据集特别适合用于教学演示和实验操作。 ### 基础生成对抗网络(GAN)核心概念 **基础GAN** 是一种由生成器(Generator)和判别器(Discriminator)构成的无监督学习框架。生成器的核心任务是创造足以乱真的图像数据,而判别器的关键职责在于精确辨别真实图像生成图像之间的差异。 1. **生成器**:该模块以随机噪声为输入,通过深度神经网络进行转换,最终输出接近真实图像的数据。其根本目的在于迷惑判别器。 2. **判别器**:对输入数据(无论是真实图像还是生成图像)进行评估,并输出其属于真实样本的概率。判别器的训练重点在于提升识别的精确度。 3. **训练机制**:GAN的训练过程涉及两个网络之间的交替训练,即在锁定一个网络参数的情况下对另一个网络进行训练。首先优化判别器以增强其区分能力...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值