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

线程创建

方法一

直接使用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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

The ultimate UI kit and design system for Figma 组件库下载

Untitled UI 是世界上最大的 Figma UI 套件和设计系统。可以启动任何项目&#xff0c;为您节省数千小时&#xff0c;并祝您升级为专业设计师。 采用 100% 自动布局 5.0、变量、智能变体和 WCAG 可访问性精心制作。 900全局样式、变量&#xff1a;超级智能的全局颜色、排版和效…

JDBC,Java连接数据库

下载 JDBC https://mvnrepository.com/ 创建项目&#xff0c;然后创建一个目录并将下载好的 jar 包拷贝进去 选择 Add as Library&#xff0c;让这个目录能被项目识别 连接数据库服务器 在 JDBC 里面&#xff0c;使用 DataSource 类来描述数据库的位置 import com.mysql.cj.…

openGauss学习笔记-126 openGauss 数据库管理-设置账本数据库-归档账本数据库

文章目录 openGauss学习笔记-126 openGauss 数据库管理-设置账本数据库-归档账本数据库126.1 前提条件126.2 背景信息126.3 操作步骤 openGauss学习笔记-126 openGauss 数据库管理-设置账本数据库-归档账本数据库 126.1 前提条件 系统中需要有审计管理员或者具有审计管理员权…

CTF-虚拟机——【前置知识三】

文章目录 内存虚拟化常见缩写虚拟机内存访问原理影子页表扩展页表VPID&#xff08;Virtual Processor Identifier&#xff09;&#xff1a;TLB&#xff08;Translation Lookaside Buffer&#xff09;资源优化 内存虚拟化 能够提供在Guest机制中识别为从零开始的连续的物理地址…

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…

链表题(4)

本章内容 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 今天继续给大家带来链表的相关练习题。 相交链表 这道题来自力扣网&#xff0c;链接…

央企太卷.....来自央企的7个面试题,一个一个生产难题

说在前面 在40岁老架构师尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;最近小伙伴&#xff0c;面试央企、美团、京东、阿里、 百度、头条等大厂。 下面是一个小伙伴成功拿到通过了一个央企设计研究院一面面试&#xff0c;现在把面试真题和参考答案收入咱们的宝典。…

FDM(傅里叶分解)

代码的使用教程 傅里叶分解&#xff08;FDM&#xff09; 代码原理 FDM (Frequency Division Multiplexing)是一种调制技术&#xff0c;将信号分成多个不同的频带进行传输&#xff0c;从而实现多路复用的通信方式。FDM分解原理是将不同频率的信号分解成不同的频带&#xff08;子…

网工内推 | Linux运维,六险二金,最高30K,IE认证优先

01 上海域起 招聘岗位&#xff1a;Linux运维工程师 职责描述&#xff1a; 1.负责游戏产品运维相关的工作&#xff0c;流程文档、技术文档、功能脚本的编写整理 2.负责分析并排除系统、数据库、网络、应用等游戏产品运维中出现的故障及错误 3.负责对游戏产品项目进行线上部署、…

阿里AoneFlow分支管理

分支模式 1.TrunkBased模式 工作方式 TrunkBased 模式是持续集成思想所崇尚的工作方式&#xff0c;它由单个主干分支和许多发布分支组成&#xff0c;每个发布分支在特定版本的提交点上从主干创建出来&#xff0c;用来进行上线部署和 Hotfix&#xff08;补丁&#xff09;。 …

制作Go程序的Docker容器

今天突然遇到需要将 Go 程序制作成 Docker 的需求&#xff0c;所以进行了一些研究。方法很简单&#xff0c;但是官方文档和教程有些需要注意的地方&#xff0c;所以写本文进行记录。 源程序 首先介绍一下示例程序&#xff0c;示例程序是一个 HTTP 服务器&#xff0c;会显示si…

一次java系统调优 从150到最高1800的过程

前言 在做公司系统压力测试(500个线程并发)的时候 某个服务的接口 压测初始结果如下 初始指标(最高)&#xff1a; 吞吐量 150/s TPS: 240 CPU,内存&#xff0c;带宽&#xff0c;磁盘io 如下图所示 可以看到资源使用是有问题的 cpu和带宽并没有给足压力 说明并不是资源所导致…

代码随想录算法训练营第三十九天【动态规划part02】 | 62.不同路径、63. 不同路径 II

62.不同路径 题目链接&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 求解思路&#xff1a; 动规五部曲 确定dp数组及其下标含义&#xff1a;dp[i][j] 表示从&#xff08;0,0&#xff09;出发&#xff0c;到&#xff08;i,j&#x…

计算机科学速成课

建议看看计算机科学速成课&#xff0c;一门很全面的计算机原理入门课程&#xff0c;短短10分钟可以把大学老师十几节课讲的东西讲清楚&#xff01;整个系列一共41个视频&#xff0c;B站上有中文字幕版。 每个视频都是一个特定的主题&#xff0c;例如软件工程、人工智能、操作系…

Django的可重用HTML模板示例

01-配置并运行Django项目 首先按照博文 https://blog.csdn.net/wenhao_ir/article/details/131166889配置并运行Django项目。 02-创建可重用模板文件 templates目录下新建目录common&#xff0c;然后在目录common下新建文件&#xff1a;navbar.html&#xff0c;并写入下面的…

Pandas 求平均值

Pandas是Python中最流行的数据分析库之一&#xff0c;它提供了许多强大的工具来处理和分析数据集。其中&#xff0c;求平均值是数据分析中最常见的操作之一。在本文中&#xff0c;我们将从多个角度分析Pandas中如何求平均值。 一、基础操作 Pandas中求平均值的基础操作是使用m…

电磁场与电磁波part3--静态电磁场及其边值问题的解

1、当场源&#xff08;电荷、电流&#xff09;不随时间变化时&#xff0c;所产生的电场、磁场也不随时间变化&#xff0c;称为静态电磁场。静止电荷产生的静电场、在导电媒质中恒定运动电荷形成的恒定电场以及恒定电流产生的恒定磁场都属于静态电磁场。 2、静电场基本方程微分形…

深信服AC应用控制技术

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

springboot323基于Java的美妆购物网站的设计与实现

交流学习&#xff1a; 更多项目&#xff1a; 全网最全的Java成品项目列表 https://docs.qq.com/doc/DUXdsVlhIdVlsemdX 演示 项目功能演示&#xff1a; ————————————————

梦想编织者——Adobe Dreamweaver

今天&#xff0c;我们来谈谈一款在Adobe系列中推出的一款Adobe Dreamweaver&#xff0c;简称“DW”&#xff0c;中文名称 “梦想编织者”&#xff0c;是集网页制作和管理网站于一身的所见即所得网页代码编辑器。 利用对 HTML、CSS、JavaScript等内容的支持&#xff0c;设计人员…