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

线程创建

方法一

直接使用Thread

public class demo {public static void main(String[] args) {new Thread(){@Overridepublic 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() {@Overridepublic 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>() {@Overridepublic 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());//主线程休眠让子线程执行sleepThread.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){//如果正常状态下被打断会被标记为trueif (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{@Overridepublic 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.hqwc.cn/news/191916.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

掌握Shell:从新手到编程大师的Linux之旅

1 shell介绍 1.1 shell脚本的意义 1.记录命令执行的过程和执行逻辑&#xff0c;以便以后重复执行 2.脚本可以批量处理主机 3.脚本可以定时处理主机 1.2 脚本的创建 #!/bin/bash # 运行脚本时候执行的环境1.3 自动添加脚本说明信息 /etc/vimrc # vim主配置文件 ~/.vimrc # 该…

IoC DI

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

Qt布局技巧

可以先把控件放置了&#xff0c;再选中所有控件右键布局 或者是点击上面的&#xff1a;

【数据结构与算法】线性表 - 顺序表

目录 1. 线性表2.顺序表3.顺序表的优缺点4.实现&#xff08;C语言&#xff09;4.1 头文件 seqList.h4.2 实现 seqList.c 1. 线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见…

计算机网络的体系结构

目录 一. 计算机体系结构的形成二. 协议与层次划分2.1 数据传输过程2.2 什么是网络协议2.3 网络协议的三要素2.4 协议有两种形式2.4 各层协议2.5 什么是复用和分用 \quad 一. 计算机体系结构的形成 \quad 计算机网络是一个非常复杂的系统, 相互通信的两个计算机系统必须高度协调…

tomcat8.5处理get请求时,控制台输出中文乱码问题的解决

问题描述 控制台输出中文乱码 版本信息 我使用的是tomcat8.5 问题解决 配置web.xml 注&#xff1a;SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前&#xff0c;否则无效 <!--配置springMVC的编码过滤器--> <filter><filter-name>CharacterEn…

C#特性(Attribute)

C#特性&#xff08;Attribute&#xff09;是一种在程序中添加元数据的机制&#xff0c;它可以为代码提供额外的信息和指示。通过使用特性&#xff0c;我们可以为类、方法、属性等元素添加标记&#xff0c;以便在运行时进行更多的操作和决策。 C#特性是一种声明式编程的工具&…

NSSCTF第13页(1)

[NCTF 2018]Easy_Audit 小小代码审计 $_REQUEST:PHP的内置变量&#xff0c;是一个数组&#xff0c;保存传递的参数&#xff0c;它的特性是如果get,post一起传参&#xff0c;则会优先post传参&#xff0c;可以由此进行变量覆盖。 $_SERVER:PHP的内置变量&#xff0c;是一个数组…

一文搞懂RC滤波器的设计?

滤波器是一种可以对“波”进行过滤的器件&#xff0c;一般是特定频率的信号。所以可以常常看到滤波器的种类繁多&#xff0c;有高通滤波器&#xff0c;低通滤波器&#xff0c;带通滤波器及带阻滤波器等等。 滤波器的主要作用就是滤波&#xff0c;它需要尽可能的让有用信号能够做…

unity教程

前言 伴随游戏行业的兴起&#xff0c;unity引擎的使用越来越普遍&#xff0c;本文章主要记录博主本人入门unity的相关记录大部分依赖siki学院进行整理。12 一、认识unity引擎&#xff1f; 1、Unity相关信息&#xff1a; Unity的诞生&#xff1a;https://www.jianshu.com/p/550…

M2 Mac Xcode编译报错 ‘***.framework/‘ for architecture arm64

In /Users/fly/Project/Pods/YYKit/Vendor/WebP.framework/WebP(anim_decode.o), building for iOS Simulator, but linking in object file built for iOS, file /Users/fly/Project/Pods/YYKit/Vendor/WebP.framework/WebP for architecture arm64 这是我当时编译模拟器时报…

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

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