首页 > 编程语言 >【JavaEE】【多线程】Thread类讲解

【JavaEE】【多线程】Thread类讲解

时间:2024-10-13 09:52:49浏览次数:7  
标签:join Thread void JavaEE 线程 static 多线程 public

目录


一、Thread构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名(当前线程名)
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即为线程组

二、Thread 的常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

解释:

  • ID 是线程的唯一标识,不同线程不会重复,但是这里的id是Java给的id,不是前面PCB中说的id。
  • 名称在各种调试工具用到,前面构造方法给的名称就是这个。
  • 状态表示线程当前所处的一个情况。
  • 优先级高的线程理论上来说更容易被调度到,但是这个是系统微观程度上的,很难感知到。
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程(前台线程)结束后,才会结束运行,而后台线程不影响Java进程的结束,可以在start()调用前使用setDaemon(true)来设置线程为后台线程。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了

三、创建一个线程

前一篇文章中就介绍了相关操作,在这简单提一下一定要使用线程变量名.start();创建一个新线程,start()方法是Java提供的API来调用系统中创建线程的方法。而run()方法是这个线程要干的事情,在线程创建好之后自动就会调用。
每个线程对象只能start一次

四、获取当前线程引用

方法说明
public static Thread currentThread();返回当前线程对象的引用

是静态方法直接使用Thread.currentThread();就可以获取到当前的线程引用。

五、终止一个线程

在Java中终止一个线程的思路就是让线程中的run()方法尽快结束。

5.1 使用标志位

由于线程迟迟不结束大多是因为里面有循环语句,我们就可以使用一个成员变量来控制循环的结束。
不能使用局部变量定义在main方法内,因为虽然lambda表达式可以捕获上层变量,但是这个变量不可以进行修改。

public class Demo {
    private static boolean isQuit = false;
    public static void main(String[] args) {
        Thread thread = new Thread(() ->{
            while(isQuit) {
              //具体操作  
            }
        });
        thread.start();
        isQuit = true;
    }
}

5.2 使用自带的标志位

方法说明
public void interrupt()中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位
public static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位,不建议使用,静态方法为所有线程共用的
public boolean isInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位

Java中自带了标志位来标志是否结束循环。先使用Thread.currentThread()获取到当前线程,在.isInterrupted()获取标志位。然后再主进程中调用interrupte()方法来将标志位值修改为true。

public class Demo {
	public static void main(String[] args) {
	        Thread thread = new Thread(() ->{
	           while (!Thread.currentThread().isInterrupted()) {
	
	               //操作
	           }
	        });
	        thread.start();
	        thread.interrupt();
	    }
}

但是如果在线程中有捕获InterruptedException异常的语句,那么会在调用interrupte()同时捕获到该异常,并且消除标志位。

此时我们就可以在catch语句中自己选择是将线程结束还是进行其它操作。

public class Demo {
    public static void main(String[] args) {
        Thread thread = new Thread(() ->{
           while (!Thread.currentThread().isInterrupted()) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                  //1.不操作继续执行线程(因为sleep唤醒后会又将标志位改为true)
                   e.printStackTrace();
                   //2.结束线程
                   break;
                   //3.进行其它操作
               }
           }
        });
        thread.start();
        thread.interrupt();
    }
}

六、等待一个线程

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)等待线程结束,最多等 millis 毫秒,但可以更高精度

在主线程中调用线程对象.join();就是等待线程对象执行完再执行主线程。
调用细节:

  • 调用线程对象.join();就会让该线程执行完才继续执行外面的线程,如果线程对象对应的线程一直不结束那么外面的线程就会一直等(死等)
  • 调用线程对象.join(long millis);就会在该线程执行millis毫秒后执行外面的线程。
  • 如果遇到调用join前线程已经结束,外面的线程不会陷入等待。

如下代码执行结果就是先打印5个thread线程,最后在打印main线程:

public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          for(int i = 0; i < 5; i++) {
          	System.out.println("thread线程");
          }
        });       
        thread。start();
        thread.join();
        System.out.println("main线程");
    }
}

