Java基础教程(15)-多线程基础

简介: 【4月更文挑战第15天】Java内置多线程支持,通过Thread类或Runnable接口实现。线程状态包括New、Runnable、Blocked、Waiting、Timed Waiting和Terminated。启动线程调用start(),中断线程用interrupt(),同步用synchronized关键字。线程安全包如java.util.concurrent提供并发集合和原子操作。线程池如ExecutorService简化任务管理,Callable接口允许返回值,Future配合获取异步结果。Java 8引入CompletableFuture支持回调。

多线程是Java最基本的一种并发模型;Java语言内置了多线程支持;

进程和线程

进程和线程的关系就是:进程和线程是包含关系;一个进程可以包含一个或多个线程,但至少会有一个线程;

在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。
某些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把子任务称为线程。

启动多线程

要创建一个新线程非常容易,只需要实例化一个 Thread 实例,然后调用它的 start() 方法;

package com.demo;

public class ThreadDemo {
   
   

    public static void main(String[] args) {
   
   
        Thread thread = new MyThread();
        thread = new Thread(new MyRunnable());
        //优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。
        thread.setPriority(10); //设置优先级

        thread.start();
    }

}

class  MyThread extends Thread{
   
   
    @Override
    public void run() {
   
   
        System.out.println("my thread run...");
    }
}

class MyRunnable implements Runnable{
   
   

    @Override
    public void run() {
   
   
        System.out.println("myRunnable is run....");
    }
}

常见的两种方法创建 Thread 实例:

  • 从 Thread 派生一个自定义类,然后覆写 run() 方法
  • 创建 Thread 实例时,传入一个 Runnable 实例

线程状态

在Java程序中,一个线程对象只能调用一次 start() 方法启动新线程,并在新线程中执行 run() 方法。一旦 run() 方法执行完毕,线程就结束了。因此,Java线程的状态有以下几种:

  • New:新创建的线程,尚未执行;
  • Runnable:运行中的线程,正在执行 run() 方法的Java代码;
  • Blocked:运行中的线程,因为某些操作被阻塞而挂起;
  • Waiting:运行中的线程,因为某些操作在等待中;
  • Timed Waiting:运行中的线程,因为执行 sleep() 方法正在计时等待;
  • Terminated:线程已终止,因为 run() 方法执行完毕

当线程启动后,它可以在 Runnable 、 Blocked 、 Waiting 和 Timed Waiting 这几个状态之间切换,直到最后变成 Terminated 状态,线程终止。

一个线程还可以等待另一个线程直到其运行结束。例如, main 线程在启动 t 线程后,可以通过 t.join() 等待 t 线程结束后再继续运行

操作线程

中断线程两种方式:

对目标线程调用 interrupt() 方法可以请求中断一个线程,目标线程通过检测 isInterrupted() 标志获取自身是否已中断。如果目标线程处于等待状态,该线程会捕获到 InterruptedException ;目标线程检测到 isInterrupted() 为 true 或者捕获了InterruptedException 都应该立刻结束自身线程;

通过标志位判断需要正确使用 volatile 关键字;volatile 关键字解决了共享变量在线程间的可见性问题。

为什么要对线程间共享的变量用关键字 volatile 声明?
在Java虚拟机中,变量的值保存在主内存中,但是,当线程访问变量时,它会先获取一个副本,并保存在自己的工作内存中。如果线程修改了变量的值,虚拟机会在某个时刻把修改后的值回写到主内存,但是,这个时间是不确定的;

volatile 关键字的目的是告诉虚拟机:
每次访问变量时,总是获取主内存的最新值;
每次修改变量后,立刻回写到主内存。

线程同步synchronized

多线程模型下,要保证逻辑正确,对共享变量进行读写时,必须保证一组指令以原子方式执行:即某一个线程执行时,其他线程必须等待:
保证一段代码的原子性就是通过加锁和解锁实现的。Java程序使用 synchronized 关键字对一个对象进行加锁;

使用 synchronized的步骤 :

  • 找出修改共享变量的线程代码块;
  • 选择一个共享实例作为锁;
  • 使用 synchronized(lockObject) { … } 。在使用 synchronized 的时候,不必担心抛出异常。因为无论是否有异常,都会在 synchronized 结束处正确释放锁;

用 synchronized 修饰方法可以把整个方法变为同步代码块, synchronized 方法加锁对象是 this ;
一个类没有特殊说明,默认不是thread-safe;

