Java实现多线程

news2024/12/24 0:48:41

目录

基本概念

1、程序、进程、线程

2、使用线程的优点

3、线程的分类

4、线程的生命周期

多线程的实现方法

1、继承Thread类

2、实现Runnable接口

3、实现Callable接口

4、使用线程池

线程同步

1、同步代码、同步方法

2、同步机制中的锁

3、锁(Lock)

4、线程的通信

5、经典例题:

(1)生产者消费者问题

(2)车票问题



基本概念

1、程序、进程、线程

首先先来了解关于什么是程序、进程、线程

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代

,静态对象。

进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态 的过程:有它自身的产生、存在和消亡的过程。—生命周期
1、如:运行中的QQ,运行中的MP3播放器
2、程序是静态的,进程是动态的
3、进程作为资源分配的单位, 系统在运行时会为每个进程分配不同的内存区域
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
1、若一个进程同一时间并行执行多个线程,就是支持多线程的。
2、线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
3、一个进程中的多个线程共享相同的内存单元/内存地址空间 -> 它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患
进程与线程

一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

2、使用线程的优点

1、提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2、提高计算机系统CPU的利用率。
3、改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

3、线程的分类

Java中的线程分为两类:一种是守护线程,一种是用户线程
1、它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
2、守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
3、Java垃圾回收就是一个典型的守护线程。
4、若JVM中都是守护线程,当前JVM将退出。

4、线程的生命周期

(1)JDK 中用 Thread.State 类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五 种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
(2)线程周期的图例

多线程的实现方法

1、继承Thread类

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。
Thread类的特性
(1)每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常
(2)把run()方法的主体称为线程体
(3)通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
实现步骤:
1) 定义子类继承Thread类。
2) 子类中重写Thread类中的run方法。
3) 创建Thread子类对象,即创建了线程对象。
4) 调用线程对象start方法:启动线程,调用run方法。

 注意点:

1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
3. 想要启动多线程,必须调用start方法。
4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。

2、实现Runnable接口

实现步骤:

1) 定义子类,实现Runnable接口。
2) 子类中重写Runnable接口中的run方法。
3) 通过Thread类含参构造器创建线程对象。
4) Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。

关于继承Thread方式和实现Runnable方式的联系与区别

区别

(1)继承Thread类:线程代码存放在Thread子类的run方法中。

(2)实现Runnable接口:线程代码存放在接口子类的run方法中。

实现方式的好处

(1)避免了单继承的局限性。

(2)多个线程可以共享一个接口实现类的对象,非常适合多个相同的线程来处理同一份资源。

练习:创建两个分线程,让其中一个线程输出1-100之间的偶数,另一 个线程输出1-100之间的奇数。

可以使用继承Thread类或实现Runnable接口,下面我以继承Thrrad类为例

public class TestDmo {
    public static void main(String[] args) {
        OddNumber o = new OddNumber();  //创建OddNumber的对象
        EvenNumber e = new EvenNumber();  //创建EvenNumber的对象
        o.start();
        e.start();  //两个线程同时启动,没有先后之分
    }
}

class OddNumber extends Thread{  //奇数
    //方法
    public void run() {  //重写thread类的run方法(将线程的执行操作声明其中)
        for (int i = 0; i < 100; i++) {
            if(i%2!=0){
                System.out.println("奇数:"+i);
            }
        }
    }
}

class EvenNumber extends Thread{  //偶数
    //方法
    public void run() {  //重写thread类的run方法(将线程的执行操作声明其中)
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println("偶数:"+i);
            }
        }
    }
}

3、实现Callable接口

与使用Runnable相比, Callable功能更强大些
(1)相比run()方法,可以有返回值
(2)方法可以抛出异常
(3)支持泛型的返回值
(4)需要借助FutureTask类,比如获取返回结果
Future接口
1、可以对具体RunnableCallable任务的执行结果进行取消、查询是 否完成、获取结果等
2、FutrueTaskFutrue接口的唯一的实现类
3、FutureTask 同时实现了Runnable, Future接口。它既可以作为
4、Runnable被线程执行,又可以作为Future得到Callable的返回值

 

 实现Callable接口 —— JDK 5.0新增

