Java多线程通关———基础知识

简介: Java多线程通关———基础知识

掌握基础知识。


线程与进程


1 线程:进程中负责程序执行的执行单元

线程本身依靠程序进行运行


线程是程序中的顺序控制流,只能使用分配给程序的资源和环境

2 进程:执行中的程序

一个进程至少包含一个线程


3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程


4 多线程:在一个程序中运行多个任务

目的是更好地使用CPU资源


线程的实现

继承Thread类

java.lang包中定义, 继承Thread类必须重写run()方法


classMyThread extendsThread{
    privatestatic int num = 0;
    publicMyThread(){
        num++;
    }
    @Override
    publicvoid run() {
        System.out.println("主动创建的第"+num+"个线程");
    }
}

创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。


publicclass Test {
    publicstatic void main(String[] args)  {
        MyThread thread = newMyThread();
        thread.start();
    }
}
classMyThread extendsThread{
    privatestatic int num = 0;
    publicMyThread(){
        num++;
    }
    @Override
    publicvoid run() {
        System.out.println("主动创建的第"+num+"个线程");
    }
}


1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;

2)虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。

通过Thread的源代码,我们现在对其主要的的一些方法进行讲解一下

     native关键字 -  native是与C++联合开发的时候用的!使用native关键字说明这个方法是原生函数,也就是说这个方法是用C/C++语言实现的,并且被编译成dll相关组件,由java来调用。所以从上面的Thread类源代码中可以看到,有好多是调用了原生的函数。

     构造方法 - Thread有一组构造方法,具体指定了线程名称(name)线程组(ThreadGroup)接口类(Runnable)栈大小(stackSize)等参数 具体如下:


public Thread()
public Thread(Runnable target)
Thread(Runnable target, AccessControlContext acc)
public Thread(ThreadGroup group, Runnable target)
public Thread(String name)
public Thread(ThreadGroup group, String name)
public Thread(Runnable target, String name)
public Thread(ThreadGroup group, Runnable target, String name)
public Thread(ThreadGroup group, Runnable target, String name, long stackSize)

   isAlive() - 方法isAlive()是判断当前的线程是否处于活动状态。而这个活动状态指的是:线程已经启动且尚未终止,如正在运行,准备开始运行的状态,都认为线程是“存活”的。

   sleep() - 在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。

   getId() - 取得线程的唯一标识。每个线程在初始化的过程中都会调用nextThreadID方法 获取到一个唯一标识。


private static long threadSeqNumber;
private static synchronized long nextThreadID() {
   return ++threadSeqNumber;
}

    在一个进程中,线程的ID是唯一的。

    停止线程 - 停止线程是在多线程开始时很重要的技术点,而停止线程在Java中并不像break语句那样干脆,需要一些技巧性的处理。


   在Java中有以下3种方法可以终止正在运行的线程。

   使用退出标志,使线程正常退出,即当run方法完成后线程终止。

   使用stop方法强行终止线程,但是不推荐使用该方法,因为stop和suspend及resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果。

使用interrupt方法中断线程。

  暂停线程 -  暂停线程意味着此线程还可以恢复运行。使用suspend()方法暂停线程,resume()方法恢复线程的执行。

   yield - yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但是放弃的时间不确定,有可能刚放弃,马上又获得CPU时间片了。线程优先级 -  在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资料较多,也就是CPU优先执行优先级较高的线程对象中的任务。在Thread中,我们使用setPriority()方法设置优先级别。


java的线程优先级分为1~10这10个等级
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

     线程优先级具有继承特性, 比如A线程启动B线程,则B线程的优先级与A是一样的。                     优先级具有规则性,虽然我们使用setPriority()方法设置了优先级,但是真正执行的过程中,不会保证优先级高的线程绝对比优先级低的线程优先完成。即CPU尽量将执行资源让给优先级比较高的线程。

   *优先级具有随机性,具优先级较高的线程不一定每一次都先执行完。

    守护线程 - 在Java线程中有两种线程,一种是用户线程,另一种是守护线程守护线程是一种特殊的线程,它的特性有“陪伴”的含义,当进程中不存在非守护线程了,则守护线程自动销毁。

    典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程则没有存在的必要了,自动销毁。

    只要当前JVM实例中存在任何一个非守护线程没有结束,守护线程就在工作,只有当最后一个非守护线程结束时,守护线程才随着JVM一同结束工作。

    通过调用Thread.setDaemon(true)设置是否为守护线程。


实现Runnable接口

在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。

下面是一个例子:


publicclass Test {
    publicstatic void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyRunnable runnable = newMyRunnable();
        Thread thread = newThread(runnable);
        thread.start();
    }
}
classMyRunnable implementsRunnable{
    publicMyRunnable() {
    }
    @Override
    publicvoid run() {
        System.out.println("子线程ID:"+Thread.currentThread().getId());
    }
}

     Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这根普通的方法调用没有任何区别。

     事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。

    在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。

     使用ExecutorService、Callable、Future实现有返回结果的多线程。


线程的状态

     在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解。

    创建(new)状态: 准备好了一个多线程的对象

    就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度

    运行(running)状态: 执行run()方法

    阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用

    终止(dead)状态: 线程销毁

    当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。

    线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。

  注:sleep和wait的区别:

  sleep是Thread类的方法,wait是Object类中定义的方法.

  Thread.sleep不会导致锁行为的改变, 如果当前线程是拥有锁的, 那么Thread.sleep不会让线程释放锁.

  Thread.sleep和Object.wait都会暂停当前的线程. OS会将执行时间分配给其它线程. 区别是, 调用wait后, 需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间.


上下文切换

    对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。

    由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。

    因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

    说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行

    虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。

目录
相关文章
|
5天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
25 9
|
8天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
5天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
8天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
22 3
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
7天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
18 1
|
8天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
8天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
35 1
|
6月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
3月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
64 1