Java的 synchronized 锁是可重入锁;
死锁产生的条件是多线程各自持有不同的锁,并互相试图获取对方已持有的锁,导致无限等待;

等待和唤醒

wait() 和 notify() 用于多线程协调运行:

  • 在 synchronized 内部可以调用 wait() 使线程进入等待状态;
  • 必须在已获得的锁对象上调用 wait() 方法;
  • 在 synchronized 内部可以调用 notify() 或 notifyAll() 唤醒其他等待线程;
  • 必须在已获得的锁对象上调用 notify() 或 notifyAll() 方法;
  • 已唤醒的线程还需要重新获得锁后才能继续执行。

使用 notifyAll() 将唤醒所有当前正在 this 锁等待的线程,而 notify() 只会唤醒其中一个(具体哪个依赖操作系统,有一定的随机性)。这是因为可能有多个线程正在 getTask() 方法内部的 wait() 中等待,使用 notifyAll() 将一次性全部唤醒。通常来说, notifyAll() 更安全;

线程安全包

使用 java.util.concurrent 包提供的线程安全的并发集合可以大大简化多线程编程:多线程同时读写并发集合是安全的;

在这里插入图片描述
使用 java.util.concurrent.atomic 提供的原子操作可以简化多线程编程:原子操作实现了无锁的线程安全;适用于计数器,累加器等

线程池

能接收大量小任务并进行分发处理的就是线程池;

简单地说,线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待状态。如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理

Java标准库提供了 ExecutorService 接口表示线程池;

ExecutorService 只是接口,Java标准库提供的几个常用实现类有:

  • FixedThreadPool:线程数固定的线程池;
  • CachedThreadPool:线程数根据任务动态调整的线程池;
  • SingleThreadExecutor:仅单线程执行的线程池。
  ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService = Executors.newCachedThreadPool();
        executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("");
            }
        });

线程池在程序结束的时候要关闭。使用 shutdown() 方法关闭线程池的时候,它会等待正在执行的任务先完成,然后再关闭。shutdownNow() 会立刻停止正在执行的任务, awaitTermination() 则会等待指定的时间让线程池关闭。

需要反复执行的任务,可以使用 ScheduledThreadPool 。放入 ScheduledThreadPool 的任务可以定期反复执行。

 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("....");
            }
        },10, TimeUnit.MILLISECONDS);

获取线程返回值

Java标准库还提供了一个 Callable 接口,和 Runnable 接口比,它多了一个返回值:并且 Callable 接口是一个泛型接口,可以返回指定类型的结果。

当我们提交一个 Callable 任务后,我们会同时获得一个 Future 对象,然后,我们在主线程某个时刻调用 Future 对象的 get() 方法,就可以获得异步执行的结果。在调用 get() 时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么 get() 会阻塞,直到任务完成后才返回结果。

一个 Future 接口表示一个未来可能会返回的结果,它定义的方法有:

  • get() :获取结果(可能会等待)
  • get(long timeout, TimeUnit unit) :获取结果,但只等待指定的时间;
  • cancel(boolean mayInterruptIfRunning) :取消当前任务;
  • isDone() :判断任务是否已完成

从Java 8开始引入了 CompletableFuture ,它针对 Future 做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

相关文章
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
152 1
|
1月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
167 1
|
1月前
|
Oracle Java 关系型数据库
Java 简单教程
Java是跨平台、面向对象的编程语言,广泛用于企业开发、Android应用等。本教程涵盖环境搭建、基础语法、流程控制、面向对象、集合与异常处理,助你快速入门并编写简单程序,为进一步深入学习打下坚实基础。
321 0
|
2月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
239 10
|
2月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
140 0
|
2月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
226 16
|
3月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
3月前
|
Java 关系型数据库 数据库
Java 项目实战教程从基础到进阶实战案例分析详解
本文介绍了多个Java项目实战案例,涵盖企业级管理系统、电商平台、在线书店及新手小项目,结合Spring Boot、Spring Cloud、MyBatis等主流技术,通过实际应用场景帮助开发者掌握Java项目开发的核心技能,适合从基础到进阶的学习与实践。
486 3
|
3月前
|
数据采集 存储 前端开发
Java爬虫性能优化:多线程抓取JSP动态数据实践
Java爬虫性能优化:多线程抓取JSP动态数据实践

热门文章

最新文章