[001-02-001]. 第07-02节:线程的创建与使用

news2024/11/14 23:37:28

我的后端学习大纲

我的Java学习大纲


1、方式1:继承Thread类:

1.1.实现步骤

  • 1.创建一个继承于Thred()类的子类
  • 2.重写Thread类的run()
  • 3.创Thread类的子类的对象
  • 4.通过这个对象去调用start()方法

在调用start方法时就做了两件事,分别是:

  • 启动当前线程
  • 调用当前线程的run()

1.2.代码实现:


//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
    //2.重写Thread类的run(),把这个线程需要干的事情写在run方法中就可以,现在以循环100以内的数为例来写
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
package com.atguigu.java;

public class ThreadTest001 {
    public static void main(String[] args) {
        //3. 主线程创建Thread类的子类的对象
        MyThread t1 = new MyThread();

        //4.通过此对象调用start(),会执行如下两部操作:
        //   启动当前线程 
        //   调用当前线程的run()
        t1.start();


        //问题一:我们不能通过直接调用run()的方式启动线程。
        //t1.run();这种方式相当于是对象调方法,和多线程无关系

        //问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
        //t1.start();
        //我们需要重新创建一个线程的对象来实现
        MyThread t2 = new MyThread();
        t2.start();

        //如下操作仍然是在main线程中执行的。
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
            }
        }
    }
}

1.3.分析代码中线程执行的情况:

在这里插入图片描述

1.4.案例:找出1到100以内的所有奇数和偶数

  • 1.原始方式实现:
package com.jianqun.day09;

public class ThreadDemo {
    public static void main(String[] args) {

        //原始方式验证多线程:
        MyThread1 myThread1 = new MyThread1();
        MyThread2 myThread2 = new MyThread2();
        myThread1.start();
        myThread2.start();
    }
}



class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0){
                System.out.println(Thread.currentThread().getName() + "::" + i);
            }
        }
    }
}

class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 != 0){
                System.out.println(Thread.currentThread().getName() + "::" + i);
            }
        }
    }
}
  • 2.利用Thread类的匿名子类的方式来实现:
package com.jianqun.day09;

public class ThreadDemo {
    public static void main(String[] args) {

        //利用Thread类的匿名子类的方式来测试多线程
        //遍历奇数
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i%2 != 0){
                        System.out.println(Thread.currentThread().getName() + "::" + i);
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i%2 == 0){
                        System.out.println(Thread.currentThread().getName() + "::" + i);
                    }
                }
            }
        }.start();
    }
}

1.5.Thread中的常用方法:

a.常用方法介绍:

  • 1.start():启动当前线程;调用当前线程的run()
  • 2.run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
  • 3.currentThread():是个静态方法,返回执行当前代码的线程
  • 4.getName():获取当前线程的名字
  • 5.setName():设置当前线程的名字
  • 6.yield():释放当前cpu的执行权
  • 7.join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
  • 8.stop():已过时。当执行此方法时,强制结束当前线程
  • 9.sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
  • 10.isAlive():判断当前线程是否存活

b.编码测试方法:

class HelloThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
				// 子类重写的方法不能比父类抛的异常还大,父类如果没有抛异常,那么子类必定不能抛异常了
				//try {
				//   sleep(1000);
				// } catch (InterruptedException e) {
				//    e.printStackTrace();
				//}
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
            }

			// 如果i除以20取模为0的话,释放当前cpu的执行权
			//if(i % 20 == 0){
			//   yield();
			//}
        }
    }


    // 有参构造器方式给线程命名
    // 定义了有参构造器后,无参构造器就没有了,所以类的实例化的时候,要传一个参数,这个参数的含义就是线程的名称
    public HelloThread(String name){
        super(name);
    }
}
package com.atguigu.java;

/**
 * @author: jianqun
 * @email: 1033586391@qq.com
 * @creat: 2022-04-08-22:35
 * @Description:
 */

