【Java高并发系列】之入门篇

简介: 本文主要介绍 Java并行程序基础

什么是线程


在了解什么是线程之前,我们先了解一下什么是进程


进程是线程的母亲,进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是线程的容器,进程中可以容纳多个线程。


简单来讲就是:一间房子相当于一个容器,也就是进程的概念。这间房子住着你和你的父母,三个人就是分别的线程。你上学,妈妈干家务,爸爸上班,做着不同的事情,维持的进程的运行。


线程就是轻量级的进程,是程序执行的最小单位,使用多线程而不是多进程来进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程



线程所有状态的State如下:



NEW状态表示刚刚创建的线程,这种线程还没开始执行。等到线程的start()方法调用时,才表示线程刚开始执行。当线程执行时,处于RNNNABLE状态,表示线程所需的一切资源都已经准备好了。如果线程在执行过程中遇到了synchronized同步块,就会进入BLOCKED阻塞状态,这是线程就会暂停执行,直到获得请求的锁。WAITTINGTIMED_WAITING都表示等待状态,它们的区别是WAITING会进入一个无时间限制的等待,TIMED_WAITING会进行一个有时间限制的等待。一般来说,WAITING的线程正是在等待一些特殊的时间。比如,通过wait()方法等待的线程在等待notify()方法,而通过join()方法等待的线程则会等待目标线程的终止。一旦等待了期望的时间,线程就会再次执行,进入RUNNABLE状态。当线程执行完毕后,则进入TERMINATED状态,表示结束。


线程的基本操作


新建线程


使用 new 关键字创建一个线程对象,并调用 start() 方法即可


Thread thread = new Thread();
thread.start();


start()方法执行后,实际上会调用线程中的run()方法,start()方法就会新建一个线程并让这个线程执行run()方法。


Thread thread = new Thread();
thread.run();


如果直接调用线程中的run()方法,那么只是作为一个普通方法的调用。


不要用 run() 方法来开启新线程,它只会在当前线程中 串行 执行 run() 方法中的代码。

按照上面的方法新建线程,实际上是没有执行任何操作的,如果我们要实现自定义的方法,那么就要重写run()方法,把自定义的任务传进去。这个时候就可以使用Thread的另外一个构造方法了。


Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("这里定义自定义的任务");
    }
});
thread.start();


Runnable接口是一个单方法接口,它只有一个 run()方法


@FunctionalInterface
public interface Runnable {
    public abstract void run();
}


终止线程


一般来说,线程执行完毕就会结束,不需要手动关闭。但是如果一些服务端的后台线程常驻系统后台,它们通常不会正常终结。它们的执行体本身就是一个大大的无穷循环,用于提供某些服务。


我们可以使用 stop()将一个线程立刻终止



通过图片我们可以看到stop()方法已经被注解@Deprecated标记,说明是已经废弃的方法。这是因为stop()方法过于暴力,它会强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。所以除非你很清除自己在做什么,否则不要随便使用stop()方法来停止一个线程。


我们可以定义一个标识,来控制线程何时退出



线程中断


线程中断是一个重要的线程协作机制,线程中断并不会向stop()方法那样使线程马上停止,而是给线程发送一个通知,告知目标线程,你可以退出了。Thread中提供了三个与线程中断有关的方法。


public void interrupt() {}     //中断线程
public boolean isInterrupted() {}   //判断是否被中断
public static boolean interrupted() {}  //判断是否被中断,并清除当前中断状态



这种方法看起来与前面设置 stopSelf()方法相似,但是中断的功能更为强劲,如果循环体中,出现了类似wait()方法或者sleep()方法这样的操作,则只能通过中断来识别了。


等待(wait)和通知(notify)


为了支持多线程之间的协作,JDK提供了两个非常重要的接口线程:等待wait()方法和通知notify()方法。这两个方法并不是 Thread 类中的,而是在 Object 类中。这就意味着任何对象都可以调用这两个方法。


当 A 线程调用了 wait()方法后,A 线程就会停止执行转为等待状态。一直等到其他线程调用了 notify()方法为止。


如果一个线程调用了wait()方法后,就会进入 object 对象的等待队列。这个等待队列可能会有多个线程。因为系统运行了多个线程同时等待某一个对象。当有其他线程调用notify()方法后,会随机的从这个等待队列中选择一个线程进行唤醒,这个选择是不公平的,并不是先等待的线程就会优先选择。



