《Java 高并发》04 线程的基本操作

新建线程

新建线程很简单。只要使用new 关键字创建一个线程对象,并且调用 start 方法启动线程。

Thread t = new Thread();
t.start();

注意:run 方法不是用来启动线程。如果调用 run 方法它只会作为普通方法来执行,而不会开启线程执行。

终止线程

一般来说,线程在执行完毕后就会结束,无须手工关闭。但凡是都有例外。Thread 类提供了一个 stop 方法来终止线程。如果调用 stop 方法,就可以立即将一个线程终止。

目前 stop 方法已经过期。因为 stop 方法太过于暴力,它会把执行到一半的线程终止,此时可能会引起数据不一致问题。

举例:对象 User 有 id、name 两个属性。写线程总是把 id、name 写成相同的值。当写线程在写对象时,读线程由于无法获得锁,因此必须等待,所以读线程是看不见一个写了一半的对象。此时,写线程写完id后,很不辛被 stop,此时对象 u 的 id 为1,而 name 任然为0,出于不一致状态。而被终止的写线程简单地讲锁释放,度线程获取到锁后,读取数据,于是读到了 id=1 而 name=0 。

public class StopThreadTest {public static User u = new User();public static class User {private int id;private String name;public User() {this.id = 0;this.name = "0";}public User(int id, String name) {this.id = id;this.name = name;}public void setId(int id) {this.id = id;}public void setName(String name) {this.name = name;}public int getId() {return id;}public String getName() {return name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}}public static class ChangeObjectThread extends Thread {@Overridepublic void run() {while (true) {synchronized (u) {int i = (int) (System.currentTimeMillis() / 1000);u.setId(i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}u.setName(String.valueOf(i));}}}}public static class ReadObjectThread extends Thread {@Overridepublic void run() {while (true) {synchronized (u) {if (u.getId() != Integer.valueOf(u.getName())) {System.out.println(u.toString());}}}}}public static void main(String[] args) {try {new ReadObjectThread().start();while (true) {ChangeObjectThread thread = new ChangeObjectThread();thread.start();Thread.sleep(150);thread.stop();}} catch (InterruptedException e) {e.printStackTrace();}}
}

打印结果:

User{id=1619771639, name='1619771638'}
User{id=1619771640, name='1619771639'}

那么如果优雅的停止一个线程,又不会产生数据不一致问题?可以考虑定义一个开关,通过开关去控制。

public class StopThreadTest {public static User u = new User();public static boolean stopme = true;public static class User {private int id;private String name;public User() {this.id = 0;this.name = "0";}public User(int id, String name) {this.id = id;this.name = name;}public void setId(int id) {this.id = id;}public void setName(String name) {this.name = name;}public int getId() {return id;}public String getName() {return name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}}public static void stopMe(){stopme = false;}public static class ChangeObjectThread extends Thread {@Overridepublic void run() {while (stopme) {synchronized (u) {int i = (int) (System.currentTimeMillis() / 1000);u.setId(i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}u.setName(String.valueOf(i));}}}}public static class ReadObjectThread extends Thread {@Overridepublic void run() {while (true) {synchronized (u) {if (u.getId() == Integer.valueOf(u.getName())) {System.out.println(u.toString());}System.out.println(u.toString());}}}}public static void main(String[] args) {try {new ReadObjectThread().start();while (true) {ChangeObjectThread thread = new ChangeObjectThread();thread.start();Thread.sleep(150);stopMe();}} catch (InterruptedException e) {e.printStackTrace();}}
}

日志打印:

User{id=1619774686, name='1619774686'}
User{id=1619774686, name='1619774686'}
User{id=1619774686, name='1619774686'}

线程中断

从表面上理解,中断就是让目标线程停止执行的意思,实际上并非如此。

严格来讲,线程中断并不会是线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出。至于目标线程是否退出,由目标线程自己决定。

线程中断三个方法:

// 中断线程
public void interrupt();
// 判断线程是否中断
public boolean isInterrupted();
// 判断线程是否中断,并清楚当前中断状态
public static boolean interrupted();

interrupt() 方法通知目标方法中断,也就是设置中断标志位,中断标志位表示当前线程已经被中断了;isInterrupted() 判断当前线程是否有被中断;interrupted() 也是用来判断当前线程是否被中断,但同时会清除当前线程的中断标志位状态。

    public void interruptTest1(){try {Thread t = new Thread() {@Overridepublic void run() {while (true) {Thread.yield();}}};t.start();Thread.sleep(2000);t.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}

线程t 虽然进行了中断,但是并没有线程中断后处理的逻辑,因此线程t 即使被中断,但是这个中断不会发生任何左右。

优化:线程中断就退出while

    public void interruptTest2() {try {Thread t = new Thread() {@Overridepublic void run() {while (true) {// 判断当前线程是否被中断 if (Thread.currentThread().isInterrupted()){System.out.println("Interrupted");break;}Thread.yield();}}};t.start();Thread.sleep(2000);t.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}

等待和通知

为了支持多线程之间的协作,JDK 提供了两个非常重要的接口线程等待 wait() 和通知 notify()。注意,这两个方法不是在 Thread 类中,而是在 Object 类。这也意味着任何对象都能调用。

public final void wait() throws InterruptedException;
public final native void notify();

当一个对象实例调用wait 方法后,当前线程就会在这个对象上等待。比如,线程A 中,调用了obj.wait() 方法,那么线程A 就会停止继续执行,转为等待状态。当其他线程调用obj.notify() 方法为止结束等待状态。此时obj 对象就俨然成为多个线程之间的有效通讯手段。

扩展

面试题:多线程之间的通讯方式?

  1. wait()、notify()
  2. 同步 synchronized
  3. while 轮训
  4. 管道通信(PipedInputStream、PipedOutPutStream)

PS:清楚有这么一个东西即可,如何实现水平有限,可自行查阅。有错请指教

wait()、notify() 工作过程:如果一个线程调用了 object.wait() 方法,那么它就会进入object 对象的等待队列。在这个队列中,可能会有多个线程。当调用 object.notify() 被调用时,它会从这个等待队列中,随机选择一个线程,并将它唤醒。同时 Object 对象还提供了另一个方法 notifyAll() 方法,它和notify() 功能基本一致,不同的是notifyAll 会唤醒这个队列中的所有等待的线程,而不是随机选择一个。

强调,调用wait() 方法必须在 snchronzied 语句中,无论是wait()、notify() 都需要先获得锁,当执行wait() 方法后,会释放这个锁。这样做的目的是使得其他等待该锁的线程不至于无法正常执行。

public class WaitNotifyTest {final static Object object = new Object();public static class T1 extends Thread {@Overridepublic void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + ": T1 start");try {System.out.println(System.currentTimeMillis() + ": T1 wait for object");object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(System.currentTimeMillis() + ": T1 end");}}}public static class T2 extends Thread {@Overridepublic void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + ": T2 start ! notify one thread");object.notify();System.out.println(System.currentTimeMillis() + ": T2 end");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {T1 t1 = new T1();T2 t2 = new T2();t1.start();t2.start();}}

如上,两个线程 t1、t2。t1 执行 object.wait() 方法前,获取object 对象锁。因此,在执行 object.wait() 是,它是持有 object 锁,wait() 执行后,t1 会进入等待,并释放 object 的锁。t2 在执行 notify() 之前也会先获取 object 的对象锁。t1 在得到 notify() 通知后,还是会先尝试重新获取 object 锁。上述运行日志打印:

1620273470618: T1 start
1620273470618: T1 wait for object
1620273470618: T2 start ! notify one thread
1620273470618: T2 end
1620273472620: T1 end

挂起和继续执行

挂起suspend 和继续执行resume 是一对相反的操作,被挂起suspend 的线程,必须要等到继续执行resume 操作后,才能继续执行。目前 suspend()、resume() 已经过时,不推荐使用。

使用 suspend() 挂起线程会导致线程被暂停,同时并不会释放任何锁资源。此时,其他线程想要访问被它暂用的锁时,都会导致无法正常继续执行。直到对应的线程进行了resume() 操作,被挂起的线程才能继续,从而其他阻塞的线程才可以继续执行。严重的情况是:它暂用的锁不会被释放,因此可能会导致整个系统工作不正常。而且,对于被挂起的线程,从它的线程状态上看,居然还是Runnable ,严重影响对系统当前状态的判断。

public class SuspengResumeTest {public static Object object = new Object();static ChangeObjectThread t1 = new ChangeObjectThread("t1");static ChangeObjectThread t2 = new ChangeObjectThread("t2");public static class ChangeObjectThread extends Thread{public ChangeObjectThread(String name) {super.setName(name);}@Overridepublic void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + " in "+ getName());Thread.currentThread().suspend();System.out.println(System.currentTimeMillis() + " in "+ getName());}}}public static void main(String[] args) {try {t1.start();Thread.sleep(1000);t2.start();t1.resume();t2.resume();t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}}
}

结果打印:

1620285481858 in t1
1620285482859 in t1
1620285482859 in t2

通过日志发现,他们都获取到了锁。但是线程不会退出,而是是会挂起。虽然主函数已经调用了 resume() ,但是由于事件先后顺序的缘故,导致 t2 线程被永远挂起,并且占用了对象锁。

优化 suspend()、resume():

public class SuspengResumeTest2 {public static Object object = new Object();public static class ChangeObjectThread extends Thread {volatile boolean suspendme = false;public void suspendsMe() {suspendme = true;}public void resumeMe() {suspendme = false;synchronized (this) {notify();}}@Overridepublic void run() {while (true) {synchronized (this) {while (suspendme) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}synchronized (object) {System.out.println("in ChangeObjectThread");}Thread.yield();}}}public static class ReadObjectThread extends Thread{@Overridepublic void run() {while (true) {synchronized (object) {System.out.println("in ReadObjectThread");}Thread.yield();}}}public static void main(String[] args) {try {ChangeObjectThread t1 = new ChangeObjectThread();ReadObjectThread t2 = new ReadObjectThread();t1.start();t2.start();Thread.sleep(1000);t1.suspendsMe();System.out.println("suspend t1 2 sec");Thread.sleep(2000);System.out.println("resume t1");t1.resumeMe();} catch (InterruptedException e) {e.printStackTrace();}}}

等待线程结束join 和谦让yield

很多时候,一个线程的执行很可能需要依赖于另外一个或者多个线程执行完毕之后才能继续执行。比如,日常工作需要产品先出需求文档,然后召开需求评审,紧接着进行软件开发。JDK 提供了 join() 来实现这个功能。

public final void join() throws InterruptedException;
public final synchronized void join(long millis) throws InterruptedException;

第一个 join() 表示无限等待,他会一致阻塞当前线程,直到目标线程执行完毕。

第二个 join(long) 表示最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。

public class JoinTest {public volatile static int num = 1;public static class JoinThread extends Thread {@Overridepublic void run() {for (; num < 100000000; num++) ;}}public static void main(String[] args) {try {JoinThread joinThread = new JoinThread();joinThread.start();joinThread.join();System.out.println("num :" + num);} catch (InterruptedException e) {e.printStackTrace();}}}

结果打印:

num :100000000

如果把 joinThread.join(); 注释掉,查看日志 num :1

主函数在等待 joinThread 线程执行完毕再继续执行,此时 num 为 100000000。

扩展

join() 的本质是让调用线程 wait() 在当前线程对象实例上。源码:

public final void join() throws InterruptedException {join(0);
}
    public final synchronized void join(long millis) throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}

