多线程的创建

news2024/11/19 17:41:37

一、基本概念

1 cpu

CPU的中文名称是中央处理器,是进行逻辑运算用的,主要由运算器、控制器、寄存器三部分组成,从字面意思看就是运算就是起着运算的作用,控制器就是负责发出cpu每条指令所需要的信息,寄存器就是保存运算或者指令的一些临时文件,这样可以保证更高的速度。也就是我们的线程运行在cpu之上。

2 进程/线程

进程是资源分配最小单位,线程是程序执行的最小单位。 计算机在执行程序时,会为程序创建相应的进程,进行资源分配时,是以进程为单位进行相应的分配。每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程

  • 总结:进程是资源分配最小单位,线程是程序执行的最小单位

  • 什么是进程:

    1. cpu从硬盘中读取一段程序到内存中,该执行程序的实例就叫做进程

    2. 一个程序如果被cpu多次被读取到内存中,则变成多个独立的进程

  • 什么是线程:

    线程是程序执行的最小单位,在一个进程中可以有多个不同的线程同时执行。

3 多线程优点

采用多线程的形式执行代码,目的就是为了提高程序的效率。

比如:现在一个项目只有一个程序员开发,需要开发功能模块会员模块、支付模块、订单模块。

如果小军,小明,小薇的开发水平一样,一个人5天只能完成一个模块,那么一个人完成全部工作需要15天,3个人同时完成这些工作只需要5天。

4 并行/串行

串行也就是单线程执行 代码执行效率非常低,代码从上向下执行; (同步操作)

并行就是多个线程并行一起执行,效率比较高。 (异步操作)

并发指两个或多个事件在同一个时间段内发生。

5 CPU时间片

  1. 单核的cpu上每次只能够执行一次线程,如果在单核的cpu上开启了多线程,则会发生对每个线程轮流执行 。

  2. Cpu每次单个计算的时间成为一个cpu时间片,实际只有几十毫秒人为感觉好像是在多线程。

  3. 对于线程来说,存在等待cpu调度的时候 该线程的状态是为就绪状态,如果被cpu调度则该线程的状态为运行状态

  4. 当cpu转让执行其他的线程时,则该线程有变为就绪状态。

6 计算密集型/IO密集型

计算密集型:长时间占用cpu;例如: 视频剪辑

IO密集型 :cpu计算时间短 , 访问外接设备时间长Input/output

7 上下文切换

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候,就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

二、创建方式

创建方式:

  • 继承Thread类创建线程

  • 实现Runnable接口创建线程

  • 使用Callable和Future创建线程

  • 使用线程池例如用Executor框架

  • 使用匿名内部类的形式创建线程

  • 使用lambda表达式创建线程

1 继承Thread类创建线程

public class ThreadDemo01 extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"在运行");
    }

    public static void main(String[] args) {
        ThreadDemo01 demo01 = new ThreadDemo01();
        demo01.start();
    }
}

2 实现Runnable接口创建线程

public class ThreadDemo02 implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ",我是子线程");
    }

    public static void main(String[] args) {
        new Thread(new ThreadDemo02()).start();
    }
}

3 使用Callable和Future创建线程

从Java 5开始,Java提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法,可以看作是线程的执行体,但call()方法比run()方法更强大。

call()方法可以有返回值。

call()方法可以声明抛出异常。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return 1;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadCallable callable = new ThreadCallable();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
        new Thread(futureTask).start();
        Integer result = futureTask.get();
        System.out.println(result);
    }
}

4 使用线程池例如用Executor框架

public class ThreadDemo03{
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        //方法参数是Runnable的实现类
        service.execute(()->System.out.println(Thread.currentThread().getName() + ">我是子线程<"));
    }
}

5 使用匿名内部类的形式创建线程

public class ThreadDemo04 {
    
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
    }
}

6 使用lambda表达式创建线程

public class ThreadDemo04 {

    public static void main(String[] args) {
        new Thread(()->System.out.println(Thread.currentThread().getName())).start();
    }
}