注意:wait() 方法并不能随便调用,它必须包含在对应的synchronized语句中,因为无论是 wait() 方法还是 notify() 方法都需要获取目标对象的一个监视器,在 wait() 方法执行后,会释放这个监视器。



*wait()方法和sleep()的区别就是:*wait() 方法会释放目标对象的锁,而 sleep() 则不会


挂起(suspend)和继续执行(resume)线程


挂起(suspend) 和 继续执行(resume)这两个操作是一对相反的操作,被挂起的线程,必须要等到resume()方法操作后才能继续执行。当时这两个也是被标注为废弃的方法。

这是因为suspend()方法在导致线程暂停的同时,并不会释放任何资源,此时,其他任何线程想要访问被它占用的锁时,都会被前两,导致无法正常继续执行。直到对应的线程上进行了resume()方法操作,被挂起的线程才能继续执行,从而其他所有阻塞在相关锁上的线程也可以继续执行。


重要的是这两个方法不能保证有序性。也就是说resume()方法可能会发生在suspend()方法的前面,那么结果就是,挂起线程的锁永远不会被释放,进入死锁状态,将会导致整个系统工作不正常!



等待线程结束(join) 和 谦让(yeild)


有些时候,一个线程的输入可能需要依赖其他线程的输出,这个时候,这个线程就需要等待其他线程执行完成才能继续执行。那么就需要通过join()方法来协助。JDK中有两个join()方法:


public final void join() throws InterruptedException 
public final synchronized void join(long millis) throws InterruptedException


第一个join()方法表示无限等待,它会一直阻塞线程,直到目标线程执行完毕。第二个方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程就会停止等待,继续往下执行。



在主函数中,如果不使用join()方法等待 AThread 线程执行结束那么打印 i 的结果是0,使用了 join()方法后,打印 i 的结果是10000。


yield()方法则是一个静态方法,它会使当前线程让出CPU。但是注意的是,让出CPU并不表示当前线程不执行了,当前线程让出CPU后,还会进行CPU资源的争夺,但是是否能够再次分配到就不一定了。


如果你觉得一个线程不那么重要,或者优先级非常低,而且又害怕它会占用太多的CPU资源,那么可以在适当的时候调用yield()方法,给予其他重要线程更多工作机会。


线程组的使用




守护线程(Daemon)


守护线程是一种特殊的线程,它是系统的守护者,在后台默默地完成一些系统性的服务,如垃圾回收线程JIT线程就可以理解为守护线程。


与之相对应的就是用户线程,用户线程可以认为是系统的工作线程,它会完成这个程序应该要完成的业务操作。如果用户线程全部完成,那么这个程序实际上就无事可做了,守护线程要守护的对象已经不存在了,整个应用程序就应该结束。因此,当一个 Java 应用内只有守护线程时,Java 虚拟机就会自然退出。


守护线程的使用:



线程优先级


Java 中线程可以有自己的优先级,优先级高的线程在竞争资源时会有更多的优势,更可能抢占资源,当然这也是一个概率问题,有些时候设置高优先级的线程并不管用。

在 Java 中使用 1-10 来表示线程优先级,一般可以使用内置的三个静态标量表示:


public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;



上图中,将 A 线程的优先级设置为 10,B 线程的优先级设置为1,可以看到最后先打印的是 “A 线程执行完毕”,但是因为这种方法有概率性的,因为并不会总是打印 “A 线程执行完毕”。


目录
相关文章
|
1月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第17天】本文详细介绍了Java编程中Map的使用,涵盖Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的并发处理和性能优化技巧,适合初学者和进阶者学习。
50 3
|
13天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
18天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
25天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
65 5
|
22天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
32 1
|
28天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
45 3
|
29天前
|
缓存 监控 Java
Java 线程池在高并发场景下有哪些优势和潜在问题?
Java 线程池在高并发场景下有哪些优势和潜在问题?
|
30天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
1月前
|
开发框架 IDE Java
java制作游戏,如何使用libgdx,入门级别教学
本文是一篇入门级教程,介绍了如何使用libgdx游戏开发框架创建一个简单的游戏项目,包括访问libgdx官网、设置项目、下载项目生成工具,并在IDE中运行生成的项目。
51 1
java制作游戏,如何使用libgdx,入门级别教学
|
1月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第19天】本文介绍了Java编程中重要的数据结构——Map,通过问答形式讲解了Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的使用和性能优化技巧,适合初学者和进阶者学习。
45 4
下一篇
无影云桌面