Java多线程——生命周期、并发、临界资源问题

news2024/11/19 23:33:01

目录

进程是什么?

线程是什么?

那这样做(同时运行)有什么优点呢?(为什么要有多线程?)

那什么时候需要使用多线程?

那线程和进程的关系是什么?

那线程和进程之间有什么区别?

线程的生命周期有哪些?

实战演练:

方法一:继承Thread,重写run()方法

start()和run()的区别?

方法二:使用Runnable接口

线程的常用方法——线程命名、优先级、礼让

1、线程命名

2、线程优先级

3、线程礼让

4、线程睡眠

sleep和wait区别?

什么时候数据在多线程并发的环境下会存在安全问题呢?

临界资源问题

什么是临界资源?

实战演练:

解决方案有哪些?

方法一、同步代码段

方法二、同步方法

方法三、显示锁


进程是什么?

对程序进行占用各种资源的集合。我们打开自己电脑的任务管理器就能看到不同的进程在不停的进行切换,我们可以把每一个.exe文件的执行认为是一个进程

线程是什么?

是进程执行的最小单元。这样说是不是很抽象?比方拿我们的浏览器来说,一个浏览器可以同时下载几幅图片,可以边听歌的同时边聊天、边播放视频的同时还可以打印文件,这每一个行为我们都可看作是一个不同的线程,不同的线程可以同时运行。

那这样做(同时运行)有什么优点呢?(为什么要有多线程?)

随着技术发展,操作系统演变的会为每个进程分配CPU资源,由于CPU运行速度非常快,线程之间频繁切换,使得我们看起来所有的线程都在同一个时刻执行

那什么时候需要使用多线程?

1、程序需要同时执行两个或多个任务

2、需要一些后台运行的程序时

当我们在访问浏览器看网页的时候,我们正在等待图片下载完成,等着百度服务器那边给我们返回结果,那等待的这个过程中其实浏览器是一直占着CPU的,在等待的间隙我们就可以让我们的浏览器先干点其他的事情释放掉CPU资源,不要一直霸占着,等百度服务器把结果响应给我们之后再继续回来执行,这样既提高了程序处理速度,也提高了CPU的利用率

那线程和进程的关系是什么?

我们每打开一个程序就在计算机里增加了一个进程,计算机要给程序分配不同的系统资源,比方说CPU,每个进程所需要的cpu大小也不一样,比方说谷歌浏览器、xmind占的cpu资源比较少就少分配一些,游戏软件或者idea这种就需要分配多一些cpu资源。而一个软件也可能会干不同的事情,比方说QQ,听歌的同时还能QQ截图、收消息,而听歌、截图等这些都可以看成是一个一个的线程

注:进程和进程之间内存空间不共享,但线程和线程共享堆内存和方法区,但是栈内存独立,一个线程一个栈,如:

阿里巴巴和京东他们就可以看作是两个不同的进程,他们之间不共享资源

那线程和进程之间有什么区别?

进程:每个进程都有独立的代码和数据空间,进程间切换开销大

线程:同一个进程中的线程共享资源,每个线程有独立的运行栈和PC,线程切换开销小

我们通过一张图来表示一下它们之间的关系:

一个进程正在运行时至少有一个线程正在运行,比方说main()方法

线程的生命周期有哪些?

一个线程也是有生到死的过程的,一共有5个线程,我们来看看它们分别都做了些什么吧

当我们的程序执行需要两种资源:CPU(谁干活)、内存(要占的地方)

目标就是怎么让我们的cpu在不同程序之间快速切换,怎么用进程实现并发?看似并发实际是切换很快有一定顺序

接下来我们来看看线程具体是怎么使用的吧!

实战演练:

线程的创建

方法一:继承Thread,重写run()方法

public class ThreadCreate {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        //myThread.run();

        System.out.println("主线程执行结束");
    }
}

class MyThread extends Thread {
    //重写run方法,需要并发的任务写到run方法中
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程1--" + i);
        }
    }
}

start()和run()的区别?