7 Thread和Runnable的区别

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。

  2. 可以避免java中的单继承的局限性。

  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

 三、线程状态

 

1 新建

当用new关键字创建一个线程时,还没调用start 就是新建状态。

2 就绪

调用了 start 方法之后,线程就进入了就绪阶段。此时,线程不会立即执行run方法,需要等待获取CPU资源。

3 运行

当线程获得CPU时间片后,就会进入运行状态,开始执行run方法。

4 阻塞

当遇到以下几种情况,线程会从运行状态进入到阻塞状态。

  • 调用wait方法,jvm会将线程放入等待队列(waiting queue),使线程进入等待。

  • 当线程去获取同步锁的时候,锁正在被其他线程持有,jvm会将该线程放入锁池(lock pool)

  • 其他阻塞,运行状态的线程调用sleep方法、join方法,或者进入io请求的时候,会导致线程阻塞。

需要注意的是,阻塞状态只能进入就绪状态,不能直接进入运行状态。因为,从就绪状态到运行状态的切换是不受线程自己控制的,而是由线程调度器所决定。只有当线程获得了CPU时间片之后,才会进入运行状态。

5 死亡

当run方法正常执行结束时,或者由于某种原因抛出异常都会使线程进入死亡状态。另外,直接调用stop方法也会停止线程。但是,此方法已经被弃用,不推荐使用。

四、线程的基本方法

1 线程等待:wait方法

  • 调用wait()方法,线程会进入WATING状态,只有等到其他线程的通知活被中断后才会返回。需要注意的是,在调用wait()方法的后会释放对象的锁,因此wait()方法一般被用到同步方法或者同步代码块中。

2 线程睡眠:sleep方法

  • 用sleep方法会导致当前线程休眠。与 wait方法不同的是,sleep方法不会释放当前占有的锁,会导致线程进人 TIMED-WATING 状态,而 wait方法会导致当前线程进入WATING.

3 线程让步: yield 方法

调用yield 方法会使当前线程让出(释放)CPU执行时间片,与其他线程一起重新竞争CPU 时间片。在一般情况下,优先级高的线程更有可能竞争到CPU时间片,但这不是绝对的,有的操作系统对线程的优先级并不敏感。

4 线程中断:interrupt方法

  • interrupt方法用于向线程发行一个终止通知信号,会影响该线程内部的一个中断标识位,这个线程本身并不会因为调用了interrupt方法而改变状态(阻塞、终止等)。状态的具体变化需要等待接收到中断标识的程序的最终处理结果来判定。

5 线程加入 join方法

8 start方法和run方法的区别

  • join方法用于等待其他线程终止,如果在当前线程中调用一个线程的 join方法,则当前线程转为阻塞状态,等到另一个线程结束,当前线程再由阻塞状态转为就绪状态,等待获取CPU 的使用权。在很多情况下,主线程生成并启动了子线程,需要等到子线程返回结果并收集和处理再退出,这时就要用到join方法,具体的使用方法如下:

    System.out.println(”子线程运行开始!"):
    ChildThread childThread - new ChildThread();
    childThread.join(0);//等待子线程childThread执行结束
    System.out.println("子线join ()结束,开始运行主线程");

    6 线程唤醒: notify方法

    Object类有个 notify方法,用于唤醒在此对象监视器上等待的一个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的我们通常调用其中一个对象的 wait方法在对象的监视器上等待 直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他线程竞争。类似的方法还有notifyAll,用于唤醒在监视器上等待的所有线程。

    7 sleep方法与 wait方法的区别如下:

  • sleep方法属于Thread类,wait方法则属于Object类。

  • sleep方法暂停执行指定的时间,让出 CPU 给其他线程,但监控器状态依然保持在指定的时间过后又会自动恢复运行状态。

  • start方法用于启动线程,真正实现了多线程运行。在调用了线程的start方法后,线程会在后台执行,无须等待run方法体的代码执行完毕,就可以继续执行下面的代码。

  • 在通过调用 Thread 类的 start方法启动一个线程时,此线程处于就绪状态,并没有运行。

  • run方法也叫作线程体,包含了要执行的线程的逻辑代码,在调用run方法后,线程就进入运行状态,开始运行 run方法中的代码。在run方法运行结束后,该线程终止,CPU再调度其他线程

  • 在调用sleep方法的时候线程不会释放对象锁。

  • 在调用wait方法时,线程会放弃对象锁,进入等此对象的等待锁池,只有针对此对象调用notify方法后,该线程才能进人对象锁池准备获取对象锁,并进入运行状态。

 五、线程安全问题

1 问题出现的原因

如果有多个线程在同时运行,而这些线程可能会同时操作一个变量。程序每次运行结果和单线程运行的结果是一样

的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

# 我们通过一个案例,演示线程的安全问题: 

电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个 
(本场电影只能卖100张票)。 
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票) 
需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟
public class Ticket implements Runnable {
    private int ticket = 100;
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
        //每个窗口卖票的操作
        //窗口 永远开启
        while (true) {
            if (ticket > 0) {//有票 可以卖
                //出票操作
                //使用sleep模拟一下出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //获取当前线程对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖:" + ticket--);
            }
        }
    }
}
public class Demo01 {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");