public class ThreadTest002 {
    public static void main(String[] args) {
      // 未声明HelloThread 的有参构造的时候,如下方式更改线程的名称
      // HelloThread h1 = new HelloThread();
      // h1.setName("线程一");

		//利用有参构造器给线程名称命名
        HelloThread h1 = new HelloThread("Thread:1");

        //设置分线程的优先级
        h1.setPriority(Thread.MAX_PRIORITY);

        h1.start();

        //给主线程命名
        Thread.currentThread().setName("主线程");
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
            }

		//  if(i == 20){
		//    try {
		// 			在主线程中调用了子线程的join()方法,
		// 			此时主线程就进入阻塞状态,直到子线程完全执行完以后,主线程才结束阻塞状态
		//       	h1.join();
		//    } catch (InterruptedException e) {
		//   	 e.printStackTrace();
		//  }
		//}
      }

		// System.out.println(h1.isAlive());

    }
}

1.6.线程的调度

a.调度策略:

  • 策略1:时间片:
    在这里插入图片描述
  • 策略2:抢占式,高优先级的线程抢占CPU

b.调度方法:

  • 同优先级线程组成的先进先出队列(先到先服务),使用时间片策略
  • 高优先级,使用优先调度的抢占式策略

c.线程优先级:

  • MAX_PRIORITY:10
  • MIN _PRIORITY:1
  • NORM_PRIORITY:5

d.涉及的方法

  • getPriority() :返回线程优先级的值
  • setPriority(int newPriority) :改变线程的优先级

e.说明

  • 线程创建时继承父线程的优先级
  • 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

2、方式2:实现Runnable接口

2.1.线程创建的步骤:

  • 1.定义子类,实现Runnable接口
  • 2 子类中重写Runnable接口中的run方法
  • 3.创建实现类的对象secondTread :SecondTread secondTread = new SecondTread();
  • 4.将创建的实现类的这个对象secondTread作为参数传递到Thread类的含参构造器中,创建线程对象
  • 5.调用Thread类的start方法:开启线程,去调用Runnable子类接口的run方法
class SecondTread implements Runnable{
    // b `子类中重写Runnable接口中的run方法`。
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + "::" + i);
            }
        }
    }
}
package com.atguigu.java;

/**
 * @author: jianqun
 * @email: 1033586391@qq.com
 * @creat: 2022-04-09-9:29
 * @Description: 第二种创建方式
 */
public class ThreadTest004 {
    public static void main(String[] args) {
        //c.创建实现类的对象
        SecondTread secondTread = new SecondTread();
        //d.将创建的实现类的这个对象secondTread作为参数传递到Thread类的含参构造器中,创建线程对象。
        Thread t1 = new Thread(secondTread);
        //e.调用Thread类的start方法
        t1.setName("线程1");
        t1.start();

        //再启动一个线程的方式:
        Thread t2 = new Thread(secondTread);
        t2.setName("线程2");
        t2.start();
    }
}


2.2.案例分析:卖票问题

a.编码实现卖票:

在这里插入图片描述
在这里插入图片描述

  • 上述代码中,卖票的三个窗口,每个窗口都卖100张票,共计300张票,但是其实就100张,卖超了; private int ticket = 100;这个会导致每个对象都有100张票,属性资源不共享

b.迭代1:改善代码:

  • 1.解决办法是添加static,定义:private static int ticket = 100;,对象们共享一个静态变量,一共就100张票
package com.atguigu.java;
class Bwindow extends Thread{
    //private int ticket = 100;//每个对象都有100张票,属性不共享
    private static int ticket = 100;//对象们共享一个静态变量,一共就100张票,但是存在线程安全问题,会导致卖票重复