继承重写run()方法,run方法就被当成了一个普通方法来运行,在jvm就只有main方法这一个主线程,需要等到run方法体逻辑走完才能执行下面的逻辑。没有实现多线程

缺点:java是单继承,继承Thread类重写run()方法,无法继承其他的类

start方法会产生新线程,用来启动线程,当调用start方法后此时线程变为了就绪态,说明具有争夺CPU的权利,当获得CPU之后便开始执行run方法体内逻辑。实现多线程

方法二:使用Runnable接口

好处:上面我们讲到有由于java是单继承,为了支持多继承的特点,我们可以实现Runnable接口

package com.example;

public class ThreadCreate {
    public static void main(String[] args) {
        Runnable r1 = () -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程2--" + i);
            }
        };

        Thread t2 = new Thread(r1);
        t2.start();

        System.out.println("主线程执行结束");
    }
}

下面我们再来一起看看线程常用的一些设置


线程的常用方法——线程命名、优先级、礼让

class MyThreads extends  Thread{
    public MyThreads(String name){
        this.setName(name);
    }
}

1、线程命名

public class ThreadName {
    public static void main(String[] args) {
        //方法一:重写构造函数,将名称通过实例化对象的同时传入复制
        MyThreads thread = new MyThreads("young");

        //方法二:通过set方法设置
        Thread t1= new Thread();
        t1.setName("t1");

        //方法三:自带的无参构造函数设置
        Thread t2= new Thread("t2");

        //方法四:实例化线程对象的同时,通过构造方法对线程进行命名
        Thread t3= new Thread(()->{},"t3");

    }
}

2、线程优先级

优先级:1-10,一个线程的缺省优先级是5

  • Thread.MIN_PRIORITY =1
  • Thread.MAX_PRIORITY=10
  • Thread.NORM_PRIORITY=5(默认)

注意:线程的优先级只是说明了线程可以抢到CPU的概率,并不是说就能抢到CPU或者谁的优先级高就先把这个线程执行完

public class ThreadLevel {
    public static void main(String[] args) {
        Runnable r = () -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        };

        //创建连个线程分别叫t1和t2
        Thread t1 = new Thread(r, "Thread-1");
        Thread t2 = new Thread(r, "Thread-2");

        //设置线程t1的优先级比t2的优先级高
        t1.setPriority(1);
        t2.setPriority(10);

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

3、线程礼让

让出当前的运行状态的线程释放自己CPU资源,运行态——>就绪态

public class ThreadYield {
    public static void main(String[] args) {
        Runnable r= new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                    if(i==3){
                        Thread.yield();
                    }
                }
            }
        };

        Thread t1= new Thread(r,"t1-");
        Thread t2= new Thread(r,"t2-");

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

4、线程睡眠

sleep单位是毫秒,每间隔2秒执行一次

class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程执行---" + i);
            try {
                //sleep单位是毫秒,每间隔2秒执行一次
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadSleep {
    public static void main(String[] args) {
        MyThread2 myThread = new MyThread2();
        myThread.start();
        System.out.println("主线程执行结束");
    }
}

sleep和wait区别?

1、sleep():休眠了自己可以醒过来。使用sleep是别的线程也不可以访问锁定的对象

2、wait():休眠了自己无法醒过来。需要用notify()方法使线程醒过来。使用wait时别的线程可以访问锁定的对象

3、stop方法。泼凉水,直接把进程杀死。比方说正在打开一个程序,stop方法,这个程序可能造成程序就一直关不上了。很粗暴!!

O.waite()方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁

O.notify()方法只会通知,不会释放之前占有的o对象的锁。


什么时候数据在多线程并发的环境下会存在安全问题呢?

1、多线程并发

2、有共享数据

3、共享数据有修改行为

上图是经典的银行取钱问题,当张三和张三媳妇同时去银行取钱,张三拿着银行卡去柜台取钱,张三媳妇在不知情的情况下拿着存折去银行取钱,他们同时都要取1000元,这时候多线程的情况下就出现了数据不一致的情况,他们共从银行拿走了2000元,而账户余额只有1000元。

那如何解决出现的安全问题?为了更好的讲述线程安全以及解决线程安全的问题,下面我从一个售票员卖票的例子讲起


临界资源问题

什么是临界资源?

多个线程同时争夺资源

实战演练:

class TicketCenter {
    //剩余票数
    static int restCount = 100;
}
package com.example;

import java.util.concurrent.locks.ReentrantLock;

public class CriticalResource {
    public static void main(String[] args) {
        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount +
                        "张");
            }

        };

        Thread t1 = new Thread(r, "t1--");
        Thread t2 = new Thread(r, "t2--");
        Thread t3 = new Thread(r, "t3--");
        Thread t4 = new Thread(r, "t4--");

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