        t1.start();
        t2.start();
    }
}

 

可以看到同一张票被买了两次,这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写 操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全。

2 如何解决问题

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。 要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。

窗口1线程进入操作的时候,窗口2线程只能在外等着,窗口1操作结束,窗口1和窗口2才有机会进入代码 去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU 资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

3 解决方法

  • 同步代码块。

  • 同步方法。

  • 锁机制

 

 同步代码块

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  • 锁对象 可以是任意类型。

  • 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){
	需要同步操作的代码
}
public class Ticket implements Runnable {
    private int ticket = 100;
    Object lock = new Object();
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
        //每个窗口卖票的操作
        //窗口 永远开启
        while (true) {
            synchronized (lock){
                if (ticket > 0) {//有票 可以卖
                    //出票操作
                    //使用sleep模拟一下出票时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //获取当前线程对象的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在卖:" + ticket--);
                }
            }

        }
    }
}

当使用了同步代码块后,上述的线程的安全问题,解决了。

 

同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外 等着。

同步锁是谁?

  • 对于非static方法,同步锁就是this。

  • 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

格式:

public synchronized void method(){
	可能会产生线程安全问题的代码
}
public class Ticket implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        //每个窗口卖票的操作 ,窗口 永远开启
        while(true){
            sellTicket();
        }
    }
    /*
     * 锁对象 是 谁调用这个方法 就是谁
     * 隐含 锁对象 就是 this
     *
     */
    public synchronized void sellTicket(){
        if(ticket>0){//有票 可以卖
            //出票操作
            //使用sleep模拟一下出票时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取当前线程对象的名字
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在卖:"+ticket--);
        }
    }
}

Lock锁

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock() :加同步锁。

  • public void unlock() :释放同步锁。

语法格式

Lock lock = new ReentrantLock();
try{
    lock.lock();//加锁操作
}finally{
    lock.unlock();
}
public class Ticket implements Runnable {
    private int ticket = 100;
    Lock lock = new ReentrantLock();
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
        //每个窗口卖票的操作永远开启
        while(true){
            lock.lock();
            if(ticket>0){//有票 可以卖
                //出票操作
                //使用sleep模拟一下出票时间
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //获取当前线程对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name+"正在卖:"+ticket--);
            }
            lock.unlock();
        }
    }
}

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

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

相关文章

spaCy库的实体链接踩坑,以及spaCy-entity-linker的knowledge_base下载问题

问题1. spacy Can’t find factory for ‘entityLinker’ 1&#xff09;问题 写了一个实体链接类&#xff0c;代码如下&#xff1a; nlp spacy.load("en_core_web_md")class entieyLink:def __init__(self, doc, nlp):self.nlp nlpself.doc self.nlp(doc)# Che…