可以看到,它让调用线程在当前线程对象上进行等待。当线程执行完成后,被等待的线程会在退出前调用 notifyAll() 通知所有的等待线程继续执行。因此,不建议直接在 Thread 对象实例上使用类似于 wait()和notify() 等方法,因为这有可能影响系统API的工作。

Thread 类中的另一个方法 yield(),定义:

public static native void yield();

静态方法,一大执行,它会使得当前线程让出CPU。但是要注意,让出CPU 并不表示当前线程不执行。当前线程在让出CPU 后,还会进行CPU 资源的争夺,能够再次被分配就不一定了。因此,Thread.yield() 的调用就好像再说,我已经完成了一些最重要的工作了,可以休息一下了,可以给其他线程一些工作机会!

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

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

相关文章

Dispatch 方法简介

后台执行 dispatch_async(dispatch_get_global_queue(0, 0), ^{ //后台程执行 something; }); 主线程执行 dispatch_async(dispatch_get_main_queue(), ^{// 主线程执行something; }); 一次性执行 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 主…

linux杂七杂八整理

64系统里执行32位程序&#xff1a; 1、在64系统里执行32位程序如果出现/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory&#xff0c;安装下glic即可sudo yum install glibc.i6862、error while loading shared libraries: libz.so.1: cannot …

《Java 高并发》05 线程的基本操作

volatile 与 Java 内存模型 Java 内存模型都是围绕着原子性、有序性和可见性展开的。为了在适当的场合&#xff0c;确保线程间的原子性、有序性和可见性。Java 使用了一些特许的操作或者关键字来申明、告诉虚拟机&#xff0c;在这个地方&#xff0c;要尤其注意&#xff0c;不能…

mybatis 2 -常用数据操作

1、写入数据并获取自增ID XML配置&#xff1a; <!-- 写入数据获取自增ID --><insert id"insertLog" parameterType"com.mamaguwen.entity.sys_loginlog" useGeneratedKeys"true" keyProperty"logid">insert into sys_…

Spring常用的的注解对应xml配置详解

Component(value"")注解&#xff1a;组件 标记在类上&#xff0c;也可以放在接口上注解作用&#xff1a;把AccountDao实现类对象交由Spring IOC容器管理 相当于XML配置文件中的Bean标签 <bean id"userAnnonMapper" class"com.spring.mapper.User…

安卓模拟器bluestacks mac地址修改教程

http://szmars2008.blog.163.com/blog/static/118893702201373181349348/ 转载于:https://www.cnblogs.com/prayer521/p/4069037.html

Docker 搭建 ELK 日志系统,并通过 Kibana 查看日志

Docker 搭建 ELK 日志系统,并通过 Kibana 查看日志 docker-compose.yml version: 3 services:elasticsearch:image: elasticsearch:7.7.0 #镜像container_name: elasticsearch #定义容器名称restart: always #开机启动&#xff0c;失败也会一直重启environment:- "cl…

蟠桃记

Problem Description 喜欢西游记的同学肯定都知道悟空偷吃蟠桃的故事&#xff0c;你们一定都觉得这猴子太闹腾了&#xff0c;其实你们是有所不知&#xff1a;悟空是在研究一个数学问题&#xff01; 什么问题&#xff1f;他研究的问题是蟠桃一共有多少个&#xff01; 不过&#…

Spring 定时任务动态管理

管理 Spring 中定时任务 pom.xml <properties><hutool.version>5.6.6</hutool.version><lombok.version>1.18.20</lombok.version><spring-boot.web.version>2.2.10.RELEASE</spring-boot.web.version> </properties><de…

高效率Oracle SQL语句

1、Where子句中的连接顺序&#xff1a; ORACLE采用自下而上的顺序解析WHERE子句。 根据这个原理&#xff0c;表之间的连接必须写在其他WHERE条件之前&#xff0c; 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾。 举例&#xff1a; (低效) select ... from table1…

RabbitMQ Management:Management API returned status code 500

错误显示&#xff1a; 解决方案&#xff1a; 因为是使用docker 容器安装的&#xff0c;所有需要进入容器 docker exec -it rabbitmq /bin/bash进入目录 cd /etc/rabbitmq/conf.d/执行命令 echo management_agent.disable_metrics_collector false > management_agent.dis…

Android JNI和NDK学习(5)--JNI分析API

Java类型和本地类型对应 在如下情况下&#xff0c;需要在本地方法中应用java对象的引用&#xff0c;就会用到类型之间的转换&#xff1a; java方法里面将参数传入本地方法&#xff1b;在本地方法里面创建java对象&#xff1b;在本地方法里面return结果给java程序。Java基本类型…

RabbitMq 消费失败,重试机制

方案一&#xff1a; 本地消息表 定时任务 本地消息表&#xff1a;主要用于存储 业务数据、交换机、队列、路由、次数 定时任务&#xff1a;定时扫描本地消息表&#xff0c;重新给业务队列投递消息。 具体思路&#xff1a;业务队列消费失败时&#xff0c;把 业务数据、交换机、…

Android常用的工具类

主要介绍总结的Android开发中常用的工具类&#xff0c;大部分同样适用于Java。目前包括HttpUtils、DownloadManagerPro、ShellUtils、PackageUtils、 PreferencesUtils、JSONUtils、FileUtils、ResourceUtils、StringUtils、 ParcelUtils、RandomUtils、ArrayUtils、ImageUtils…

0. Spring 基础

BeanDefinition BeanDefinition 表示 Bean 定义&#xff1a; Spring根据BeanDefinition来创建Bean对象&#xff1b;BeanDefinition有很多的属性用来描述Bean&#xff1b;BeanDefiniton是Spring中非常核心的概念。BeanDefiniton中重要的属性&#xff1a; a. beanClass&#xf…

1. Spring 源码:Spring 解析XML 配置文件,获得 Bena 的定义信息

通过 Debug 运行 XmlBeanDefinitionReaderTests 类的 withFreshInputStream() 的方法&#xff0c;调试 Spring 解析 XML 配置文件&#xff0c;获得 Bean 的定义。 大体流程可根据序号查看&#xff0c;xml 配置文件随便看一眼&#xff0c;不用过多在意。 <?xml version&qu…

c++ 读取文件 最后一行读取了两次

用ifstream的eof()&#xff0c;竟然读到文件最后了&#xff0c;判断eof还为false。网上查找资料后&#xff0c;终于解决这个问题。 参照文件&#xff1a;http://tuhao.blogbus.com/logs/21306687.html 在使用C/C读文件的时候&#xff0c;一定都使用过eof&#xff08;&#xff0…

java中的io系统详解(转)

Java 流在处理上分为字符流和字节流。字符流处理的单元为 2 个字节的 Unicode 字符&#xff0c;分别操作字符、字符数组或字符串&#xff0c;而字节流处理单元为 1 个字节&#xff0c;操作字节和字节数组。 Java 内用 Unicode 编码存储字符&#xff0c;字符流处理类负责将外部的…

js获取字符串最后一个字符代码

方法一&#xff1a;运用String对象下的charAt方法 charAt() 方法可返回指定位置的字符。 代码如下 复制代码 str.charAt(str.length – 1) 请注意&#xff0c;JavaScript 并没有一种有别于字符串类型的字符数据类型&#xff0c;所以返回的字符是长度为 1 的字符串 方法二&#…

Unity3D Shader入门指南(二)

关于本系列 这是Unity3D Shader入门指南系列的第二篇&#xff0c;本系列面向的对象是新接触Shader开发的Unity3D使用者&#xff0c;因为我本身自己也是Shader初学者&#xff0c;因此可能会存在错误或者疏漏&#xff0c;如果您在Shader开发上有所心得&#xff0c;很欢迎并恳请您…