Java之线程的概念及方法的学习

news2025/1/19 20:32:22

线程创建

方法一

直接使用Thread

public class demo {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }.start();
        System.out.println(Thread.currentThread().getName());
    }
}

main

Thread-0

方法二

使用Runnable配合Thread将任务与线程创建分开

public class demo {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        //第二个参数是指定线程名字
        new Thread(runnable,"t1").start();
        System.out.println(Thread.currentThread().getName());
    }
}

main

t1

不过Runnable被@FunctionalInterface注解修饰,可以使用lambda表达式化简

public class demo {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName());
        }
    //第二个参数是指定线程名字
    new Thread(runnable,"t1").start();
	System.out.println(Thread.currentThread().getName());
	}
}

方法三

使用FutureTask配合Thread。(FutureTask参数是Callable接口)

public class demo {
    public static void main(String[] args) throws Exception {
        FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(1000L);
                return 1;
            }
        });
        Thread t1 = new Thread(task, "t1");
        t1.start();
        System.out.println(task.get());
    }
}

t1

1

FutureTask实现了RunnableFuture,而RunnableFuture又继承了Runnable与Future接口,Future主要提供了一个get()方法,用于接收子线程的返回值。task.get()在执行时会阻塞,当子线程执行完毕后返回结果才会恢复正常。

线程执行

线程在执行过程中是交替执行的,谁先谁后不由我们控制,由操作系统中的任务调度器控制。

线程的常见方法

  • start():线程的执行方法,只能说明线程准备好了,并不一定立即执行run方法,要等待CPU时间片分配给他才可以执行。
  • run():线程被调用后要执行的方法
  • join():等待线程的运行结束
  • join(long n):等待线程运行结束的最大等待时间。
  • sleep(long n):线程休眠时间
  • interrupt():打断指定线程
  • isInterrupted():判断线程是否被打断 不会被清除打断标记
  • interrupted():判断线程是否被打断 会被清楚打断标记

sleep与yield区别

调用sleep会将线程状态从Running进入到Timed waiting状态,其他线程可以调用interrupt方法叫醒其他睡眠的线程,线程休眠结束后并不一定立即执行。

yield会将当前线程状态从Running到Runnable状态,然后调度执行其他线程。但是具体执行哪个线程还是由操作系统决定

sleep与wait区别

sleep并不会释放锁,而wait会释放锁对象

sleep时Thread方法,wait是Object方法,并且wait需要配合synchronized使用

park与unpark

与wait与notify相似,都是让线程休眠。但是是LockSupport中的方法。

LocakSupport.park(),使作用域中的线程进入休眠。

LockSupport.unpart(线程对象),唤醒线程。

unpark可以在线程park之前进行执行,使未park的线程在执行park后起不到休眠的作用。

原理

park会将线程中的某个属性值修改为0,如果本身就为0时执行park会使线程休眠。如果本身为1,则修改为0不会休眠。

unpark会将线程中属性修改为1,多次调用unpark也只是设置为1。如果线程本身就在休眠,那么会将其唤醒不修改其属性值还是为0,如果线程本身就在执行中,那么会将其属性修改为1。

查看进程的方法

windows

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程
  • tasklist 查看进程 筛选进程 tasklist | findstr 程序名
  • taskkill 杀死进程

linux

  • ps -fe 查看所有进程 筛选 ps -fe | grep 程序名
  • ps -fT -p <PID> 查看某个进程(PID)的所有线程
  • kill杀死进程
  • top 按大写 H 切换是否显示线程
  • top -H -p <PID> 查看某个进程(PID)的所有线程

Java

  • jps 命令查看所有 Java 进程
  • jstack <PID> 查看某个 Java 进程(PID)的所有线程状态
  • jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

线程运行原理

栈与栈帧

栈内存是给线程使用的,每启动一个线程JVM都会为其分配一个栈内存

每个栈由多个栈帧组成,对应每次方法调用所占用的内存。每个栈只有一个活动栈帧,对应正在运行的方法。

线程上下文

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

以上前三种是被动停止,最后一种属于主动停止自己的线程。

线程在进行切换时会记录停止线程的当前状态,方便恢复执行时,从停止地方接着执行。

防止CPU占用率100%

