[Java基础系列第5弹]Java多线程:一篇让你轻松掌握并发编程的指南

2023-11-10

多线程是一种编程技术,它可以让一个程序同时执行多个任务,从而提高程序的性能和效率。但是,使用Java多线程也不是一件容易的事情,它涉及到很多复杂的概念和问题,如线程安全、同步、锁、原子类、并发集合、生产者消费者模式、线程池模式、Future模式、线程协作模式等。那么,如何才能轻松地学习和使用Java多线程呢?别担心,都在这里解决啦

目录

一、Java多线程的基本概念

二、Java多线程的用法

1.创建和启动线程

2.控制和管理线程

三、Java多线程的问题

1.线程安全问题

四、Java多线程的解决方案

五、Java多线程的总结


一、Java多线程的基本概念

        什么是多线程?多线程是一种编程技术,它可以让一个程序同时执行多个任务,从而提高程序的性能和效率。每个任务都是一个线程,它是一个轻量级的执行单元,它可以共享程序的内存空间和资源。Java支持多线程的编程,它提供了Thread类和Runnable接口来创建和管理线程。

        为什么要使用多线程?多线程有以下几个优点:

  • 可以提高程序的响应速度,例如,在一个图形用户界面(GUI)程序中,我们可以使用一个线程来处理用户的输入和输出,另一个线程来执行后台的计算或者网络请求,这样就可以避免界面卡顿或者阻塞。
  • 可以提高程序的资源利用率,例如,在一个多核处理器的系统中,我们可以使用多个线程来充分利用每个核心的计算能力,从而提高程序的运行效率。
  • 可以提高程序的设计简洁性,例如,在一个复杂的业务逻辑中,我们可以使用多个线程来分解和封装不同的功能模块,从而提高程序的可读性和可维护性。

二、Java多线程的用法

1.创建和启动线程

如何创建和启动一个线程?在Java中,我们有两种方式来创建和启动一个线程:

  • 继承Thread类:我们可以创建一个自定义的类,继承Thread类,并重写run()方法,然后创建该类的对象,并调用start()方法来启动该线程。例如:
// 创建一个自定义的类,继承Thread类
class MyThread extends Thread {
    // 重写run()方法
    public void run() {
        // 在这里写上要执行的任务
        System.out.println("Hello, I am a thread.");
    }
}

// 在主方法中创建并启动该线程
public class Main {
    public static void main(String[] args) {
        // 创建MyThread类的对象
        MyThread t = new MyThread();
        // 调用start()方法来启动该线程
        t.start();
    }
}
  • 实现Runnable接口:我们可以创建一个自定义的类,实现Runnable接口,并实现run()方法,然后创建该类的对象,并将其作为参数传递给Thread类的构造器,然后创建Thread类的对象,并调用start()方法来启动该线程。例如:
// 创建一个自定义的类,实现Runnable接口
class MyRunnable implements Runnable {
    // 实现run()方法
    public void run() {
        // 在这里写上要执行的任务
        System.out.println("Hello, I am a runnable.");
    }
}

// 在主方法中创建并启动该线程
public class Main {
    public static void main(String[] args) {
        // 创建MyRunnable类的对象
        MyRunnable r = new MyRunnable();
        // 将其作为参数传递给Thread类的构造器
        Thread t = new Thread(r);
        // 调用start()方法来启动该线程
        t.start();
    }
}

        两种方式有什么区别?一般来说,我们推荐使用实现Runnable接口的方式来创建和启动线程,因为这样有以下几个优点:

  • 避免了Java单继承的限制,我们可以让自定义的类继承其他的类,并实现Runnable接口。
  • 增加了程序的灵活性,我们可以将同一个Runnable对象传递给多个Thread对象,从而实现多个线程执行同一个任务。
  • 降低了程序的耦合性,我们可以将Runnable对象和Thread对象分离,从而实现任务和执行的解耦。

2.控制和管理线程

        如何控制和管理一个线程?在Java中,我们可以使用Thread类提供的一些方法来控制和管理一个线程,例如:

  • setName()和getName()方法:我们可以使用这两个方法来设置和获取一个线程的名字,这样可以方便地识别和区分不同的线程。
  • setPriority()和getPriority()方法:我们可以使用这两个方法来设置和获取一个线程的优先级,这样可以影响线程调度器对线程的调度顺序。线程的优先级是一个整数,范围是1到10,其中1是最低优先级,10是最高优先级,5是默认优先级。但是需要注意的是,线程的优先级并不保证线程的执行顺序,只是提高了线程被选中的概率。
  • join()方法:我们可以使用这个方法来等待一个线程的结束,这样可以保证一个线程在另一个线程之后执行。例如:
// 创建两个线程
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
// 启动第一个线程
t1.start();
// 调用join()方法来等待第一个线程的结束
t1.join();
// 启动第二个线程
t2.start();
  • sleep()方法:我们可以使用这个方法来让一个线程暂停执行一段时间,这样可以模拟一些耗时或者延迟的操作。例如:
// 在run()方法中使用sleep()方法
public void run() {
    // 在这里写上要执行的任务
    System.out.println("Hello, I am a thread.");
    // 让该线程暂停执行3秒
    Thread.sleep(3000);
    // 在这里写上要继续执行的任务
    System.out.println("Bye, I am a thread.");
}
  • interrupt()和isInterrupted()方法:我们可以使用这两个方法来中断和检查一个线程的状态,这样可以实现一些取消或者退出的功能。例如:
// 在run()方法中使用isInterrupted()方法
public void run() {
    // 在这里写上要执行的任务
    System.out.println("Hello, I am a thread.");
    // 检查该线程是否被中断
    while (!Thread.currentThread().isInterrupted()) {
        // 在这里写上要循环执行的任务
        System.out.println("I am running.");
    }
    // 在这里写上要继续执行的任务
    System.out.println("Bye, I am a thread.");
}

// 在主方法中创建并启动该线程,并调用interrupt()方法来中断该线程
public class Main {
    public static void main(String[] args) {
        // 创建MyRunnable类的对象
        MyRunnable r = new MyRunnable();
        // 将其作为参数传递给Thread类的构造器
        Thread t = new Thread(r);
        // 调用start()方法来启动该线程
        t.start();
        // 让主线程暂停执行5秒
        Thread.sleep(5000);
        // 调用interrupt()方法来中断该线程
        t.interrupt();
    }
}

三、Java多线程的问题

        在使用Java多线程时,我们可能会遇到一些问题或者需要注意一些细节,下面列举一些常见的问题和注意事项:

1.线程安全问题

        当多个线程同时访问和操作同一个共享资源时,可能会导致数据不一致或者错误。这种情况称为线程不安全。为了解决线程安全问题,我们需要保证共享资源的原子性、可见性和有序性。原子性是指一个操作不可分割,要么全部执行,要么全部不执行;可见性是指一个线程对共享资源的修改,对其他线程是可见的;有序性是指一个线程内的操作,按照代码的顺序执行。

如何保证线程安全?在Java中,我们有以下几种方式来保证线程安全:

  • 使用同步(Synchronization):同步是一种最基本的线程安全机制,它可以保证一个时间点只有一个线程可以访问和操作共享资源。同步可以通过使用synchronized关键字来实现,它可以修饰代码块或者方法,从而创建一个临界区(Critical Section),在临界区内的代码只能被一个线程执行。例如:
// 创建一个共享资源
int count = 0;
// 创建一个同步代码块
synchronized (this) {
    // 在这里访问和操作共享资源
    count++;
}
  • 使用锁(Lock):锁是一种更灵活的线程安全机制,它可以实现更细粒度的控制和更多的功能。锁是一种对象,它提供了lock()和unlock()方法来获取和释放锁,以及一些其他的方法来实现条件等待、超时等待、公平性等功能。Java提供了一些内置的锁类,如ReentrantLock、ReentrantReadWriteLock、StampedLock等。例如:
// 创建一个共享资源
int count = 0;
// 创建一个锁对象
Lock lock = new ReentrantLock();
// 调用lock()方法来获取锁
lock.lock();
try {
    // 在这里访问和操作共享资源
    count++;
} finally {
    // 调用unlock()方法来释放锁
    lock.unlock();
}
  • 使用原子类(Atomic Class):原子类是一种基于硬件指令实现的线程安全机制,它可以保证对单个变量或者数组元素的读写操作是原子的,不需要使用同步或者锁。Java提供了一些内置的原子类,如AtomicInteger、AtomicLong、AtomicReference等。例如:
// 创建一个共享资源
int count = 0;
// 使用AtomicInteger类来封装该变量
AtomicInteger atomicCount = new AtomicInteger(count);
// 使用原子类提供的方法来访问和操作该变量
atomicCount.incrementAndGet();
  • 使用并发集合(Concurrent Collection):并发集合是一种支持多个线程同时访问和操作的集合类,它可以保证集合内部的数据结构是线程安全的,不需要使用同步或者锁。Java提供了一些内置的并发集合类,如CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap、ConcurrentSkipListSet、ConcurrentSkipListMap等。例如:
// 创建一个并发集合对象
Map<String, Integer> map = new ConcurrentHashMap<>();
// 使用并发集合提供的方法来访问和操作该集合
map.put("key", 1);
map.get("key");

 四、Java多线程的解决方案

        在使用Java多线程时,我们可能会遇到一些挑战或者难题,我们需要使用一些设计模式或者框架来解决这些问题。下面我们介绍一些常用的解决方案:

  • 生产者消费者模式(Producer-Consumer Pattern):生产者消费者模式是一种解决多线程间协作的设计模式,它可以实现一个或多个生产者线程和一个或多个消费者线程之间的数据交换。生产者线程负责生产数据并放入一个共享的缓冲区,消费者线程负责从缓冲区中取出数据并消费。生产者和消费者之间需要通过一些同步机制来保证缓冲区不为空也不为满,以及避免数据的丢失或者重复。在Java中,我们可以使用BlockingQueue接口来实现生产者消费者模式,它是一种支持阻塞操作的队列,它可以自动实现缓冲区的同步和管理。例如:
// 创建一个共享的缓冲区
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
// 创建一个生产者线程
class Producer implements Runnable {
    public void run() {
        // 在这里写上要生产的数据
        String data = "Hello, I am a data.";
        try {
            // 调用put()方法将数据放入缓冲区,如果缓冲区满了,会阻塞等待
            queue.put(data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
// 创建一个消费者线程
class Consumer implements Runnable {
    public void run() {
        try {
            // 调用take()方法从缓冲区中取出数据,如果缓冲区空了,会阻塞等待
            String data = queue.take();
            // 在这里写上要消费的数据
            System.out.println("I got a data: " + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 线程池模式(Thread Pool Pattern):线程池模式是一种解决多线程资源管理的设计模式,它可以实现对多个线程的复用和控制。线程池是一种容器,它可以存储一定数量的空闲线程,当有新的任务到来时,就从线程池中取出一个线程来执行该任务,当任务完成后,就将该线程归还到线程池中。这样可以避免频繁地创建和销毁线程,提高程序的性能和稳定性。在Java中,我们可以使用Executor接口和ExecutorService接口来实现线程池模式,它们是一种支持执行Runnable或Callable任务的服务,它们可以自动实现线程池的创建和管理。例如:
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
// 创建一个Runnable任务
class MyTask implements Runnable {
    public void run() {
        // 在这里写上要执行的任务
        System.out.println("Hello, I am a task.");
    }
}
// 将任务提交给线程池执行
executor.execute(new MyTask());
// 关闭线程池
executor.shutdown();
  • Future模式(Future Pattern):Future模式是一种解决多线程异步计算的设计模式,它可以实现对一个可能需要很长时间才能返回结果的任务的代理和管理。Future是一种对象,它表示一个未来会完成的任务,它提供了一些方法来获取任务的状态和结果,以及取消任务等功能。在Java中,我们可以使用Future接口和FutureTask类来实现Future模式,它们是一种支持获取Callable任务返回值的对象,它们可以自动实现Future的功能和属性。例如:
// 创建一个Callable任务
class MyCallable implements Callable<String> {
    public String call() throws Exception {
        // 在这里写上要执行的任务,并返回结果
        Thread.sleep(5000);
        return "Hello, I am a result.";
    }
}
// 创建一个FutureTask对象,并将Callable任务作为参数传递给它
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
// 创建一个Thread对象,并将FutureTask对象作为参数传递给它
Thread t = new Thread(futureTask);
// 启动该线程
t.start();
// 在主线程中,调用FutureTask对象的get()方法来获取任务的结果,如果任务还没有完成,会阻塞等待
String result = futureTask.get();
// 在这里写上要使用结果的代码
System.out.println("I got a result: " + result);
  • 线程协作模式(Thread Cooperation Pattern):线程协作模式是一种解决多个线程之间相互等待和通知的设计模式,它可以实现一些复杂的同步逻辑。线程协作模式通常使用一些同步工具类来实现,如CountDownLatch、CyclicBarrier、Semaphore、Exchanger等。这些工具类提供了一些方法来实现线程之间的计数、阻塞、释放、交换等功能。在Java中,我们可以使用java.util.concurrent包中提供的这些工具类来实现线程协作模式。例如:
// 创建一个CountDownLatch对象,并指定需要等待的线程数量
CountDownLatch latch = new CountDownLatch(3);
// 创建三个线程,并将CountDownLatch对象作为参数传递给它们
class MyThread implements Runnable {
    private CountDownLatch latch;
    public MyThread(CountDownLatch latch) {
        this.latch = latch;
    }
    public void run() {
        // 在这里写上要执行的任务
        System.out.println("Hello, I am a thread.");
        // 调用CountDownLatch对象的countDown()方法来减少计数器的值
        latch.countDown();
    }
}
Thread t1 = new Thread(new MyThread(latch));
Thread t2 = new Thread(new MyThread(latch));
Thread t3 = new Thread(new MyThread(latch));
// 启动这三个线程
t1.start();
t2.start();
t3.start();
// 在主线程中,调用CountDownLatch对象的await()方法来等待计数器变为零,如果计数器还没有变为零,会阻塞等待
latch.await();
// 在这里写上要在所有线程结束后执行的代码
System.out.println("Bye, I am the main thread.");

五、Java多线程的总结

        Java多线程是一种非常强大和灵活的编程技术,它可以让一个程序同时执行多个任务,从而提高程序的性能和效率。Java支持多线程的编程,它提供了Thread类和Runnable接口来创建和管理线程,以及一些其他的类和接口来实现一些设计模式和框架。在使用Java多线程时,我们需要了解Java多线程的基本概念、优点、用法、问题和解决方案。通过掌握Java多线程,我们可以更好地处理并发和异步问题,并且编写出高质量和高性能的代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[Java基础系列第5弹]Java多线程:一篇让你轻松掌握并发编程的指南 的相关文章

随机推荐

  • npm的.npmrc文件在哪里?缓存及全局包文件在什么位置?

    npm的 npmrc文件在哪里 缓存及全局包文件在什么位置 npm作为node开发过程中的必备工具 长期使用之后 您可能会想 这些全局安装的node包都放在硬盘里面的哪个地方 配置文件 npmrc文件在哪里 node包的缓存位置在哪里 本文
  • Ubuntu和windows系统下安装odoo15 企业版终于安装成功了附带安装方法

    方法和社区版一样 用官方方法 轻松简单 界面清爽多了 比社区版 和14的企业版 比起来 效率高了很多 很多细节上的改变 网站模板多了 还有多了视频会议功能 安装方法 先安装数据库 sudo apt install postgresql y
  • Windows------openvino 2022.1安装步骤

    openvino安装 1 下载安装包 官网链接 https www intel com content www us en developer tools openvino toolkit download html 点击download
  • C#泛型方法的定义及使用

    在 C 语言中泛型方法是指通过泛型来约束方法中的参数类型 也可以理解为对数据类型设置了参数 如果没有泛型 每次方法中的参数类型都是固定的 不能随意更改 在使用泛型后 方法中的数据类型则有指定的泛型来约束 即可以根据提供的泛型来传递不同类型的
  • [疯狂Java]AWT:菜单栏、菜单、菜单项、菜单事件处理

    1 菜单栏 菜单 菜单项之间的关系 1 菜单栏 MenuBar 就是窗口中常见的顶层菜单栏 包含文件 编辑 格式等等子菜单的菜单条 即包含菜单的容器 2 菜单 Menu 是必定包含菜单项或者菜单 嵌套包含菜单 也叫子菜单 的容器 3 菜单项
  • python爬虫入门案例(爬取lol所有英雄名称及技能)

    滴滴滴 这几天忙着增强自己的实力 发现了一个非常适合新手的案例 案例就是爬取英雄联盟的所有英雄名称和技能 废话不多说 我们来分析分析 要练手的链接 进入此网站我们会发现 网页上的响应数据里面并没有我们想要的数据 由此我们可以猜测 该响应的数
  • onvif协议常见错误总结

    1 返回28 TCP ERROR或者error 28 SOAP ENV Receiver No route to h 可能存在的原因 原因可能是账号密码错误 或者设备不在线的原因 导致TCP连接超时 或者onvif的地址写错导致的 我当时是
  • 使用easy excel进行简单的excel表格导入导出

    1 创建项目 导入easy excel的依赖
  • 腾讯云DDoS攻击防护指南

    1 什么是DDoS攻击 DDoS是目前成本较低的一种攻击方式之一 攻击者通过控制大量肉鸡 被黑客入侵控制的终端 同时向目标站点发起访问 目标站点被大量涌入的访问会话占满性能 而无法接收实际正常用户的访问请求 形成 拒绝服务 攻击 2 腾讯云
  • 基于遗传算法二维下料问题/矩形件排样/matlab程序

    基于遗传算法的二维板材切割下料优化问题 matlab程序 关键词 遗传算法 二维板材切割 matlab 引言 二维板材切割问题在实际的工程中有很多的应用 该问题基本等同于矩形件优化排样 具体是指将若干尺寸不相同的矩形零件在给定的矩形板材上以
  • 打靶练习:DC-1

    文章目录 主机发现和nmap信息收集 web信息收集 漏洞扫描 密码爆破 获得系统立足点 MySQL渗透 web渗透 用户提权 总结 主机发现和nmap信息收集 主机发现 kali kali sudo arp scan l 查找到主机地址为
  • 计算机最最基础的原理

    这是一篇知乎的文章详细的介绍了电子计算机运算器加减的原理及存储的原理 作者 张大昭 链接 https www zhihu com question 20112194 answer 84394468 来源 知乎 著作权归作者所有 商业转载请联
  • Heroku登录失败

    Heoku 在国内 注册和登录是个大问题 不知道原来怎么注册上了 如今需要登录删除 app 就是删除不了 今天努力找了个vpn 无奈还是登录不成功 https id heroku com login 在群里问了下有人能上 就知道肯定设置的有
  • Android Update Engine 分析(二十)为什么差分包比全量包小,但升级时间却更长?

    本文为洛奇看世界 guyongqiangx 原创 转载请注明出处 原文链接 https blog csdn net guyongqiangx article details 132343017 0 导读 时不时有同学在 OTA 讨论群和 V
  • 华为OD机试真题-机房布局/栈解法【2023.Q1】

    小明正在规划一个大型数据中心机房 需要满足的条件是 确保在每个机柜边上至少要有一个电箱 已知 机房排成1排 我们用M表示机柜 I表示间隔 请你返回这整排机房 至少需要多少个电箱 如果无解请返回 1 输入描述 第一行输入一个字符串 由 M 和
  • SqliLabs Less21-22

    第二十一关 基于单引号和括号 64位编码的密码注入 1 判断注入点 有了20关的经验 输入爆破账户 admin密码 admin后抓包 发现admin输入为YWRtaW4 3D 根据提示用64位数据解码得到YWRtaW4 用url编码 为 3
  • 新老域名更替时的页面跳转

    不少站长都有换域名的经历 新买的www xxx com的域名和老域名都已经绑定在空间上了 但老域名总是不敢轻易拿掉 更换的话 跟老域名相关的所有链接都将失效 出现404的页面非常不友好 用户也将找不到原来的内容 其实用JS跳转功能可以帮站长
  • 机器学习零基础?手把手教你用TensorFlow搭建图像识别系统

    转 http www leiphone com news 201701 Y4uyEktkkwb5YhJM html http www leiphone com news 201701 2tH3DgLmsGhnDd8D html 导语 这是W
  • 基于nb-iot和arduino的气象站(一)

    基于nb iot和arduino的气象站 一 温湿度和紫外线传感器 上一篇已经介绍了项目的大概情况 这一篇先讲温湿度传感器和紫外线传感器的使用 一 温湿度传感器 我使用的温度传感器为DHT21 DHT21数字温湿度传感器是一款含有已校准数字
  • [Java基础系列第5弹]Java多线程:一篇让你轻松掌握并发编程的指南

    多线程是一种编程技术 它可以让一个程序同时执行多个任务 从而提高程序的性能和效率 但是 使用Java多线程也不是一件容易的事情 它涉及到很多复杂的概念和问题 如线程安全 同步 锁 原子类 并发集合 生产者消费者模式 线程池模式 Future