4、使用线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前 创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
1、提高响应速度(减少了创建新线程的时间)
2、降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3、便于线程管理
      3.1  corePoolSize :核心池的大小
      3.2 maximumPoolSize :最大线程数
      3.3 keepAliveTime :线程没有任务时最多保持多长时间后会终止

线程同步

1、同步代码、同步方法

同步代码:

synchronized (对象){
       // 需要被同步的代码;
}

 同步方法:synchronized还可以放在方法声明中,表示整个方法为同步方法

public synchronized void show (String name){ 
    // 需要同步的代码
}

同步分析:

 

使用线程同步的原因是为了防止一张票被多个窗口同时卖出等类似的问题。

 

2、同步机制中的锁

同步锁机制:
在《 Thinking in Java 》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
synchronized 的锁是什么?
1、任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
2同步方法的锁:静态方法(类名 .class)、非静态方法(this
3、同步代码块:自己指定,很多时候也是指定为this或类名.class
注意:
1、必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全
2、一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)

3、锁(Lock)

JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
class A{
    private final ReentrantLock lock = new ReenTrantLock(); //实例化ReentrantLock
    public void m(){
        lock.lock();  //调用锁定方式lock()
       
        try{

            //lock.lock(); //也可以放在try里面
            //保证线程安全的代码;

        }
        finally{

             //因为防止同步代码有异常,所以将unlock写入finally里面
            lock.unlock(); //调用解锁方法unlock()

         }
    }
}
synchronized 与 Lock 的对比
1、Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了用域自动释放
2、Lock只有代码块锁,synchronized有代码块锁和方法锁
3、使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

使用Lock解决Runnable接口中的线程安全问题。(以售卖车票的问题为例子)

import java.util.concurrent.locks.ReentrantLock;

public class TestDmo {
    public static void main(String[] args) {
        Window w = new Window();  //共用一个

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w); //创建三个线程

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");  //设置三个线程的名称

        t1.start();
        t2.start();
        t3.start();  //启动三个线程
    }
}

class Window implements Runnable{
    //属性
    private int ticket = 100;  //票数及票号
    private ReentrantLock lock = new ReentrantLock();//1.实例化ReentrantLock

    //方法
    public void run() {  //实现Runnable中的抽象方法
        while(true)
            try {
                lock.lock();   //2.调用锁定方法lock()
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+",票号:"+ticket);
                    ticket--;
                }
                else {
                    break;
                }
        }finally {
                lock.unlock();  //3.调用解锁方法:unlock()
            }
    }
}

4、线程的通信

wait() 与 notify() 和 notifyAll()
wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
notifyAll ():唤醒正在排队等待资源的所有线程结束等待。

注意:

这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常
class Communication implements Runnable {
    int i = 1;
    public void run() {
        while (true) {
            synchronized (this) {
                notify();  //唤醒被阻塞的线程
                if (i <= 100) {
                       System.out.println(Thread.currentThread().getName() + ":" + i++);
                } else
                    break;

                try {
                    wait();  //使线程进入阻塞状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5、经典例题:

(1)生产者消费者问题

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。
这里可能出现两个问题:
1、生产者比消费者快时,消费者会漏掉一些数据没有取到。
2、消费者比生产者快时,消费者会取相同的数据。
class Clerk{

    private int productCount = 0;
    //生产产品
    public synchronized void produceProduct() {

        if(productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");

            notify();

        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    //消费产品
    public synchronized void consumeProduct() {
        if(productCount > 0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;

            notify();
        }else{
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class Producer extends Thread{//生产者

    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始生产产品.....");

        while(true){

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.produceProduct();
        }

    }
}

class Consumer extends Thread{//消费者
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始消费产品.....");

        while(true){

            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.consumeProduct();
        }
    }
}

public class ProductTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");

        p1.start();
        c1.start();

    }
}

(2)车票问题

三个窗口售卖100张车票。

注意:

1、不能出现重复数字的票。

2、也不能出现负数的票。

public class TestDmo {
    public static void main(String[] args) {
        Window w = new Window();  //共用一个

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w); //创建三个线程

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");  //设置三个线程的名称

        t1.start();
        t2.start();
        t3.start();  //启动三个线程
    }
}

class Window implements Runnable{
    //属性
    private int ticket = 100;  //票数及票号

    //方法
    public void run() {  //实现Runnable中的抽象方法
        while(true){
            show();  //没有可以结束循环的条件,要手动结束
        }
    }
    private synchronized void show(){  //同步方法
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+",票号:"+ticket);
            ticket--;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【电商】电商后台---采购管理模块

从供应商的管理到合同的管理&#xff0c;再到商品系统的模块的介绍、商品价格与税率维护策略&#xff0c;不知不觉已经完成了几篇文章&#xff0c;前期的准备工作完成后&#xff0c;接下来就应该进入到采购管理模块了。 几天来一直在构思如何写&#xff0c;写的内容让大家看过觉…

使用天地图加载Geoserver的图层

一、写在前面 在项目中往往使用地图作为底图(比如 天地图卫星图等)&#xff0c;再其上覆盖你的通过geoserver发布自定义图层。本文记录了我的实现方法。 二、过程 2.1 我遇到的难题 遇到难题1&#xff1a;使用无人机拍摄制作的正射影像图有几百MB甚至1个G&#xff0c;直接展示图…

YOLO系列目标检测算法——PP-YOLOE

YOLO系列目标检测算法目录 - 文章链接 YOLO系列目标检测算法总结对比- 文章链接 YOLOv1- 文章链接 YOLOv2- 文章链接 YOLOv3- 文章链接 YOLOv4- 文章链接 Scaled-YOLOv4- 文章链接 YOLOv5- 文章链接 YOLOv6- 文章链接 YOLOv7- 文章链接 PP-YOLO- 文章链接 …

深入浅出面向对象设计模式(Java)

设计模式是什么 设计模式是面向对象的一种思想。 设计模式的基本原则&#xff1f; 单一职责原则开放封闭原则里氏替换原则接口隔离原则依赖翻转原则 基本分类和为什么分为3类&#xff1f; 创建型&#xff08;怎么优雅创建对象&#xff09; 结构性&#xff08;对象的结构&am…

巧用Hibernate 完成多数据库的DDL脚本创建

巧用Hibernate 完成多数据库的DDL脚本创建 spring boot jpa 默认的orm框架就是Hibernate。 由hibernate完成数据库的读写也是主流的方式之一。但是不同数据库之间&#xff0c;建表、建索引的方言语句都有较多差别&#xff0c;很难做到一套SQL在所有数据库上进行执行。 那么Hibe…

C++11之线程库

文章目录一、thread二、mutex三、lock_guard 与 unique_lock1. lock_guard2. unique_lock四、atomic五、condition_variable在 C11 之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如 Windows 和 Linux 下各有自己的接口&#xff0c;这使得代码的…

PHP另类判断 - 数组是一维还是二维

之前有一个需求&#xff0c;需要判断一个数组是一维还是二维数组&#xff0c;如果是二维的话就要使用foreach循环来处理 在网上搜了一下给出来的都是下面所写的方式&#xff1a; if(count($updata) count($updata,1)) {// 一维 } else {// 二维 }首先我要说的是&#xff0c;上…

第三十七章 数论——博弈论(1)

第三十七章 数论——博弈论&#xff08;1&#xff09;一、Nim游戏1、题目2、结论3、结论验证4、代码二、集合——Nim游戏1、问题2、思路—SG()函数2、代码实现&#xff08;记忆化搜索&#xff09;一、Nim游戏 1、题目 2、结论 这里直接说结论&#xff1a; 假设有nnn堆石子&am…

【LeetCode每日一题】——275.H 指数 II

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 二分查找 二【题目难度】 中等 三【题目编号】 275.H 指数 II 四【题目描述】 给你一个整数数…

Jmeter分布式测试

因为jmeter本身的性能问题&#xff0c;有时候为了尽量模拟业务场景&#xff0c;需要模拟大量的并发请求&#xff0c;此时单台压力机就显得力不从心。针对这个情况&#xff0c;jmeter的解决方案是支持分布式压测&#xff0c;即将大量的模拟并发分配给多台压力机&#xff0c;来满…

三优两重政策解读

什么是三优两重&#xff1a; 优秀大数据产品、优秀大数据解决方案、优秀大数据应用案例和重点大数据企业、重点大数据资源&#xff1b; 1、申报主体 在山东省内注册登记&#xff0c;具备独立承担民事责任的能力&#xff0c;包括各类政府机关、企事业单位及社会组织。 ①.大数据…

【从零开始学习深度学习】33.语言模型的计算方式及循环神经网络RNN简介

目录1. 语言模型1.1 语言模型的计算1.2 nnn元语法的定义2. 循环神经网络RNN2.1 不含隐藏状态的神经网络2.2 含隐藏状态的循环神经网络2.3 应用&#xff1a;基于字符级循环神经网络的语言模型3. 总结1. 语言模型 语言模型&#xff08;language model&#xff09;是自然语言处理…

多媒体服务器核心实现(流管理)

多媒体服务器比较多&#xff0c;实现的功能也很复杂&#xff0c;但其核心就是是转协议&#xff0c;流管理&#xff0c;连接管理&#xff0c;就是一个时序状态机和信令结合的系统。现在的生态有很多现成的轮子&#xff0c;c/c go实现的均可以拿来就用&#xff0c;只需要按一定的…

插槽,依赖注入,动态组件,异步组件,内置组件

插槽&#xff1a;父组件和子组件内容的一个通信 子组件使用<slot>接收父组件传入的内容 如果内容有多个标签时&#xff0c;使用<template>包裹 默认插槽&#xff1a; <template v-slot:default><h2>标题</h2><p>插槽内容</p> <…

Windows——编写jar启动脚本和关闭脚本

文章目录前言启动脚本编写关闭脚本restart.bat 重启脚本前言 假设项目打包后&#xff0c;项目结构为&#xff1a; 此时如果需要再windows环境中进行项目的启动或关闭&#xff0c;需要频繁的手敲命令&#xff0c;很不方便。此时可以编写.bat脚本文件进行项目的控制。 启动脚本…

就业信息追踪|基于Springboot+Vue开发实现就业信息追踪系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

双向链表,添加,删除一个节点

文章目录前言一、创建双向链表&#xff08;重命名&#xff09;二、添加一个节点1.添加头指针&#xff1a;2.若 头指针为空3.若头指针非空三、删除一个节点1.找到某节点2.将节点从链表中删除四. 展示所有的节点五. 实验效果总结前言 链表有几种&#xff0c;大致分为&#xff1a…

小程序之会议OA项目--其他界面

目录一、tabs组件及会议管理布局1、tabs.js2、tabs.wxml3、tabs.wxss4、app.wxss5、list.js6、list.json7、list.wxml二、个人中心布局1、ucenter/index/index.js2、ucenter/index/index.wxml3、ucenter/index/index.wxss一、tabs组件及会议管理布局 1、tabs.js // component…

UDS - 15.2 RequestDownload (34) service

15.2 请求下载(34)服务 来自&#xff1a;ISO 14229-1-2020.pdf 15.2.1 服务描述 客户机使用requestDownload服务发起从客户机到服务器的数据传输(下载)。 在服务器接收到requestDownload请求消息之后&#xff0c;服务器应该在发送积极响应消息之前采取所有必要的操作来接收数据…

常用图像像素格式 NV12、NV2、I420、YV12、YUYV

文章目录目的RGBYUVYCrCb采样格式YUV 4:4:4 采样YUV 4:2:2 采样YUV 4:2:0 采样YUV 存储格式YUV422&#xff1a;YUYV、YVYU、UYVY、VYUYYUV420&#xff1a;I420、YV12、NV12,、NV21扩展目的 了解常用图像像素格式 RGB 和 YUV,像素格式描述了像素数据存储所用的格式&#xff0c;…