MATLAB入门-矩阵的运算

MATLAB入门-矩阵的运算 本篇文章为学习笔记&#xff0c;课程链接为&#xff1a;头歌 相关知识 常见的矩阵运算有算术运算、关系运算和逻辑运算。MATLAB中的所有变量都是以矩阵的形式存储的&#xff0c;单个变量就相当于一个1*1的矩阵。 算术运算 下面展示的是常见的矩阵之…

云负载均衡

华为云&#xff1a;弹性负载均衡&#xff08;ELB&#xff09; 服务列表----弹性负载均衡 腾讯云&#xff1a;负载均衡 阿里云&#xff1a;弹性负载均衡&#xff08;SLB&#xff09;【ALB NLB CLB】 云服务&#xff1a; 云弹性负载均衡&#xff08;ELB/SLB&#xff09; 云服务…

Unity3D之简单溶解特效制作

文章目录 新建Shader Graph设置参数新建Step节点新建Simple Noise节点查看效果 新建Shader Graph 这里选择 Shader Graph -> URP -> Unlit Shader Graph 创建 设置参数 把 Surface Type 改成 Transparent 新建Step节点 创建一个Step的节点 把 Step 的 Out 连接到 Frag…

Java8-17 --- idea2022

目录 一、idea官网 二、使用idea编写hello world 三、查看工程中的JDK配置信息 四、详细设置 4.1、显示工具栏 4.2、默认启动项目配置 4.3、取消自动更新 4.4、选择整体主体与背景图 4.5、设置编辑器主题样式 4.5.1、编辑器主题 4.5.2、字体大小 4.5.3、修改注…

JS看板:bryntum taskboard 5.5.2 Crack

Bryntum 任务板是一个灵活的看板 Web 组件&#xff0c;可帮助您可视化和管理您的工作。 任务板非常灵活&#xff0c;允许您完全自定义卡片、列和泳道的渲染和样式。借助丰富的 API&#xff0c;您甚至可以在运行时打开或关闭功能。 在繁忙的团队中跟踪任务可能会令人畏惧。任务…

AMD GPU 内核驱动架构分析(一)

在Linux系统中&#xff0c;GPU驱动是集成在DRM框架中的&#xff0c;AMD GPU的内核驱动结构是一个复杂的软件堆栈&#xff0c;负责管理和控制AMD图形硬件&#xff0c;以便应用程序可以与GPU进行通信并利用其图形处理能力。以下是AMD GPU内核驱动的主要组成部分&#xff1a; 硬件…

【操作系统】聊聊进程、线程、协程

进程内部有那些数据 为什么创建进程的成本高 进程和线程 进程是资源分配的基本单位&#xff0c;而线程是程序执行的基本单位&#xff0c;一个是从资源分配的角度看&#xff0c;另一个是执行角度。 那么进程和程序的区别是什么&#xff1f; 程序&#xff0c;一段代码&#xff…

Linux 6.6 初步支持AMD 新一代 Zen 5 处理器

AMD 下一代 Zen 5 CPU 现已开始为 Linux 6.6 支持提交相关代码&#xff0c;最新补丁包括提供温度监控和 EDAC 报告等。 最新的 Linux 6.6 代码中已经加入了包括支持硬件监视器温度监控和 EDAC 报告的补丁。此外&#xff0c;新版本还加入了 x86 / misc 补丁&#xff0c;Phoronix…

9.11C高级day4

实现一个对数组求和的函数&#xff0c;数组通过实参传递给函数 sum0 function add() {for i in $*  #$*接收传来的所有参数do((sumi))doneecho $sum }#定义一个数组 arr(1 2 3 4 5 7 9)#向函数传参 add ${arr[*]}写一个函数&#xff0c;输出当前用户的uid和gid&#xff0c;并…

shell中分支语句,循环语句,函数

