JAVA基础:多线程 (学习笔记)

news2024/11/24 20:37:59

多线程

一,什么是线程?

  • 程序:为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码
  • 进程:程序的一次执行过程。

          正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 (进程是动态的)是一个动的过程 ,进程的生命周期  :  有它自身的产生、存在和消亡的过程 

  • 线程:是进程的进一步细化, 是一个程序内部的一条执行路径。

          若一个进程同一时间并行执行多个线程,就是支持多线程的。

     1.并行和并发

  • 并行:多个CPU同时执行多个任务

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

二,创建线程的三种方式

     1,继承Thread类

  1. 这个run方法不能直接调用,直接调用就会被当做一个普通方法,要用 . start() ,启动线程。
  2. 开辟道路的代码必须在前面!!
  • 入门例子
//运行的类
public static void main(String[] args) {

   /*   DomeThread dt = new DomeThread();
        dt.start();*/

        new DomeThread().start();//开了一条新的路

        for (int i = 0; i < 100; i++) {
            System.out.println("main()" + i);
        }

    }//穿插运行
}
================================================================================
//准备
public class DomeThread extends Thread {//继承线程类  重写run()

    @Override
    public void run() {//另外一条道的执行代码
        show();
    }

    public void show() {
        for (int i = 0; i < 100; i++) {
            System.out.println("DomeThread_show()" + i);
        }
    }
}

  •  三个窗口售卖火车票----三种方式的例题,也是线程安全问题的例题
//运行
 /**
     * 用继承方式的并行售票
     *
     * @param args
     */

    public static void main2(String[] args) {//main方法---主线程
        //假设有三个窗口同时买票
        //没有构造方法命名会报错
        TicketThread tt1 = new TicketThread("窗口1");//命名方法1
        TicketThread tt2 = new TicketThread();
        tt2.setName("窗口2");//命名方法2
        TicketThread tt3 = new TicketThread("窗口3");
        tt1.start();
        tt2.start();
        tt3.start();

    }
===========================================================================================
//准备

public class TicketThread extends Thread{


    static int count=10;//静态--有一个对象对他进行改变,其他对象再使用时就是修改过的

    @Override
    public void run() {
        while (count>0) {
            count--;
            System.out.println(this.getName()+"卖出一张票,还剩"+count+"张");
        }
    }

    public TicketThread() {
    }

    public TicketThread(String name) {
        super(name);
    }
}
  •  缺点:
  1. 没有返回值
  2. 不能抛出异常

 接 三,1------同步代码块  (重写 run() )

 @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            synchronized (TicketThread.class) {//同步代码块    同步关键字(锁子)
                if (count > 0) {
                    count--;
                    System.out.println(this.getName() + "卖出一张票,还剩" + count + "张");
                }
            }
        }
    }
//TicketThread.class  或者 this.getClass

2,实现Runnable接口

Thread类实现了Runnable接口

//运行 
 public static void main3(String[] args) {
        TicketRunnable tr = new TicketRunnable();
        Thread thread1 = new Thread(tr, "窗口1");
        Thread thread2 = new Thread(tr, "窗口2");
        Thread thread3 = new Thread(tr, "窗口2");
        thread1.start();
        thread2.start();
        thread3.start();
    }

============================================================================
//准备

public class TicketRunnable implements Runnable {
    int count = 10;


    @Override
    public void run() {
        while (count > 0) {
            count--;
            System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + count + "张");
        }//Thread.currentThread().getName()---获得正在运行的线程的名字
    }

}
  •  缺点
  1. 没有返回值
  2. 不能抛出异常

接 三,2----- 同步方法 (重写run() , 新增一个synchronized同步方法)

package thread;

public class TicketRunnable implements Runnable {
    int count = 10;


    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            buyTicket();
        }
    }
    public synchronized  void buyTicket(){//默认锁 this对象
        if (count > 0) {
            count--;
            System.out.println(Thread.currentThread().getName()+ "卖出一张票,还剩" + count + "张");
        }
    }
}

 补充---休眠

 @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            buyTicket();

           //休眠,如果运行时一直是一个窗口,可以用这个
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
    }

接 三,3----Lock锁 (重写run() ,  定义锁)

package thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TicketRunnable implements Runnable {
    int count = 10;

    Lock lock=new ReentrantLock(); //多态  接口=实现类  可以使用不同的实现类
    @Override
    public void run() {

        for (int i = 0; i <= 100; i++) {
            lock.lock();
            try {
                if (count > 0) {
                    count--;
                    System.out.println(Thread.currentThread().getName()+ "卖出一张票,还剩" + count + "张");
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                lock.unlock();
            }


        }
    }
  
}

 3,实现Callable接口

  • 好处:
  1. 有返回值  
  2. 能抛出异常
  • 缺点:
  1. 线程创建比较麻烦
//运行 
/**
     * 实现Callable接口的并行售票
     */
 public static void main(String[] args) {
        TicketCall tc=new TicketCall();

        FutureTask ft=new FutureTask(tc);
        Thread t1=new Thread(ft,"窗口1");//存在默认名,如果你不命名,程序会显示Thread-0
        FutureTask ft1=new FutureTask(tc);
        Thread t2=new Thread(ft1,"窗口2");
        FutureTask ft2=new FutureTask(tc);
        Thread t3=new Thread(ft2,"窗口3");
        t1.start();
        t2.start();
        t3.start();

//获取线程得到的返回值:
        Object obj = ft.get();
        System.out.println(obj);

    }
======================================================================
//准备

package thread;

import java.util.concurrent.Callable;

public class TicketCall implements Callable {

    int count = 10;

    @Override
    public Object call() throws Exception {
        while (count > 0) {
            count--;
            System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + count + "张");
        }//Thread.currentThread().getName()---获得正在运行的线程的名字
        return null;
    }
}
  • 补充
  1. 实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型 

    public class TicketCall implements Callable<Integer> {
  2. 如果带泛型,那么call的返回值就是泛型对应的类型
  3. 从call方法看到:方法有返回值,可以跑出异常 

 4,线程的生命周期

四,线程的常用方法

  • start() :  启动当前线程,表面上调用start方法,实际在调用线程里面的run方法  【例题中有】
  • run() : 线程类 继承 Thread类 或者 实现Runnable接口的时候,都要重新实现这个run方法,run方法里面是线程要执行的内容 【例题中有】
  • currentThread :Thread类中一个静态方法:获取当前正在执行的线程 【Runnable例中】
  • setName: 设置线程名字 【Thread例中】
  • getName 读取线程名字 【例题中有】
  • 通过调用interrupt()方法来中断其阻塞状态
  • 设置优先级
  1. 同优先级别的线程,采取的策略就是先到先服务,使用时间片策略
  2. 如果优先级别高,被CPU调度的概率就高
  3. 级别:1-10   默认的级别为5
  4. 线程的优先级是在创建线程时设置的,在创建线程后的任何时候都可以重新设置
//运行
class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //创建两个子线程,让这两个子线程争抢资源:
        TestThread01 t1 = new TestThread01();
        t1.setPriority(10);//优先级别高
        t1.start();
        TestThread02 t2 = new TestThread02();
        t2.setPriority(1);//优先级别低
        t2.start();
    }
}
=====================================================================
//准备
public class TestThread01 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(i);
        }
    }
}
-----------------------------------------------------------
class TestThread02 extends Thread{
    @Override
    public void run() {
        for (int i = 20; i <= 30 ; i++) {
            System.out.println(i);
        }
    }
}
  •  join() : 当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。 注意:必须先start,再join才有效。
//测试
class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 100 ; i++) {
            System.out.println("main-----"+i);
            if(i == 6){
                //创建子线程:
                TestThread tt = new TestThread("子线程");
                tt.start();
                tt.join();//“半路杀出个程咬金”
            }
        }
    }
}
====================================================
//准备
public class TestThread extends Thread {
    public TestThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(this.getName()+"----"+i);
        }
    }
}
  • sleep : 人为的制造阻塞事件 
public class Test01 {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("00000000000000");
    }
}

 案例:完成秒表功能

public class Test02 {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //2.定义一个时间格式:
        DateFormat df = new SimpleDateFormat("HH:mm:ss");
        while(true){
            //1.获取当前时间:
            Date d = new Date();
            //3.按照上面定义的格式将Date类型转为指定格式的字符串:
            System.out.println(df.format(d));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  • setDaemon 设置伴随线程
  1. 将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也不要继续执行了
  2. 案例:皇上 --》驾崩 ---》妃子陪葬   【将指定的线程设置成后台线程】
//运行
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);
        }
    }
}
=========================================================
//准备
public class TestThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 1000 ; i++) {
            System.out.println("子线程----"+i);
        }
    }
}
  • yield方法----的作用是让当前线程放弃CPU时间片,进入和可运行状态,与其他等待的可运行状态线程竞争CPU时间片。它并不会直接提升线程的优先级,而是让当前线程主动放弃执行权,从而让其他线程有机会运行‌

