Java多线程介绍及使用指南

news2024/11/30 7:56:45

“多线程”:并发

要介绍线程,首先要区分开程序、进程和线程这三者的区别。

程序:具有一定功能的代码的集合,但是是静态的,没有启动运行
进程:启动运行的程序【资源的分配单位】
线程:进程中的每一条执行路径,就是线程。

概念
并行:多个CPU同时执行多个任务
并发:一个CPU“同时”执行多个任务(采用时间片切换)

将1分钟---分成10000份=6毫秒

5.1使用线程的3种方式:

第一种方式:继承父类

1.创建线程类:
public class NumberThread extends Thread
2.创建线程对象:【新生状态】
NumberThread num=new NumberThread();
3.【就绪状态】--->cpu给资源--->运行状态
num.start();
如果调用run(),则不是多线程的了,会直接执行完

例子:售票窗口
有10张车票,3个窗口,同时售票,显示售票结果。

package thread;  
  
public class WindowThread extends Thread {  
    static int ticket=10;  
  
    public WindowThread() {  
    }  
  
    public WindowThread(String name) {  
        super(name);  
    }  
  
    @Override  
    public void run() {  
        while (ticket >= 1) {  
            System.out.println(getName()+"窗口,卖出第"+ticket+"票");  
            ticket--;  
        }  
    }  
}
package thread;  
  
public class TestMain {  
  
    public static void main(String[] args) {  
        WindowThread w1=new WindowThread("第1");  
        w1.start();  
        WindowThread w2=new WindowThread("第2");  
        w2.start();  
        WindowThread w3=new WindowThread("第3");  
        w3.start();  
    }
}

在这里插入图片描述

优点:启动线程对象高效率
缺点:占用了父类位置

第二种方式:实现接口

public class Window implements Runnable {  
    int ticket=10;  
    @Override  
    public void run() {  
        while (ticket >= 1) {  
            System.out.println(Thread.currentThread().getName()+"窗口卖出第"+ticket+"张票");  
            ticket--;  
        }  
    }  
}
public class Test {  
    public static void main(String[] args) {  
        Window w1=new Window();  
        Thread t1 = new Thread(w1,"第1");  
        t1.start();  
  
        Thread t2 = new Thread(w1, "第2");  
        t2.start();  
  
        Thread t3 = new Thread(w1, "第3");  
        t3.start();  
    }  
}

优点:没有占用父类位置,共享资源能力强,资源不用加static
缺点:启动线程对象 效率低

第三种方式:实现接口

对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法
但是这个run方法有不足:

在这里插入图片描述

1)没有返回值
2)不能抛出异常

基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现Callable接口:

实现Callable接口好处:(1)有返回值 (2)能抛出异常
缺点:线程创建比较麻烦

package com.msb.test05;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * @author : msb-zhaoss
 */
public class TestRandomNum implements Callable<Integer> {
    /*
    1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型
    2.如果带泛型,那么call的返回值就是泛型对应的类型
    3.从call方法看到:方法有返回值,可以跑出异常
     */
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(10);//返回10以内的随机数
    }
}
class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //定义一个线程对象:
        TestRandomNum trn = new TestRandomNum();
        FutureTask ft = new FutureTask(trn);
        Thread t = new Thread(ft);
        t.start();
        //获取线程得到的返回值:
        Object obj = ft.get();
        System.out.println(obj);
    }
}

FutureTaskRunnable接口的一个实现类,因此它可以作为参数传给Tread
用线程任务对象来接返回值(这里用的是ft)
注意:get方法的使用位置必须在start之后
创建几个线程对象,就要创建几个FutreTask,而不能new两个Thread,因为这样是一个对象的内容接了两遍

5.2线程对象的常用方法:

  • a.getName()获得当前线程对象的名字:
    ---线程名 如果开发者使用了无参构造器,程序自动设置线程名Thread-0++(主线程除外)
    ---开发者使用有参构造器,参数的值就是线程的名字。
public class NumberThread extends Thread{  
  
    public NumberThread() {  
    }  
  
    public NumberThread(String name) {  
        super(name);  
    }  
    @Override  
    public void run() {    //第二条执行路线的内容  
        for (int i = 1; i < 100; i++) {  
            System.out.println(super.getName()+"i="+i);   //获得当前线程名  
        }  
    }  
}

---借助num.setName("线程1"),为线程名赋值