实现对一个数组求和的函数&#xff0c;将数组作为实参传给函数 #!/bin/bash sum() {for i in $do((sumi))doneecho $sum} read -p "请输入一组数字: " -a arr sum ${arr[*]}2 调用函数&#xff0c;输出当前用户的uid gid 并使用变量接收结果 #!/bin/bashget() {uid…

R语言发送邮件丨blastula包使用教程

R语言自动发送邮件 今天分享一个小技巧&#xff0c;用R语言脚本实现STMP邮件自动发送功能。 生信分析实际操作过程中&#xff0c;可能会偶尔会遇到耗时比较久的计算&#xff0c;比如基因组上游分析GATK标准流程往往耗时几天时间。 使用R语言自动发送邮件&#xff0c;在关键节点…

【性能测试】Jmeter —— jmeter计数器

jmeter计数器 如果需要引用的数据量较大&#xff0c;且要求不能重复或者需要递增&#xff0c;那么可以使用计数器来实现 如&#xff1a;新增功能&#xff0c;要求名称不能重复 1&#xff0c;新增计数器 计数器&#xff1a;允许用户创建一个在线程组之内都可以被引用的计数器…

LeGo-LOAM 源码解析

文章目录 0、整体框架1、imageProjection —— 点云分割0. main()1. cloudHandler()2. copyPointCloud()3. findStartEndAngle()4. projectPointCloud()5. groundRemoval()6. cloudSegmentation()7. labelComponents()8. publishCloud()9. resetParameters() 2、featureAssocia…

宠物互联网医院|宠物互联网医院掌上宠物的健康助手

宠物互联网医院依托于互联网技术&#xff0c;将宠物健康管理与现代科技相结合&#xff0c;为宠物主人们提供了全方位、便捷的养宠服务。通过智能手机APP&#xff0c;宠物主人不仅能够快速轻松地了解自己宠物的健康状况&#xff0c;还能够与兽医进行实时的在线咨询、预约就诊。不…

迷你无人车 Navigation 导航(3)

迷你无人车 Navigation 导航&#xff08;3&#xff09; 自己实现了对于迷你无人车关节的控制&#xff0c;由于原本的关节布置仅支持阿克曼转向&#xff0c;因此先进行阿克曼转向的控制 修改 URDF 文件 添加 transmission 标签&#xff0c;定义关节的驱动 <transmission …

MySQL Oracle区别

由于SQL Server不常用&#xff0c;所以这里只针对MySQL数据库和Oracle数据库的区别 (1) 对事务的提交 MySQL默认是自动提交&#xff0c;而Oracle默认不自动提交&#xff0c;需要用户手动提交&#xff0c;需要在写commit;指令或者点击commit按钮 (2) 分页查询 MySQL是直接在SQL语…

使用鳄鱼指标和ADX开立空头的条件,3秒讲清楚

使用鳄鱼指标和ADX开立空头的条件其实很简单&#xff0c;anzo capital昂首资本3秒钟讲清楚。 首先&#xff0c;市场行情需呈水平状态。再者&#xff0c;均线体系开始向上发散&#xff0c;给出明确的信号。最后&#xff0c;ADX确认该信号&#xff0c;要求指数上涨20%以上&#…

Weblogic(CVE-2017-10271)与 Struts2(s2-045) 反序列化漏洞复现

文章目录 Java 反序列化漏洞复现weblogic环境搭建漏洞复现 Struts2(s2-045)环境搭建漏洞复现**漏洞利用** Java 反序列化漏洞复现 weblogic Weblogic < 10.3.6 ‘wls-wsat’ XMLDecoder 反序列化漏洞&#xff08;CVE-2017-10271&#xff09; ​ Weblogic的WLS Security组…

深度学习模型复杂度分析大杂烩

深度学习模型复杂度分析大杂烩 时间复杂度和空间复杂度是衡量一个算法的两个重要指标,用于表示算法的最差状态所需的时间增长量和所需辅助空间. 在深度学习神经网络模型中我们也通过&#xff1a; 计算量/FLOPS&#xff08;时间复杂度&#xff09;即模型的运算次数 访存量/By…