    @Override
    public void run() {
        while(true){
            if (ticket >0){
                System.out.println(getName() + ":卖票,票号是::" + ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}

public class ThreadTest003 {
    public static void main(String[] args) {
        Bwindow bwindow1 = new Bwindow();
        Bwindow bwindow2 = new Bwindow();
        Bwindow bwindow3 = new Bwindow();

        bwindow1.setName("窗口1");
        bwindow2.setName("窗口2");
        bwindow3.setName("窗口3");

        bwindow1.start();
        bwindow2.start();
        bwindow3.start();

    }
}

上述改善方式存在线程安全问题,假设还有最后一张票,在if判断这里,如果线程1判断为true,但是还没有减票,此时线程B来了,然后减少1张票,此时剩余的实际票的数量为0,然后线程1再去减少票的数量的时候,就出现问题了!!,所以说存在线程安全问题,会导致卖票重复

c.迭代2:改善代码:

class Bwindow implements Runnable{
    private int ticket = 100;
   
    @Override
    public void run() {
        while(true){
            if (ticket >0){
                System.out.println(getName() + ":卖票,票号是::" + ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}
package com.atguigu.java;

/**
 * @author: jianqun
 * @email: 1033586391@qq.com
 * @creat: 2022-04-09-9:09
 * @Description:
 */

public class ThreadTest003 {
    public static void main(String[] args) {
        Bwindow bwindow1 = new Bwindow();
    
		// 创建线程:3个线程共用一个bwindow1对象,这样就共用了ticket 属性,就不用再加static了,但是还是会存在线程安全问题
		Thread t1 = new Thread(bwindow1);
		Thread t2 = new Thread(bwindow1);
		Thread t3 = new Thread(bwindow1);

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

上述改善方式仍然存在线程安全问题,假设还有最后一张票,在if判断这里,如果线程1判断为true,但是还没有减票,此时线程B来了,然后减少1张票,此时剩余的实际票的数量为0,然后线程1再去减少票的数量的时候,就出现问题了!!,所以说存在线程安全问题,会导致卖票重复

2.3.两种线程创建方式的对比:

在这里插入图片描述

说明:上面说到的线程安全问题在此笔记中进行了分析和解决


3、方式3:实现Callable接口

3.1.与使用Runnable相比, Callable功能更强大些

  • 1.相比run()方法,可以有返回值
  • 2.方法可以抛出异常
  • 3.支持泛型的返回值
  • 4.需要借助FutureTask类,比如获取返回结果

3.2.Future接口

  • 1.可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
  • 2.FutrueTask是Futrue接口的唯一的实现类
  • 3.FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

3.3.实现过程:

  • 1.创建一个实现callable接口的实现类
  • 2.实现call方法,将此线程需要执行的操作声明在call()方法中
  • 3.创建Callable接口实现类的对象
  • 4.将实现Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
  • 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法

3.4.代码举例:

在这里插入图片描述

  • 如下代码中的get()方法的返回值是:FutureTask构造器参数Callable实现类重写的call方法的返回值
    在这里插入图片描述

3.5.Callable和Runnable对比:

在这里插入图片描述

3.6.泛型举例:

在这里插入图片描述
在这里插入图片描述


4、方式4:使用线程池

4.1.线程池背景:

  • 1.经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
  • 2.提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

4.2.好处:

  • 1.提高响应速度(减少了创建新线程的时间)
  • 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 3.便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

4.3.线程池相关API:

  • 1.JDK 5.0起提供了线程池相关API:ExecutorServiceExecutors
  • 2.ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
    • void shutdown() :关闭连接池
  • 3.Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行

4.4.代码实现:

在这里插入图片描述
在这里插入图片描述


5、多线程创建中的继承方式和实现方式的联系与区别

5.1.区别

  • 继承Thread:线程代码存放Thread子类run方法中。
  • 实现Runnable:线程代码存在接口的子类的run方法

5.2.实现Runnable接口方式的好处:

  • Runnable接口 避免了单继承的局限性:
    在这里插入图片描述
  • Runnable方式可以实现多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源

5.3.联系:

  • Thread类也是实现了Runnable接口 -
  • 两种方式都需要重写run(),将线程要执行的逻辑在run()方法中声明

6、线程的启动

  • 1.Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现
    在这里插入图片描述

  • 2.Thread类的特性

    • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
    • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

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

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

相关文章

贪心问题———区间覆盖

输入样例&#xff1a; 1 5 3 -1 3 2 4 3 5 输出样例&#xff1a; 2 分析&#xff1a; 我们根据贪心的思路&#xff0c;能覆盖更大的范围就意味着能用更少的区间段 我们将线段从左端点进行排序 代码演示&#xff1a; #include <iostream> #include <vector>…

从0开始的算法(数据结构和算法)基础(十一)

回溯算法 什么是回溯算法 回溯算法&#xff0c;根据字面意思来理解这个算法是将每一步的操作可以进行回溯&#xff0c;实际上是对这个每一步的操作进行记录&#xff0c;确保可以返回上一步的操作&#xff0c;可能是对回溯操作之前的做一个复现&#xff0c;也有可能是可操作的回…

STM32与ESP8266的使用

串口透传 “透传”通常指的是数据的透明传输&#xff0c;意思是在不对数据进行任何处理或修改的情况下&#xff0c;将数据从一个接口转发到另一个接口。值得注意的是要避免串口之间无限制的透明&#xff0c;可以采用互斥锁的方式进行限制使用方法 对USART1和USART3(用他俩举例…

高效物流管理从固乔快递批量查询助手开始

固乔快递批量查询助手&#xff1a;物流管理的智能化升级 固乔快递查询助手&#xff1a;批量追踪&#xff0c;物流无忧 轻松应对海量单号&#xff0c;固乔快递批量查询助手来帮忙 跨境电商新利器&#xff1a;固乔快递批量查询助手 高效物流管理从固乔快递批量查询助手开始 …

Spring-AOP核心源码、原理详解前篇

本文主要分4部分 Aop原理介绍介绍aop相关的一些类通过源码详解aop代理的创建过程通过源码详解aop代理的调用过程Aop代理一些特性的使用案例 Spring AOP原理 原理比较简单&#xff0c;主要就是使用jdk动态代理和cglib代理来创建代理对象&#xff0c;通过代理对象来访问目标对象…

漏洞复现-泛微-E-Cology-SQL

更多漏洞分析复现&#xff0c;可前往无问社区查看http://www.wwlib.cn/index.php/artread/artid/10358.html 0x01 产品简介 泛微e-cology是一款由泛微网络科技开发的协同管理平台&#xff0c;支持人力资源、财务、行政等多功能管理和移动办公。 0x02 漏洞概述 泛微e-cology…

路由器WAN口和LAN口有什么不一样?

“ 路由器WAN口和LAN口的区别&#xff0c;WAN是广域网端口&#xff0c;LAN是本地网端口。WAN主要用于连接外部网络&#xff0c;而LAN用来连接家庭内部网络&#xff0c;两者主要会在标识上面有区别。以往大部分路由器的WAN只有一个&#xff0c;LAN口则有四个或以上&#xff0c;近…

shader 案例学习笔记之偏移

效果 代码 #ifdef GL_ES precision mediump float; #endifuniform vec2 u_resolution; uniform float u_time;vec2 brickTile(vec2 _st, float _zoom){_st * 5.;_st.x step(1., mod(_st.y,2.0)) * 0.5;return fract(_st); }float box(vec2 _st, vec2 _size){_size vec2(0.5)…

红帽9中nginx-源码编译php

什么是PHP-FPM&#xff1f; PHP-FPM(FastCGI Process Manager&#xff1a; FastCGI进程管理器)是一个实现了Fastcgi的程序&#xff0c;并且提供进程管理的功能。 进程包括master进程和worker进程。master进程只有一个&#xff0c;负责监听端口&#xff0c;接受来自web server 的…

Linux进程(2)(进程状态 - 僵尸、孤儿进程)

目录 1.进程状态 1&#xff09;直接谈论Linux的进程状态 R &#xff1a;进程运行的状态 S &#xff1a;休眠状态 &#xff08;进程在等待“资源”就绪&#xff1b;可中断睡眠&#xff09; T / t &#xff1a;让进程暂停&#xff0c;等待被进一步唤醒 D …

【springboot】整合spring security 和 JWT

目录 1. 整合spring security 1. 导入依赖 2. 配置类 3. 实体类实现UserDetails接口 4. 业务逻辑实现类实现UserDetailsService接口 5. 控制类实现登录功能 6. 测试登录功能 2. 分析源码 1. UsernamePasswordAuthenticationToken 2. A…

【c/c++】类型转换:隐式类型转换、强制类型转换

目录 前言 一、了解类型转换 二、隐式类型转换 1.适用场景 2.转换规则 三、强制类型转换 适用场景 使用规则 注意事项 前言 类型转换是编程中一个常见的现象。在我们进行编码的时候不经意间就发生了&#xff0c;但却能让整个程序运行得更加流畅。 但是这种不经意&am…

C++:STL之vector

1.vector的使用 1.1vector的定义 使用vector需要包含头文件 #include<vector> vector的构造 &#xff08;constructor&#xff09;构造函数声明接口说明vector() (重点)无参构造vector(size_type n,const value_type& val value_type())用n个val初始化并构造vecto…

Java11环境安装(Windows)

目录 1 Java11安装2 配置2.1 JavaHome配置2.2 CLASSPATH配置PATH路径配置 3 验证 1 Java11安装 从官网下载Java11安装包&#xff1a;jdk-11_windows-x64_bin.exe,安装时选择安装到D:\Java目录。 安装完目录结构如下&#xff1a; 2 配置 2.1 JavaHome配置 如下图所示配置JAV…

Ubuntu构建只读文件系统

本文介绍Ubuntu构建只读文件系统。 嵌入式系统使用过程中&#xff0c;有时会涉及到非法关机&#xff08;比如直接关机&#xff0c;或意外断电&#xff09;&#xff0c;这可能造成文件系统损坏&#xff0c;为了提高系统的可靠性&#xff0c;通常将根文件系统设置为只读&#xf…

Linux下进程间的通信--信号量

前言&#xff1a; 资源竞争&#xff1a; 资源竞争&#xff08;Race Condition&#xff09;是多线程或多进程环境中的一种常见问题&#xff0c;它发生在多个进程或线程并发访问和修改同一资源&#xff08;如内存位置、文件、数据库记录等&#xff09;时&#xff0c;而最终结果…

mysql学习教程,从入门到精通,SQL AND OR 运算符(12)

1、SQL AND & OR 运算符 在本教程中&#xff0c;您将学习如何在子句中使用ASELECT column1_name, column2_name, columnN_nameFROM table_nameWHERE condition1 AND condition2;ND&#xff06;OR运算符&#xff0c;WHERE以根据多个条件过滤记录。 1.1、根据条件选择记录 …

从代码直观理解Self-Attention和Cross-Attention的本质区别

Transformer的模型架构实际上非常简单&#xff0c;Self-Attention 和 Cross-Attention 仅仅是在 k, v上有所不同&#xff08;这里不讨论 mask&#xff09;。 论文原文&#xff1a;Attention Is All You Need 我们可以使用同一个 Attention 类来实现 Self-Attention 和 Cross-At…

day11-多线程

一、线程安全问题 线程安全问题出现的原因&#xff1f;存在多个线程在同时执行同时访问一个共享资源存在修改该共享资源 线程安全:多个线程同时修改同一个资源 取钱案例 小明和小红是一对夫妻&#xff0c;他们有一个共同的账户&#xff0c;余额是10万元 如果小明和小红同时来取…

速看!6款可以写论文的ai写作网站,这才是真正的论文神器!(含教程)

在当今信息爆炸的时代&#xff0c;AI写作工具的出现极大地提高了写作效率和质量。特别是对于需要撰写论文的学生和研究人员来说&#xff0c;这些工具提供了极大的便利。本文将重点介绍一款备受推荐的AI写作平台——千笔-AIPassPaper&#xff0c;并结合相关教程帮助用户更好地使…