新建线程

新建线程很简单。只要使用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() 的调用就好像再说,我已经完成了一些最重要的工作了,可以休息一下了,可以给其他线程一些工作机会!

《Java 高并发》04 线程的基本操作相关推荐

  1. Java高并发入门-线程初步

    Java高并发入门-线程初步 线程与进程之间的关系 进程就是我们运行在计算机上的一个程序,对应Java程序来说就是运行在计算机上的Java应用程序,这个程序在运行的时候就会创建了一个进程,服务器上就会 ...

  2. Java高并发入门-线程初步(二)

    Java高并发入门详细讲解 上期回顾及问题总结 上次说了创建线程的两种常用方式,第三种方式在后面的更新中会讲解到.这里对于上一节的内容做个回顾. 在上一节中说到了创建多线程的问题,分析了Thread的 ...

  3. java queue 线程安全_详解Java高并发——设计线程安全的类

    前言: 将现有的线程安全的组件组合为更大规模的组件或程序. 通过使用封装技术可以使得在不对整个程序进行分析的情况下就可以判断一个类是否是线程安全的. 一. 基本要素 1. 找出对象状态的所有变量 如果 ...

  4. java高并发系列 - 第6天:线程的基本操作,必备技能

    新建线程 新建线程很简单.只需要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可. Thread thread1 = new Thread1(); t1.start(); 那么 ...

  5. Java高并发编程详解系列-Java线程入门

    根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点.对于代码示例会以Maven工程的形式分享到个人的GitHub上面.   首先介绍一下这个系列的东西是什么,这个系列自己 ...

  6. [Java高并发系列(5)][详细]Java中线程池(1)--基本概念介绍

    1 Java中线程池概述 1.1 什么是线程池? 在一个应用当中, 我们往往需要多次使用线程, 这意味着我们需要多次创建和销毁线程.那么为什么不提供一个机制或概念来管理这些线程呢? 该创建的时候创建, ...

  7. cpu高 thread vm_阿里大佬总结,Java高并发必读!

    作者:wxdoop 原文:https://blog.csdn.net/qq_36235098 来源:前程有光 前言 进程是计算机中程序关于某几何数据集合上的一次运行活动,是系统进行资源分配和调度的基本 ...

  8. java高并发系列 - 第1天:必须知道的几个概念

    java高并发系列-第1天:必须知道的几个概念 同步(Synchronous)和异步(Asynchronous) 同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后, ...

  9. Java高并发程序设计入门

    转自:http://blog.csdn.net/johnstrive/article/details/50667557 说在前面 本文绝大部分参考<JAVA高并发程序设计>,类似读书笔记和 ...

  10. 《实战Java高并发程序设计》github笔记和源码

    笔记 <实战Java高并发程序设计>中有很多代码范例,适合初学者通过实践入门并发编程,这本书有个问题就是前面的代码都用JDK7,第六章开始又用JDK8了 笔者做了相关笔记并整理源代码,欢迎 ...

最新文章

  1. vivox21升级鸿蒙,vivo X21i相机规格再升级,加持AI成又一拍照神器
  2. # JavaScript中的执行上下文和队列(栈)的关系?
  3. 爬去哪儿网5A景点评论
  4. 阿里云 mysql 超时_mysql数据库超时
  5. 机器学习:利用卷积神经网络实现图像风格迁移 (一)
  6. 性能提升一个数量级,大杀器来了!| 文内福利
  7. Django的MEDIA_ROOT和STATIC_ROOT
  8. 介绍Angular的注入服务
  9. zipkin实战(python)
  10. java逆向框架_JOOQ框架学习(1):逆向编译生成代码
  11. JAVA王思聪吃热狗程序_王思聪申请“吃热狗”专利,“吃热狗”已经要付费啦...
  12. get请求中文乱码问题解决
  13. 代购类网站商品高清晰大图片(1000x1000)的采集解决方案 - hackercai - 博客园
  14. 漂亮的CSS背景颜色
  15. 机器学习实例-决策树和随机森林预测员工离职率
  16. 云计算未来的新方向会是“Sky Computing”吗?
  17. 微信小程序招聘管理系统+后台管理系统
  18. mysql中括号_手把手教你看MySQL官方文档
  19. 学业竞技实业网址窗口
  20. 如何隐藏微信内置底部前进后退的按钮?

热门文章

  1. Yolov5目标检测模型运行遇到的相关问题汇总
  2. html怎么使图片无法另存为,如何禁止图片另存为?禁止网页另存为到本地的方法...
  3. 杀死应用进程 android,如何杀死Android应用程序启动的logcat进程?
  4. python打包工具报错_python打包生成exe报错
  5. Git回滚操作的总结
  6. java 8 新特性之日期-时间 API
  7. package.json---入门说明
  8. Qt使用信号与槽时出现的错误“Incompatible sender/receiver arguments”
  9. 查看apk包名和Activity的方法
  10. 蒙提霍尔悖论(三门问题)终极分析(补充)附完整源码