七、线程休眠

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException可以更高精度的休眠

在系统让线程休眠sleep中的参数毫秒后,线程会被唤醒从阻塞状态变成就绪状态,但不会马上执行,涉及到调度开销。所以实际使用的时间是大于sleep中的参数的。
并且在Windows和Linux系统上达到毫秒级误差。

八、线程状态

在操作系统里面进程和线程最重要的状态就是:就绪状态和阻塞状态。
在Java中又给线程又给线程赋予了一些其他状态。
线程的状态是一个枚举类型 Thread.State。

状态说明
newThread对象已经创建,但是start()方法没有调用
terminatedThread对象还在,但是内核中线程已经结束了
Runnable就绪状态,线程已经在CPU上执行或者在CPU上等待执行
timed_waiting由于像sleep(),join(时间) 这种固定时间产生的阻塞
waiting由于像wait(),join() 这种不固定时间产生的阻塞
blocked由于锁竞争产生的阻塞

九、线程安全

线程安全的简单说法就是符不符合预期:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

例如以下代码:
我们的预期结果是10000,但是其实每次的结果都是不一样的,这种就是线程不安全。

public class Demo {
    private static int ret;
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                ret++;
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                ret++;
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        
        System.out.println(ret);;
    }
}

就以上诉代码例子来讲解出现线程不安全的原因。

在CPU上实现自增操作主要有三步:

  1. 将数据给到CPU的寄存器中;
  2. 数据在寄存器中加1;
  3. 将数据返回到内存中。

就以一个thread1和一个thread2来说,每个线程都进行这三步操作,但是线程在CPU上又是随机调用的,这就相当于有六个位置随机坐,相当于排列组合的A66,当数据作为不同线程的开始值进入寄存器时就相当于两次自增只执行了一次。

但是线程调用就更加复杂了,线程数量不一样,顺序不一样,这就相当于有无数种可能了,所以结果是不可控的,就导致了线程不安全的情况。

9.1 线程不安全原因总结

在介绍线程不安全原因之前先介绍一个概念:原子性。

原子性:简单来讲就是执行一段代码连续执行完不被其他线程干扰。举个例子:

我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。

那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。

有时也把这个现象叫做同步互斥,表示操作是互相排斥的。

原因总结:

  • 操作系统调度线程是随机的(抢占式执行);
  • 多个线程对同一个变量进行修改;
  • 修改操作不是原子性的;
  • 内存可见性问题;
  • 指令重排序问题。

9.2 解决由先前线程不安全问题例子

要解决就要从原因入手:

  • 操作系统随机调度是操作系统带来的解决不了;
  • 多个线程对一个变量修改,有些可以规避,但有些根据需求无法规避。
  • 将操作改为原子性,可以通过synchronized关键字 加锁操作来实现。

语法:

synchronized(变量){
//修改操作
}

()括号内的变量不重要,作用是区分加锁对象是否一样,如果对同一个对象加锁,那么两个操作就会产生“blocked”锁竞争阻塞问题,后一个线程就会等到前一个线程解锁再执行。
进入左大括号 ‘{’ 就是加锁,出了右大括号 ‘}’ 就是解锁。

对上诉代码进行如下修改,就会出现预期结果10000:

public class Demo7 {
    private static int ret;
    public static void main(String[] args) throws InterruptedException {
        Object block = new Object();
        
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                
                synchronized (block){
                    ret++;
                }
                
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {

                synchronized (block){
                    ret++;
                }

            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(ret);;
    }
}

synchronized还可以修饰方法(静态方法也行)。

  • synchronized修饰实例方法:
class Counter{
    public int ret;
    public void increase1() {
        synchronized (this) {
            ret++;
        }
    }
    //简化版本
    synchronized public void increase2() {
        ret++;
    }
}
  • synchronized修饰静态方法:相当于修饰这个类
class Counter{
	private static int ret2;
	public static void increase3() {
        synchronized (Counter.class) {
            ret2++;
        }
    }
    //简化版本
    synchronized public static void increase4() {
        ret2++;
    }
}

标签:join,Thread,void,JavaEE,线程,static,多线程,public
From: https://blog.csdn.net/yj20040627/article/details/142643179

相关文章