五,线程安全问题

     1,同步代码块  //代码见  上面  --接 三,1    

 synchronized (TicketThread.class)
  • 总结一 

              ----认识同步监视器(锁子)   -----  synchronized(同步监视器){ }

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

  • 总结二 

              ----同步代码块的执行过程

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

     2,同步方法    //代码见  上面  --接 三,2

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

              --- 关于同步方法

  1.  不要将run()定义为同步方法
  2. 非静态同步方法的同步监视器是this
  3. 静态同步方法的同步监视器是 类名.class 字节码信息对象
  4. 同步代码块的效率要高于同步方法     ------------ 原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部
  5. 同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法;同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块 

     3,Lock锁     //代码见  上面  --接 三,3

 Lock lock=new ReentrantLock(); //多态  接口=实现类  可以使用不同的实现类
    。。。。。。。。
 lock.lock();//打开锁
    。。。。。。。
 lock.unlock();//关闭锁:--->即使有异常,这个锁也可以得到释放
  •  Lock和synchronized的区别
  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. .使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
  1.   Lock----同步代码块(已经进入了方法体,分配了相应资源)----同步方法(在方法体之外)

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

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

相关文章

国内大语言模型哪家更好用?

大家好&#xff0c;我是袁庭新。 过去一年&#xff0c;AI大语言模型在爆发式增长&#xff0c;呈现百家争鸣之态。国内外相关厂商积极布局&#xff0c;并相继推出自家研发的智能化产品。 我在工作中已习惯借助AI来辅助完成些编码、创作、文生图等任务&#xff0c;甚至对它们产…

基于SSM轻型卡车零部件销售系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;配件类型管理&#xff0c;配件信息管理&#xff0c;订单信息管理&#xff0c;检修休息管理&#xff0c;系统管理 用户账号功能包括&#xff1a;系统首页&#xff0c;个人中心&…

鸿蒙是必经之路

少了大嘴的发布会&#xff0c;老实讲有点让人昏昏入睡。关于技术本身的东西&#xff0c;放在后面。 我想想来加把油~ 鸿蒙发布后褒贬不一&#xff0c;其中很多人不太看好鸿蒙&#xff0c;一方面是开源性、一方面是南向北向的利益问题。 不说技术的领先点&#xff0c;我只扯扯…

【汇编语言】第一个程序(二)—— 带你真正了解一个源程序的结构是怎样的

文章目录 前言1. 示例程序2. 伪指令2.1 XXX segment2.2 end2.3 assume 3. 源程序中的“程序”4. 标号5. 程序的结构6. 程序返回7. 语法错误和逻辑错误结语 前言 &#x1f4cc; 汇编语言是很多相关课程&#xff08;如数据结构、操作系统、微机原理&#xff09;的重要基础。但仅仅…

ubuntu22 安装labelimg制作自己的深度学习目标检测数据集

参考文章&#xff1a;目标检测---利用labelimg制作自己的深度学习目标检测数据集-CSDN博客 以上文章是windows下使用的方法&#xff0c;本章是在ubuntu22下使用的方法 一、准备工作 确保您的Ubuntu系统已安装Python 3.7或更高版本。可以通过在终端输入 python3 --version 来检…

2024 BuildCTF 公开赛|MISC

1.what is this? BuildCTF{S0_TH1S_15_M0R5E_C0DE_!!} 2.一念愚即般若绝&#xff0c;一念智即般若生 解压缩密码&#xff1a;s2j6dg* BuildCTF{D3crypt10n_1s_4_l0ng_r04d} 3.如果再来一次&#xff0c;还会选择我吗&#xff1f; 修复png 密码&#xff1a;8!67adz6&#xff…

二进制方式部署k8s集群

目标任务: 1、Kubernetes集群部署架构规划 2、部署Etcd数据库集群 3、在Node节点安装Docker 4、部署Flannel网络插件 5、在Master节点部署组件(api-server,schduler,controller-manager) 6、在Node节点部署组件(kubelet,kube-proxy) 7、查看集群状态 8、运行⼀个测…

springboot087植物健康系统(论文+源码)_kaic

植物健康系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了植物健康系统的开发全过程。通过分析植物健康系统管理的不足&#xff0c;创建了一个计算机管理植物健康系统的方案。文章介绍了植物健康系统的系统…

web3对象如何连接以太网络节点

实例化web3对象 当我们实例化web3对象&#xff0c;我们一般开始用本地址&#xff0c;如下 import Web3 from web3 var web3 new Web3(Web3.givenProvider || ws://localhost:5173)我们要和以太网进行交互&#xff0c;所以我们要将’ws://localhost:5173’的本地地址换成以太…