public class TestMain {  
    //main()方法所在的线程叫主线程  
    public static void main(String[] args) {  
        NumberThread num=new NumberThread("窗口1");  //此时程序开启第二条路  
        num.start();  
        NumberThread num2=new NumberThread("窗口2");  //此时程序开启第二条路  
        num2.start();  
        for (int i = 1; i < 100; i++) {  
            System.out.println(Thread.currentThread().getName()+"i="+i);  
        }  
    }  
}
  • b.currentThread()获得当前正在运行的线程对象
    针对实现接口的情况,由于没有再继承Thread类,因此也无法直接使用其中的方法getName(),但Thread类中有静态方法currentThread(),因此可以通过这种方法获得当前运行的线程对象的信息。
  • c.setPriority()设置线程的优先级,1-10之间,默认是5
Thread t2 = new Thread(w1, "第2");  
t2.setPriority(1);  //设置线程的优先级,1-10之间,默认是5  
t2.start();
  • d.Thread.currentThread().stop() 过期方法,不建议执行
  • e.强行占用。当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。
public static void main(String[] args) throws InterruptedException {  
    NumberThread num=new NumberThread("窗口1");  //此时程序开启第二条路  
    num.start();  
   for (int i = 1; i < 100; i++) {  
        System.out.println(Thread.currentThread().getName()+"i="+i);  
        if (i == 50) {  
            num.join();  //强势加入(num运行完了后,其余的才能执行)
            System.out.println("maini=50");  
        }  
    }  
}
  • f.setDaemon(true);伴随线程
    tt.setDaemon(true);//设置伴随线程
    主线程死亡,伴随线程也会跟着一起死亡。
public class TestThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 1000 ; i++) {
            System.out.println("子线程----"+i);
        }
    }
}
class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //创建并启动子线程:
        TestThread tt = new TestThread();
        tt.setDaemon(true);//设置伴随线程  注意:先设置,再启动
        tt.start();
        //主线程中还要输出1-10的数字:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println("main---"+i);
        }
    }
}
  • g.sleep(毫秒):设置线程休眠
public static void main(String[] args) throws InterruptedException {  
    System.out.println("1111");  
    Thread.sleep(1000);  //休眠的例子
    System.out.println("222");  
}

5.3线程的生命周期:

使用线程构造器---创建线程对象--->线程新生状态
创建线程对象.start()--->进入到就绪状态【有资格,没资源】
线程对象.run()--->进入到运行状态【有资格,有资源】

在时间片段内,执行完--->死亡状态
在时间片段内,没执行完--->重回就绪状态
在时间片段内,出现突发事件--->阻塞状态--->就绪状态

在这里插入图片描述

5.4解决线程安全问题:

【第一种:同步代码块】

public void run() {  
  
    while (true) {  
        synchronized (WindowThread.class){ //WindowThread.class是监视器对象  
        if(ticket >= 1) {  
            System.out.println(getName() + "窗口,卖出第" + ticket + "票");  
            ticket--;  
            }  
        }  
    }  
}

总结:
同步监视器总结:
1:认识同步监视器(锁子) ----- synchronized(同步监视器){ }
1)必须是引用数据类型,不能是基本数据类型
2)也可以创建一个专门的同步监视器,没有任何业务含义
3)一般使用共享资源做同步监视器即可
4)在同步代码块中不能改变同步监视器对象的引用
5)尽量不要String和包装类Integer做同步监视器
6)建议使用final修饰同步监视器

2:同步代码块的执行过程
1)第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码
2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open
3)第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态
4)第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open
5)第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)
强调:同步代码块中能发生CPU的切换吗?能!!! 但是后续的被执行的线程也无法执行同步代码块(因为锁仍旧close)

3:其他
1)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块
2)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块, 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块

【第二种:同步方法】

public class WindowThread extends Thread {  
    static int ticket = 300;  
  
    public WindowThread() {  
    }  
  
    public WindowThread(String name) {  
        super(name);  
    }
@Override  
public void run() {  
    while (true) {  
        if (ticket == 0) {  
            break;  
        } else {  
            buyTicket();  
        }   
    }  
}  
  
public static synchronized void buyTicket(){   
//如果只有synchronized,锁住的只是当前this对象(相当于每一个都有300张票)
//如果加了static,锁住的就是方法。
    if(ticket >= 1) {  
        System.out.println(Thread.currentThread().getName() + "窗口,卖出第" + ticket + "票");  
        ticket--;  
	    }  
	}
}