  • C#线程---ThreadPool
    线程池的简介   为每个短暂的异步操作创建线程会产生显著的开销,线程池可以成功地适应于任何需要大量短暂的开销大的资源的情形。我们事先分配一定的资源,将这些资源放入到资源池。每次需要新的资源.只需从池中获取一个,而不用创建一个新的。当该资源不再被使用时,就将其返......
  • java 网络知识 + 多线程问题
    服务器:packagep1007;importjava.io.*;importjava.net.*;importjava.util.Random;publicclassServer{publicstaticvoidmain(String[]args){intport=12345;//服务端口try(ServerSocketserverSocket=newServerSocket(port)......
  • ThreadLocal和连接池
    ThreadLocal线程隔离工具用来存储一些只有线程才可以访问的内容。你可能会想,既然我只想本线程才能访问,那么我使用局部变量不就行了吗?局部变量的问题在于它只能存在于本方法内部,没有办法让本线程内的其他方法访问使用。publicstaticvoidmain(String[]args){Run......
  • JavaEE: 深入解析HTTP协议的奥秘(1)
    文章目录HTTPHTTP是什么HTTP协议抓包fiddle用法HTTP请求响应基本格式HTTPHTTP是什么HTTP全称为"超文本传输协议".HTTP不仅仅能传输文本,还能传输图片,传输音频文件,传输其他的各种数据.因此它广泛应用在日常开发的各种场景中.HTTP往往是基于传输层的......
  • gdb多线程多进程调试命令
    多线程infothreads查看当前所有运行线程的列表thread线程编号 切换到特定线程进行调试setscheduler-lockingon只运行当前线程,停止其他线程进行调试多进程infoinferions显示所有正在调试的进程inferion进程编号 切换到特定进程运行,同时挂起其他进程detach-on-fo......
  • C# 线程---Thread1
     1.thread不带参数(Main和Thread都在同步处理)(注意usingstatic和System.Console的使用)usingstaticSystem.Console;namespaceRecipe1{classProgram{staticvoidMain(string[]args){Threadt=newThread(PrintNumber);......
  • Windows多线程编程 互斥量和临界区使用
    Windows多线程编程允许程序同时运行多个线程,提高程序的并发性和执行效率。多线程编程中的核心概念包括线程的创建、同步、调度、数据共享和竞争条件等。本文详细介绍了Windows多线程编程的关键技术点,并解释如何使用线程同步机制来保证线程安全。1.线程基础概念1.1线......
  • 如何用PyQt5创建多个窗口,同时获取多个U盘内的文件的名称,并分别在对应窗口打印文件名,要
    在PyQt5中,你可以使用QThread创建多个线程来并行处理每个U盘的文件名获取任务。每个线程负责扫描一个U盘的文件,同时在主窗口显示结果。以下是一个示例代码,用来创建多个窗口,同时在每个窗口中显示各自的U盘文件名:每个窗口使用QWidget。使用QThread创建后台线程获取U......
  • 【JavaEE】——回显服务器的实现
     阿华代码,不是逆风,就是我疯你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!!目录一:引入1:基本概念二:UDPsocketAPI使用1:socket文件2:DatagramSocket类(1)构造方法(2)方法3:DatagramPacket类三:回显服务器——服务器1:引入(必看)2:服务器响应代码(1)注释版本(2)......
  • 全局视角看技术-Java多线程演进史
    作者:京东科技文涛全文较长共6468字,语言通俗易懂,是一篇具有大纲性质的关于多线程的梳理,作者从历史演进的角度讲了多线程相关知识体系,让你知其然知其所以然。前言2022年09月22日,JDK19发布了,此版本最大的亮点就是支持虚拟线程,从此轻量级线程家族再添一员大将。虚拟线程使JVM摆脱......