哪些CRM系统适合医疗行业?主流10款产品全解析

本文介绍了10款crm系统&#xff1a;纷享销客、Zoho CRM、海创CRM、红云CRM、慧影CRM、易华录CRM、用友健康CRM、Highrise CRM、Maximizer CRM、Infusionsoft by Keap。 在医疗行业中&#xff0c;选择合适的客户关系管理&#xff08;CRM&#xff09;系统可能是一项令人头疼的挑战…

fastGpt

参考本地部署FastGPT使用在线大语言模型 1 rockylinx 1 ollama安装 在rockylinux中安装的&#xff0c;ollama由1.5G&#xff0c;还是比较大&#xff0c;所有采用在windows下下载&#xff0c;然后安装的方式&#xff0c;linux安装 tar -C /usr -xzf ollama-linux-amd64.tgz #…

Vue3学习:汇率计算器案例中event.target与event.currentTarget比较

今天从一本vue.js书中学习了《汇率计算器》的案例&#xff0c;这个案例的效果如下&#xff1a; 案例可以查询人民币、日元、港元、美元、欧元之间的汇率关系&#xff0c;代码中定义了一个汇率表rate&#xff0c;包含了每种货币对其他5种货币的汇率。其中还有一个功能是点击下方…

WPF的触发器(Trigger)

WPF&#xff08;Windows Presentation Foundation&#xff09;是微软.NET框架的一部分&#xff0c;用于构建Windows客户端应用程序。在WPF中&#xff0c;触发器&#xff08;Triggers&#xff09;是一种强大的功能&#xff0c;允许开发者根据控件的状态或属性值来动态改变控件的…

Zabbix 监控自动化

一、网络自动发现 部署环境 zabbix server ZBX 192.168.27.152 CentOS7.9 zabbix server 6.4.8 zabbix agent agent01 192.168.27.154 CentOS7.9 zabbix agent 6.4.8 zabbix agent agent02 192.168.27.158 CentOS7.9 zabbix agent 6.4.8 1.搭建LNMP环境 2.安装配…

Http 状态码 301 Permanent Rediret 302 Temporary Redirect

HTTP状态码301和302是什么&#xff1f; 1、HTTP状态码301 HTTP状态码301表示永久性转移&#xff08;Permanent Redirect&#xff09;&#xff0c;这意味着请求的资源已经被分配了一个新的URI&#xff0c;以后的引用应该使用资源现在所指的URI。 HTTP 301状态码表示请求的资源…

力扣刷题(sql)--零散知识点(1)

通过一段时间的刷题&#xff0c;感觉自己的sql能力逐渐上去&#xff0c;所以不会像前三道题一样讲那么详细了&#xff0c;这里主要会讲到一些特殊的知识点和方法。另外&#xff0c;我的建议是做完一个题有好的想法赶紧记录下来&#xff0c;不要想着最后汇总&#xff0c;不然会懒…

STATCOM静止同步补偿器原理及MATLAB仿真模型

STATCOM原理简述 整个STATCOM 装置相当于一个电压大小可以控制的电压源。当控制 STATCOM 装置产生的电压小于系统电压即UI<US 时&#xff0c;STATCOM 装置向系统输出的无功功率Q<0&#xff0c;此时 STATCOM 装置相当于电感&#xff1b;当控制 STATCOM 装置产生的电压大于…

编写一个简单的Iinput_dev框架

往期内容 本专栏往期内容&#xff1a; input子系统的框架和重要数据结构详解-CSDN博客input device和input handler的注册以及匹配过程解析-CSDN博客input device和input handler的注册以及匹配过程解析-CSDN博客 I2C子系统专栏&#xff1a; 专栏地址&#xff1a;IIC子系统_憧憬…

理工科考研想考计算机,湖南大学、重大、哈工大威海、山东大学,该如何选择?

C哥专业提供——计软考研院校选择分析专业课备考指南规划 计算机对理工科同学来说&#xff0c;还是性价比很高的&#xff0c;具有很大的优势&#xff01; 一、就业前景广阔 高需求行业 在当今数字化时代&#xff0c;计算机技术几乎渗透到了各个领域&#xff0c;无论是互联网…

在MacOS玩RPG游戏 - RPGViewerPlus

背景知识 由于我一直使用Mac电脑&#xff0c;所以一直对Mac如何玩RPGMV/RPGMZ游戏的方式有进一步的想法。 网上能给出的方案都是自行启动一个HTTP服务进行&#xff0c;进行服务加载。这个方法有效&#xff0c;但兼容性较差。涉及到自定义功能模块的游戏&#xff0c;都会有报错…