我们来看看控制台输出结果:

从输出结果我们可以看出来当多个线程并发的时候产生了数据不一致的情况,t4线程执行完毕之后剩余票数为91张,当t3线程开始执行的时候剩余票数却为97……

此时票数100就是那个临界资源,四个线程都在访问临界资源,导致数据不一致。那如何解决这个问题呢?我们可以通过三个办法来解决:同步代码段、同步方法、显示锁

解决方案有哪些?

方法一、同步代码段

添加synchronized锁,当剩余票数大于0的时候就给临界资源加上锁,此时其他线程在无法访问或修改临界资源,处于就绪状态排队等待,等待的线程需要等到当前线程释放了cpu才能进行访问

注意:当多个并发线程访问同一个同步代码段时,一段时间内只能有一个线程被执行

package com.example;

import java.util.concurrent.locks.ReentrantLock;

public class CriticalResource {
    public static void main(String[] args) {
        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                //需要在锁当中加一层判断,否则会出现票数剩余负三的情况。
                //原因:剩余票数大于0,线程进入循环,获得了抢占cpu的资格,看到资源加锁了进入排队等待;
                //如果三个线程都进入了排队状态,那么当这三个线程拿到cpu资源之后能够继续执行减减操作,导致票数出现了负三的情况
                synchronized ("") {
                    //方法一:同步代码段
                    //当票数剩余0的时候退出当前循环,表示票已经卖完
                    if (TicketCenter.restCount <= 0) {
                        return;
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount +
                    "张");
                }
            }
        };

        Thread t1 = new Thread(r, "t1--");
        Thread t2 = new Thread(r, "t2--");
        Thread t3 = new Thread(r, "t3--");
        Thread t4 = new Thread(r, "t4--");

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


class TicketCenter {
    //剩余票数
    static int restCount = 100;
}

方法二、同步方法

package com.example;

import java.util.concurrent.locks.ReentrantLock;

public class CriticalResource {
    public static void main(String[] args) {
        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                synchronized ("") {
                    //方法二:同步方法,将要加锁的方法抽成方法,不影响实际效果
                    soldTicket ();
                }
            }
        };

        Thread t1 = new Thread(r, "t1--");
        Thread t2 = new Thread(r, "t2--");
        Thread t3 = new Thread(r, "t3--");
        Thread t4 = new Thread(r, "t4--");

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


    //方法二:同步方法
    //需要在锁当中加一层判断,否则会出现票数剩余负三的情况。
    //原因:剩余票数大于0,线程进入循环,获得了抢占cpu的资格,看到资源加锁了进入排队等待;
    //如果三个线程都进入了排队状态,那么当这三个线程拿到cpu资源之后能够继续执行减减操作,导致票数出现了负三的情况
    private synchronized static void soldTicket() {
        if (TicketCenter.restCount <= 0) {
            return;
        }
        System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount + "张");
    }
}


class TicketCenter {
    //剩余票数
    static int restCount = 100;
}

方法三、显示锁

多个线程看到的锁是同一个

package com.example;

import java.util.concurrent.locks.ReentrantLock;