在没有CPU计算时,不要让while(true)空转浪费CPU,这时可以通过sleep或yield让出CPU去执行其给程序。(在单核CPU如果存在while(true)会占用率为100%)

interrupt打断线程

阻塞状态下

如果子线程在阻塞状态下如sleep、wait、join时,被interrupt方法打断会抛出异常。sleep被interrupt打断后,会清除打断标记!

阻塞状态下也会被标记为true,但是退出线程后会被清除为false。

public class demo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        //这里调用interrupt后子线程执行sleep方法,正常打断,抛出异常,结束线程
        t1.interrupt();
        System.out.println(t1.isInterrupted());
    	//主线程休眠让子线程执行sleep
        Thread.sleep(100L);
        //线程结束,清除标记
        System.out.println(t1.isInterrupted());
    }
}

true

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at demo.lambda$main$0(demo.java:5)

at java.lang.Thread.run(Thread.java:745)

false

正常状态下

如果子线程正在执行,被其他线程打断的话,由子线程自身决定自己是否停止执行

public class demo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
           while (true){
               //如果正常状态下被打断会被标记为true
               if (Thread.currentThread().isInterrupted()){
                   System.out.println("我被打断了");
                   break;
               }
           }
        });
        t1.start();
        Thread.sleep(100L);
        t1.interrupt();
    }
}

interrupt、interrupted、isInterrupted

  • interrupt:标记调用者打断标记为true。
  • interrupted:获取当前线程的中断状态、并清除。哪个线程中执行就是获取哪个线程
  • isInterrupted:获取对象线程的中断状态,但不会清除

两阶段终止模式(设计模式)

打断标记法实现

指的是线程1终止线程2的进行

比如说一个后台监控系统,一个线程while循环持续监控,当不需要监控时,打断线程即可,如果在休眠期期间被打断,那么抓住异常手动设置打断标记,如果是执行监控时被打断,等到下一次判断时就会退出循环。

public class TwoPhaseTerminationTest {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
        twoPhaseTermination.start();
        Thread.sleep(3000);
        twoPhaseTermination.stop();
    }
}

class TwoPhaseTermination{
    private Thread monitor;

    public void start(){
        monitor = new Thread(()->{
            System.out.println("开始监控");
            Thread thread = Thread.currentThread();
            while (true){
                if (thread.isInterrupted()){
                    System.out.println("结束前终止操作");
                    break;
                }
                try {
                    Thread.sleep(2000);
                    System.out.println("进行监控");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    thread.interrupt();
                }
            }
        });

        monitor.start();
    }

    public void stop(){
        System.out.println("停止监控");
        monitor.interrupt();
    }
}

开始监控

进行监控

停止监控

结束前终止操作

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at TwoPhaseTermination.lambda$start$0(TwoPhaseTerminationTest.java:23)

at java.lang.Thread.run(Thread.java:745)

volatile实现

public class TwoPhaseTerminationTest {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
        twoPhaseTermination.start();
        Thread.sleep(3000);
        twoPhaseTermination.stop();
    }
}

class TwoPhaseTermination{
    private Thread monitor;

    private volatile boolean stop = false;

    private boolean starting = false;