总结:
1:
多线程在争抢资源,就要实现线程的同步(就要进行加锁,并且这个锁必须是共享的,必须是唯一的。
咱们的锁一般都是引用数据类型的。
目的:解决了线程安全问题。

2:关于同步方法

  1. 不要将run()定义为同步方法
  2. 非静态同步方法的同步监视器是this
    静态同步方法的同步监视器是 类名.class 字节码信息对象
  3. 同步代码块的效率要高于同步方法
    原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部
  4. 同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法;同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块
    【第三种方式:Lock锁】
@Override  //Lock锁  
public void run() {  
    Lock lock = new ReentrantLock();  //创建Lock锁对象  
    while (true) {  
        lock.lock();//上锁  
        if (ticket >= 1) {  
            System.out.println(getName() + "窗口,卖出第" + ticket + "票");  
            ticket--;  
        }  
        lock.unlock();  //解锁  
        if (ticket == 0) {  
            break;  
        }  
    }  
}

5.5线程通信:

在这里插入图片描述

package com.msb.test11;
/**
 * @author : msb-zhaoss
 */
public class Product {//商品类
    //品牌
    private String brand;
    //名字
    private String name;
    //引入一个灯:true:红色  false 绿色
    boolean flag = false;//默认情况下没有商品 让生产者先生产  然后消费者再消费
    //setter,getter方法;
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    //生产商品
    public synchronized void setProduct(String brand,String name){
        if(flag == true){//灯是红色,证明有商品,生产者不生产,等着消费者消费
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //灯是绿色的,就生产:
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        //将生产信息做一个打印:
        System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
        //生产完以后,灯变色:变成红色:
        flag = true;
        //告诉消费者赶紧来消费:
        notify();
    }
    //消费商品:
    public synchronized void getProduct(){
        if(!flag){//flag == false没有商品,等待生产者生产:
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有商品,消费:
        System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
        //消费完:灯变色:
        flag = false;
        //通知生产者生产:
        notify();
    }
}

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

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

相关文章

Python-链表数据结构学习(1)

一、什么是链表数据&#xff1f; 链表是一种通过指针串联在一起的数据结构&#xff0c;每个节点由2部分组成&#xff0c;一个是数据域&#xff0c;一个是指针域&#xff08;存放下一个节点的指针&#xff09;。最后一个节点的指针域指向null&#xff08;空指针的意思&#xff0…

《心灵奇旅》观后感

1 这是一部能够给心灵带来慰藉的电影&#xff0c;或许在人生迷茫的时候&#xff0c;可以看一下&#xff0c;洗涤内心&#xff0c;换还自己一片净土。 影片的男主乔伊是一位音乐老师&#xff0c;他一直梦想着能够加入乐队演出。然而&#xff0c;在即将有机会出演时&#xff0c;他…

使用easyexcel导出复杂模板,同时使用bean,map,list填充

背景 在使用easyexcel导出时&#xff0c;如果遇到一个模板中同时存在 一部分是实体类中的字段&#xff0c;另外部分是列表的字段&#xff0c;需要特殊处理一下&#xff0c;比如下面的模板&#xff1a; 这里面 user&#xff0c; addr 是实体类&#xff08;或者map&#xff09…

3.22【计组】 流水线加法器

实验一 timescale 1ns / 1ps/* ALU模块实现两个32bit数的add、sub、and、or、not、slt功能&#xff0c; 但由于Nexy7输入口限制&#xff0c;将num1简化为8位&#xff0c;在过程中再extend成32位&#xff0c;num2作为内部wire自行赋值&#xff0c;此处赋为5 由于最后的结果在to…

漫谈推理谬误——错误因果

相关文章 漫谈推理谬误——错误假设-CSDN博客文章浏览阅读736次&#xff0c;点赞22次&#xff0c;收藏3次。在日常生活中&#xff0c;我们会面临各种逻辑推理&#xff0c;有些看起来一目了然&#xff0c;有些非常的科学严谨&#xff0c;但也有很多似是而非&#xff0c;隐藏了陷…

实现 vue3 正整数输入框组件

1.实现代码 components/InputInteger.vue <!-- 正整数输入框 --> <template><el-input v-model"_value" input"onInput" maxlength"9" clearable /> </template><script lang"ts" setup> import { ref …

Hot100 - 搜索二维矩阵II

Hot100 - 搜索二维矩阵II 最佳思路&#xff1a; 利用矩阵的特性&#xff0c;针对搜索操作可以从右上角或者左下角开始。通过判断当前位置的元素与目标值的关系&#xff0c;逐步缩小搜索范围&#xff0c;从而达到较高的效率。 从右上角开始&#xff1a;假设矩阵是升序排列的&a…

Hello SpringBoot!

Spring Initializr&#xff1a;一个快速构建springboot项目的网站 进入网站后&#xff0c;选择&#xff1a; Project: MavenLanguage: JavaSpring Boot: 最新稳定版Dependencies: Spring Web 生成的文件结构类似于&#xff1a; my-spring-boot-app ├── src │ ├── m…

模型压缩——量化方法解读

1.引言 前面我们已经介绍了剪枝、蒸馏等通过减少模型参数量来进行压缩的方法。除这些方法以外&#xff0c;量化 (quantization) 是另一种能够压缩模型参数的方法。与前面方法不同的是&#xff0c;量化并不减少模型参数量&#xff0c;而是通过修改网络中每个参数占用的比特数&a…

Core 授权 认证 案例

利用 cookie 模式 》》 框架默认的 利用 cookie 模式 》》 策略授权

计算机网络常见面试题总结(上)

计算机网络基础 网络分层模型 OSI 七层模型是什么&#xff1f;每一层的作用是什么&#xff1f; OSI 七层模型 是国际标准化组织提出的一个网络分层模型&#xff0c;其大体结构以及每一层提供的功能如下图所示&#xff1a; 每一层都专注做一件事情&#xff0c;并且每一层都需…

Macos远程连接Linux桌面教程;Ubuntu配置远程桌面;Mac端远程登陆Linux桌面;可能出现的问题

文章目录 1. Ubuntu配置远程桌面2. Mac端远程登陆Linux桌面3. 可能出现的问题1.您用来登录计算机的密码与登录密钥环里的密码不再匹配2. 找不到org->gnome->desktop->remote-access 1. Ubuntu配置远程桌面 打开设置->共享->屏幕共享。勾选允许连接控制屏幕&…

【C语言】结构体、联合体、枚举类型的字节大小详解

在C语言中&#xff0c;结构体&#xff08;struct&#xff09;和联合体&#xff08;union&#xff09; 是常用的复合数据类型&#xff0c;它们的内存布局和字节大小直接影响程序的性能和内存使用。下面为大家详细解释它们的字节大小计算方法&#xff0c;包括对齐规则、内存分配方…

免交互运用

免交互的概念 文本免交互 免交互的格式 变量配置 expect expect的格式 在脚本外传参 嵌套 练习 免交互ssh远程连接

物联网客户端在线服务中心(客服功能/私聊/群聊/下发指令等功能)

一、界面 私聊功能&#xff08;下发通知类&#xff0c;一对多&#xff09;群聊&#xff08;点对点&#xff09;发送指令&#xff08;配合使用客户端&#xff0c;基于cefsharp做的物联网浏览器客户端&#xff09;修改远程参数配置&#xff08;直接保存到本地&#xff09;&#…

使用C#开发VTK笔记(一)-开发环境搭建

一.使用C#开发VTK的背景 因为C#开发的友好性,一直都比较习惯于从C#开发程序。而长期以来,都希望有一个稳定可靠的三位工程数模的开发演示平台,经过多次对比之后,感觉VTK和OpenCasCade这两个开源项目是比较好的,但它们都是用C++编写的,我用C#形式开发,只能找到发布的C#组…

力扣96:不同的二叉搜索树

给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;1 卡…

k8s Init:ImagePullBackOff 的解决方法

kubectl describe po (pod名字) -n kube-system 可查看pod所在的节点信息 例如&#xff1a; kubectl describe po calico-node-2lcxx -n kube-system 执行拉取前先把用到的节点的源换了 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"re…

人工智能如何改变你的生活?

在我们所处的这个快节奏的世界里&#xff0c;科技融入日常生活已然成为司空见惯的事&#xff0c;并且切实成为了我们生活的一部分。在这场科技变革中&#xff0c;最具变革性的角色之一便是人工智能&#xff08;AI&#xff09;。从我们清晨醒来直至夜晚入睡&#xff0c;人工智能…

道路机器人识别交通灯,马路,左右转,黄线,人行道,机器人等路面导航标志识别-使用YOLO标记

数据集分割 train组66% 268图片 validation集22% 91图片 test集12&#xff05; 48图片 预处理 没有采用任何预处理步骤。 增强 未应用任何增强。 数据集图片&#xff1a; 交通灯 马路 右转 向右掉头 机器人识别 人行横道 黄线 直行或右转 数据集下载&#xff1a; 道路…