public class CriticalResource {
    public static void main(String[] args) {
        //方法三:显示锁
        Runnable r1 = () -> {
            while (TicketCenter.restCount > 0) {
                lock.lock();
                if (TicketCenter.restCount <= 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount + "张");
                lock.unlock();
            }
        };

        Thread t1 = new Thread(r, "t1--");
        Thread t2 = new Thread(r, "t2--");
        Thread t3 = new Thread(r, "t3--");
        Thread t4 = new Thread(r, "t4--");

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

class TicketCenter {
    //剩余票数
    static int restCount = 100;
}

如果有想要交流的内容欢迎在评论区进行留言,如果这篇文档受到了您的喜欢那就留下你点赞+收藏+评论脚印支持一下博主~

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

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

相关文章

echarts的基础知识和配置项

异步数据加载和更新 ECharts 中在异步更新数据的时候需要通过series的name属性对应到相应的系列&#xff0c;如果没有name&#xff0c;series就会根据数组的顺序索引&#xff0c;把数据跟前面的配置对应上 loading动画 如果数据加载时间较长&#xff0c;一个空的坐标轴放在画…

无人值守远程访问软件

什么是无人值守远程访问 无人值守的远程访问使您能够控制无人监督且物理上远离的设备。它是企业管理员简化故障排除过程的基本解决方案。 无人值守的远程访问软件有什么作用 当最终用户可能无法在远程设备面前使用时&#xff0c;可能会有不同的情况。在这种情况下&#xff0…

CODESYS模拟量超限报警功能块

博途PLC模拟量超限报警功能块详细介绍请参看下面文章链接: PLC模拟量超限报警功能块_RXXW_Dor的博客-CSDN博客模拟量偏差报警功能块请参看下面文章:模拟量偏差报警功能块(SCL代码)_RXXW_Dor的博客-CSDN博客工业模拟量采集的相关基础知识,可以查看专栏的系列文章,这里不再赘…

CnOpenData·A股上市公司员工评价数据

一、数据简介 除了股东、债权人、政府等外部利益相关者外&#xff0c;员工的利益更应该得到公司的恰当保护&#xff0c;因为员工才是公司创造价值的真正主体。提高企业在产品市场的竞争力&#xff0c;首先就是要提高员工对企业的满意度&#xff0c;只有员工的满意度更高、幸福感…

Typora下载及激活及将图片上传到图床

Typora下载及激活及将图片上传到图床 为了解决在typora编辑图片后上传博客时&#xff0c;博客图片消失的问题&#xff0c;可以进行下面的操作 Typora下载及激活 typora下载及激活 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;znbw 解压缩后将winmm.dll文件放…

【零基础入门学习Python---Python中的文件操作教程】

&#x1f680; Python &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

王道考研数据结构代码总结(第六章)

目录 图基本定义拓扑排序 本文包含王道考研讲课中所涉及的数据结构中的所有代码&#xff0c;当PPT代码和书上代码有所区别时以咸鱼的PPT为主&#xff0c;个人认为PPT上的代码比王道书上的代码要便于理解&#xff0c;此外&#xff0c;本博客也许会补充一些额外的代码进来&#x…

【C++】STL关联式容器之map和set

【关联式容器】之map和set 容器类型树形结构的关联式容器mapset&#xff0c;multiset&#xff0c;multimap的区别与联系 容器类型 在STL中&#xff0c;我们接触过许多容器&#xff0c;例如&#xff1a;vector&#xff0c;list&#xff0c;stack&#xff0c;queue&#xff0c;m…

Version of Delve is too old for this version of Go

背景 编译go报错提示信息&#xff0c;delve版本太老 执行下载dlv.exe go install github.com/go-delve/delve/cmd/dlvlatest 将下载的dlv文件放在ide目录下替换

基于DSP28335的三电平有源电力滤波器

完整的软硬件资料&#xff0c;其中包括两套基于DSP28335的三电平有源电力滤波器。这些资料可以直接使用。 提取的知识点和领域范围&#xff1a; 三电平有源电力滤波器DSP28335芯片 延申科普&#xff1a; 三电平有源电力滤波器是一种用于电力系统中的滤波器&#xff0c;用于减…

vue实现简单登录界面

使用Vue实现简单的用户登录界面&#xff0c;登录成功做路由跳转&#xff0c;背景图片可自定义。实现效果如下&#xff1a; html部分 <template><div class"content"><div class"login_container"><el-form v-model"loginData&q…

Locate and Label: A Two-stage Identifier for Nested Named EntityRecognition

原文链接&#xff1a;https://aclanthology.org/2021.acl-long.216.pdf ACL 2021 介绍 问题 span-based方法将实体识别当作span的分类任务来做&#xff0c;存在以下缺陷&#xff1a;1&#xff09;计算量大&#xff0c;为了尽可能覆盖到所有实体&#xff0c;就会对多个span进行…

基于YOLO V8的车牌识别

赵春江 2023年6月 1、前言 十年前就想实现车牌识别这项任务&#xff0c;虽然当时这项技术就已较成熟&#xff08;与现在的实现方法不同&#xff09;&#xff0c;但那时的我还具备这个能力。弹指一瞬间&#xff0c;没想到十年间人工智能技术已经发展到一个新的高度&#xff0c…

【每日编程Day29】有假币

目录 一、选择题 二、编程题 1、有假币 一、选择题 重点复习选择题4,8,10。 问题4&#xff1a; 类方法&#xff1a;又称为静态方法。而实例方法不能加static&#xff0c;又叫非静态方法。 类方法和实例方法的区别_类方法和实例方法区别_及可不遥的博客-CSDN博客 &#xff0…

效果!R微型变压器节省空间秘密揭晓!

通常&#xff0c;我们可能会遇到一些特殊的设备。由于其设计空间有限&#xff0c;R型变压器的体积应足够小&#xff0c;以便很好地应用。针对市场上的这个问题&#xff0c;作为R型变压器生产厂家&#xff0c;我们怎么能不注意呢&#xff1f;因此&#xff0c;我们的设计研发团队…

明确了!国家发布程序员和搬砖民工一样,都是农民工!

目录 前言 怎么解释新生代农民工&#xff1f; 2019年确定程序员属于密集型劳动者 新生代民工确实非常形象&#xff1a; 总结&#xff1a; 前言 前几天我们发现&#xff0c;人社局官网发布了一则报告&#xff0c;显示软件开发和信息技术服务业都属于新生农工&#xff0c;不…

前端获取地区的天气状况

翻阅了大量的帖子&#xff0c;在赛选了很多废的帖子之后找到了两个总体来说还不错的 一&#xff1a;配置高德地图 高德地图的查看天气的话&#xff0c;是每天免费100次 先访问高德官网&#xff1a;高德控制台&#xff0c;注册后申请应用&#xff0c;获取key值。 这个key值可…

三步轻松搞定,Word图片打印清晰度提升10倍

Word文档中插入图片是非常常见的操作&#xff0c;然而&#xff0c;在打印时有时会遇到图片清晰度不够的问题。这种情况下&#xff0c;即使图片在电脑上看起来很清晰&#xff0c;但是在打印时却变得模糊不清。这可能会影响工作效率&#xff0c;甚至影响到呈现效果。那么&#xf…

Java 面试必刷的1100 道Java大厂面试真题(含答案解析)

2023秋招即将来临&#xff0c;很多同学会问 Java 面试八股文有必要背吗&#xff1f; 我的回答是&#xff1a;很有必要。你可以讨厌这种模式&#xff0c;但你一定要去背&#xff0c;因为不背你就进不了大厂。 国内的互联网面试&#xff0c;恐怕是现存的、最接近科举考试的制度…

yolov8训练自有跌倒数据集

参考&#xff1a; https://www.bilibili.com/video/BV1xL411B7ax https://www.dgrt.cn/a/2364195.html?actiononClick https://blog.roboflow.com/how-to-train-yolov8-on-a-custom-dataset/ 1、数据集制作&#xff08;一般是coco格式&#xff09;&#xff1a; lableme&a…