    public void start(){
        //防止主线程多次使用start()方法来创建多个相同的监控线程。
        synchronized(this){
            if(starting){
                return;
            }
            starting = true;
        }
        monitor = new Thread(()->{
            System.out.println("开始监控");
            Thread thread = Thread.currentThread();
            while (true){
                if (stop){
                    System.out.println("结束前终止操作");
                    break;
                }
                try {
                    Thread.sleep(2000);
                    System.out.println("进行监控");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        monitor.start();
    }

    public void stop(){
        System.out.println("停止监控");
        //设置为false后break结束循环
        stop = true;
        //如果线程休眠时间较长,但是需要即使打断的话也可以使用interrupt方法来打断。
        monitor.interrupt();
    }
}

守护线程

通常情况下,当Java中所有线程结束后,程序才会结束,但是如果存在守护线程,当其他非守护线程结束后,即使守护线程还未执行结束,程序也会停止。

public class demo {
    public static void main(String[] args) {
        new Thread(()->{
            try {
                Thread.sleep(10000);
                System.out.println("线程结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        System.out.println("主线程结束");
    }
}

主线程结束

线程结束

以上是主线程结束后程序等待子线程结束后才会停止。那么将子线程设置为守护线程测试

public class demo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(10000);
                System.out.println("线程结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.setDaemon(true);
        t1.start();
        System.out.println("主线程结束");
    }
}

主线程结束

此时程序并没有等待子线程的结束而是直接停止了运行。

线程运行状态

五种状态

从操作系统上来讲。线程一共存在五种运行状态,分别为初始状态、可运行状态、运行状态、阻塞状态、终止状态

  1. 初始化状态:new了但没start,并未与操作系统中的线程相关联。
  2. 可运行状态:start了,但是没有执行,CPU时间片没有分配到
  3. 运行状态:获取了CPU时间片可以执行线程中的代码。
  4. 阻塞状态:执行了阻塞API,如读取文件等IO操作,测试线程进入阻塞状态,并且调度器不会为阻塞状态下的线程分配时间片,直到阻塞结束后由操作系统将其转化为可运行状态才会为其分配时间片。
  5. 终止状态:线程执行结束,声明周期结束。

六种状态

从java层次来看,线程通过枚举一共有六种状态。

  1. NEW:对应操作系统层次中的初始化状态
  2. RUNNABLE:对应操作系统中的【可运行状态】【运行状态】【阻塞状态】因为在Java中,并不能判断出自己执行的是阻塞操作,因此将阻塞状态也归结为RUNNABLE。
  3. TERMINATED:线程运行结束。
  4. TIMED_WAITING:有时限的等待如sleep
  5. WAITING:无时限的等待如join
  6. BLOCKED:其他线程拿到了同步锁对象,当其他线程再去拿就会进去BLOCKED状态。

线程变量的安全问题

成员变量与静态变量

对于成员变量与静态变量,如果没有共享则线程安全。如果共享则要看他们状态是否会发生改变。

如果是只读下,线程安全,涉及到读写则线程不安全。

局部变量

局部变量是线程安全的,但是局部变量引用的对象不一定线程安全,取决于被引用的对象有没有逃离作用范围。

public class demo2 {
    private static final int THREAD_NUM = 2;
    private static final int FOR_NUM = 200;

    public static void main(String[] args) {
        ThreadUnsafe threadUnsafe = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(()->{
                threadUnsafe.method1(FOR_NUM);
            },"t"+i).start();
        }
    }
}

class ThreadUnsafe {
    List<String> list = new ArrayList<>();
    public void method1(int num) {
        for (int i = 0; i < num; i++) {
            method2();
            method3();
        }
    }

    public void method2(){
        list.add("1");
    }

    public void method3(){
        list.remove(0);
    }
}

Exception in thread "t0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

at java.util.ArrayList.rangeCheck(ArrayList.java:653)

at java.util.ArrayList.remove(ArrayList.java:492)

at ThreadUnsafe.method3(demo2.java:32)

at ThreadUnsafe.method1(demo2.java:23)

at demo2.lambda$main$0(demo2.java:12)

at java.lang.Thread.run(Thread.java:745)

至于为啥会报错,应该看add源码

重点在于size++。字节码文件add操作不是一个原子操作。

t1线程抢占CPU时间片后,对list进行size++,加完后要要返回size值,比如说初始是0,进行size++后应该返回1但是还未进行返回,线程t2拿到CPU时间片,拿到的size值还为0,进行size++后返回1,t1恢复还是返回1但实际上size应该为2。因此remove两次后就会报错。由此可以总结问题所在是list变量被线程共享了,只需要将list变为局部变量即可解决这个问题

class ThreadSafe {
    public void method1(int num) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            method2(list);
            method3(list);
        }
    }

    public void method2(List list) {
        list.add("1");
    }

    public void method3(List list) {
        list.remove(0);
    }
}

这样并不会报错。因为各自的线程内存在各自的list。

子类重写父类方法可能会导致线程不安全

import java.util.ArrayList;
import java.util.List;

public class demo2 {
    private static final int THREAD_NUM = 2;
    private static final int FOR_NUM = 200;

    public static void main(String[] args) {
        ThreadSafeSubclass threadSafeSubclass = new ThreadSafeSubclass();
        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(() -> {
                threadSafeSubclass.method1(FOR_NUM);
            }, "thread" + i).start();
        }
    }
}

class ThreadUnsafe {
    List<String> list = new ArrayList<>();

    public void method1(int num) {
        for (int i = 0; i < num; i++) {
            method2();
            method3();
        }
    }

    public void method2() {
        list.add("1");
    }

    public void method3() {
        list.remove(0);
    }
}

class ThreadSafe {
    public void method1(int num) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            method2(list);
            method3(list);
        }
    }

    public void method2(List list) {
//        list.add("1");
        System.out.println(Thread.currentThread().getName()+":1");
    }

    public void method3(List list) {
        list.remove(0);
    }
}

class ThreadSafeSubclass extends ThreadSafe{
    @Override
    public void method3(List list) {
        new Thread(()->{
//            list.remove(0);
            System.out.println(Thread.currentThread().getName()+":2");
        }).start();
    }
}

结果可知,没办法保证方法的执行顺序。

线程安全类

String类、Integer、StringBuffer、Random、JUC包下的所有方法等都是线程安全的。

他们单个方法都是线程安全的(因为加入了synchronized关键字),但是当他们组合使用是就不是线程安全的。如下图。

HashTable hashTable = new HashTable();
if(hashTable.get("key") == null){
    hashTable.put("key",value);
}

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

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

相关文章

深信服AC应用控制技术

拓扑图 目录 拓扑图 一.上班时间不允许使用qq(假设上班时间是上午9到12&#xff0c;下午14到18) 1.新增上班时间不允许使用qq访问权限策略 2.将策略应用到组&#xff0c;例如修仙部 3.验证 上班时间发现登录不了 下班时间可以登录 二.上班时间不允许访问视频网站(假设上班时…

2023年优化算法之之霸王龙优化算法(TROA),原理公式详解,附matlab代码

霸王龙优化算法&#xff08;Tyrannosaurus optimization&#xff0c;TROA&#xff09;是一种新的仿生优化算法&#xff0c;该算法模拟霸王龙的狩猎行为&#xff0c;具有搜索速度快等优势。该成果于2023年发表在知名SCI期刊e-Prime-Advances in Electrical Engineering, Electro…

Go vs Rust:文件上传性能比较

在本文中&#xff0c;主要测试并比较了Go—Gin和Rust—Actix之间的多部分文件上传性能。 设置 所有测试都在配备16G内存的 MacBook Pro M1 上执行。 软件版本为&#xff1a; Go v1.20.5Rust v1.70.0 测试工具是一个基于 libcurl 并使用标准线程的自定义工具&#xff0c;能…

【双指针】复写0

复写0 1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&#xff1a;请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上…

ZYNQ_project:LCD

模块框图&#xff1a; 时序图&#xff1a; 代码&#xff1a; /* // 24h000000 4324 9Mhz 480*272 // 24h800000 7084 33Mhz 800*480 // 24h008080 7016 50Mhz 1024*600 // 24h000080 4384 33Mhz 800*480 // 24h800080 1018 70Mhz 1280*800 */ module rd_id(i…

html网页设计 01基础标签

<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body> <!-- 标题标签 h1最大 --><h1>最大标签</h1><h2>二级标签</h2><h3>三级标签</h3><…

JavaScript管理HTMLDOM元素(增删改查)

本文主要讲解JavaScript如何通过管理HTML上的DOM元素&#xff0c;其中包括如何查询、创建、修改以及删除具体功能和源码讲解。 增加 首先我们准备一个HTML框架和简单CSS样式&#xff0c;我对其中元素作用和关系进行一个简单说明。 <!DOCTYPE html> <html><he…

OpenCV C++ 图像 批处理 (批量调整尺寸、批量重命名)

文章目录 图像 批处理(调整尺寸、重命名)图像 批处理(调整尺寸、重命名) 拿着棋盘格,对着相机变换不同的方角度,采集十张以上(以10~20张为宜);或者棋盘格放到桌上,拿着相机从不同角度一通拍摄。 以棋盘格,第一个内焦点为坐标原点,便于计算世界坐标系下三维坐标; …

提升 Python 执行速度:Codon、C/C++、Rust、Numba(JIT)、Taichi、Nuitka、MatxScript

几种流行的 Python 性能加速方案对比&#xff1a;https://zhuanlan.zhihu.com/p/604519817 对于一般通用场景用户&#xff0c;对性能没有那么强烈的诉求&#xff0c;紧跟官方步伐&#xff0c;升级到最新版本的 Python 既可&#xff0c;或者使用 PyPy。Numba、Codon、Taichi 等这…

IoC DI

Spring 的两大核心思想 : IoC 和 AOP 我们要将对象的控制权交给Spring ,我们就需要告诉 Spring 哪些对象是需要帮我们进行创建的,这里有两类注解可以实现 : 类注解(Controller Service Repository Component Configuration)和方法注解(Bean) 这五大注解都表示把这个对象交给…

【MySQL】InnoDB和MyISAM区别详解(MySQL专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

【Java】ArrayList和LinkedList使用不当,性能差距会如此之大!

文章目录 前言源码分析ArrayList基本属性初始化新增元素删除元素遍历元素 LinkedList实现类基本属性节点查询新增元素删除元素遍历元素 分析测试 前言 在面试的时候&#xff0c;经常会被问到几个问题&#xff1a; ArrayList和LinkedList的区别&#xff0c;相信大部分朋友都能回…

C++之set/multise容器

C之set/multise容器 set基本概念 set构造和赋值 #include <iostream> #include<set> using namespace std;void PrintfSet(set<int>&s) {for(set<int>::iterator it s.begin();it ! s.end();it){cout<<*it<<" ";}cout&l…

保姆级 | Nginx编译安装

0x00 前言 Nginx 是一个 HTTP 和反向代理服务器&#xff0c; 邮件代理服务器&#xff0c; 和通用 TCP/UDP 代理服务器&#xff0c; 最初由伊戈尔西索耶夫&#xff08;Igor Sysoev&#xff09;撰写。采用编译安装可以根据自身需要自定义配置&#xff0c;让服务器有更高的安全性和…

2023腾讯云轻量应用服务器购买优惠活动,轻量服务器优惠链接

双11优惠活动即将到来&#xff0c;各大电商平台纷纷推出超值优惠&#xff0c;腾讯云也不例外。今天&#xff0c;我将向大家介绍一款在双11活动中备受瞩目的服务器套餐——腾讯云的3年轻量应用服务器配置为2核2G4M带宽、50GB SSD系统盘。这款服务器不仅配置强大&#xff0c;而且…

Mac 安装 protobuf 和Android Studio 使用

1. 安装,执行命令 brew install protoc 2. Mac 错误提示&#xff1a;zsh: command not found: brew解决方法 解决方法&#xff1a;mac 安装homebrew&#xff0c; 用以下命令安装&#xff0c;序列号选择中科大&#xff08;1&#xff09;或 阿里云 /bin/zsh -c "$(curl…

Java Web——JavaScript基础

1. 引入方式 JavaScript程序不能独立运行&#xff0c;它需要被嵌入HTML中&#xff0c;然后浏览器才能执行 JavaScript 代码。 通过 script 标签将 JavaScript 代码引入到 HTML 中&#xff0c;有3种方式&#xff1a; 1.1. 内嵌式(嵌入式) 直接写在html文件里&#xff0c;用s…

Git命令总结-常用-后续使用频繁的再添加~

Git命令总结-常用 久了不用&#xff0c;有些时候老是会忘记一些命令&#xff0c;多的都记录一下&#xff0c;方便查找 git init 初始化一个Git仓库&#xff0c;执行完git init命令后&#xff0c;会生成一个**.git**目录&#xff0c;该目录包含了资源数据&#xff0c;且只会在…

如何解决swagger-editor在线接口调试时的跨域问题

文章目录 一&#xff0c;序言二&#xff0c;问题重现1. 运行swagger-editor2. 运行接口服务3. 问题重现步骤 三&#xff0c;解决问题思路1. 去除浏览器安全限制2. 服务器接口统一处理3. 委托nginx转发 四&#xff0c;完整接口代码传送 一&#xff0c;序言 在 Docker 运行swagg…

从C语言的面向过程编程过渡理解面向对象编程风格中的封装

黑发不知勤学早&#xff0c;白首方悔读书迟 专栏推荐Easyx学习实践 有所收获希望大家能够三连哇&#xff01;&#xff01;&#xff01; 在C语言中&#xff0c;我们解决一个问题通常是采用在了解了问题如何解决后&#xff0c;设置一个一个的函数&#xff0c;依次调用实现不同的功…