2.1 有关线程你必须知道的事

  • 进程是系统进行资源分配和调度的基本单位,是程序的基本执行实体。

  • 线程就是轻量级进程,是程序执行的最小单位。

  • 线程的生命周期,如图2.3所示。

  • 线程的所有状态都在Thread中的State枚举中定义,如下所示:

public enum State {
NEW,
RUNABLE,
BLOKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
  • NEW状态表示刚刚创建的线程,这种线程还没有开始执行。等到线程的start()方法调用时,才表示线程开始执行。当线程执行时,处于RUNNABLE状态,表示线程所需的一切资源都已经准备好了。如果线程在 执行过程中遇到了synchronized同步块,就会进入BLOCKED阻塞状态,这时线程就会暂停执行,直到获得请求的锁。WAITING和TIMED_WAINTING都表示等待状态,它们的区别是WAITING会进入一个无时间限制的等待,TIMED_WAINTING会进入一个有时限的等待。那等待的线程究竟在等什么呢?一般来说,WAINTING的线程正是在等待一个特殊的事件。比如,通过wait()方法等待的线程在等待notify()方法,而通过join()方法等待的线程则会等待目标线程的终止。一旦等到了期望的事件,线程就会再次执行,进入RUNNABLE状态。当线程执行完毕后,则进入TERMINATED状态,表示结束。

2.2 初始线程:线程的基本操作

2.2.1 新建线程

  • 只要使用new关键字创建一个线程对象,并且将它start()起来即可。
Thread t1 = new Thread();
t1.start();
  • 线程Thread,有一个run()方法,start()方法就会新建一个线程并让这个线程执行run()方法。
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("Hello, I am t1");
}
};
t1.start();
  • 上述代码使用匿名内部类,重载了run()方法。
  • 考虑Java是单继承的,也就是说继承本身也是一种很宝贵的资源,因此,我们也可以使用Runnable接口来实现同样的操作。Runable接口是一个单方法接口,它只有一个run()方法:
public interface Runnable {
public abstract void run();
}
  • Thread类有一个非常重要的构造方法:
public Thread(Runnable target)
public class T1 implements Runnable {
public static void main(String[] args) {
Thread t1 = new Thread(new T1());
t1.start();
} @Override
public void run() {
System.out.println("Oh, I am Runnable");
}
}

2.2.2 终止线程

  • Thread.stop()方法在结束线程时,会直接终止线程,并且会立即释放这个线程所持有的锁。而这些锁恰恰是用来维持对象一致性的。如果此时,写线程写入数据正写到一半,并强行终止,那么对象就会 被写坏,同时,由于锁已经被释放,另外一个等待该锁的读线程就顺理成章的读到了这个不一致的对象,悲剧也就此发生。整个过程如图2.4所示。

  • 可以自行决定线程何时退出,如下所示:

public static class ChangeObjectThread extends Thread {
volatile boolean stopme = false; public void stopMe() {
stopme = true;
} @Override
public void run() {
while (true) {
if (stopme) {
System.out.println("exit by stop me");
break;
}
synchronized (u) {
int v = (int) (System.currentTimeMillis() / 1000);
u.setId(v); try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setName(String.valueOf(v));
}
Thread.yield();
}
}
}

2.2.3 线程中断

  • 与线程中断有关的三个方法:
public void Thread.interrupt()  //中断线程,设置中断标志位
public boolean Thread.isInterrupted() //判断是否被中断(通过检查中断标志位)
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
  • 下面这段代码对t1线程进行了中断,那么中断后,t1会停止执行吗?
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
  • 在这里,虽然对t1进行了中断,但是在t1中并没有中断处理的逻辑,因此,即使t1线程被置上中断状态,但是这个中断不会发生任何作用。
  • 如果希望t1在中断后退出,就必须为它增加相应的中断处理代码:
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interruted");
break;
}
Thread.yield();
}
}
}
  • 下面,先来了解一下Thread.sleep()函数,它的签名如下:
public static native void sleep(long millis) throws InterruptedException
  • Thread.sleep()方法会让当前线程休眠若干时间,它会抛出一个InterruptException中断异常。InterruptedException不是运行时异常,也就是说程序必须捕获并且处理它,当线程在sleep()休眠时,如果被中断,这个异常就会产生。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted!");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interrupted When Sleep");
//设置中断状态
Thread.currentThread().interrupt();
}
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
  • 执行Thread.sleep(),线程被中断,则程序会抛出异常,进入catch中,但没有立即退出线程。因为还要进行后续的处理,保证数据的一致性和完整性,因此,执行了Thread.interrupt()方法再次中断自己,置上中断标记位。只有这样做,在Thread.isInterrupted()检查中,才能发现当前线程已经被中断。
  • 注意:Thread.sleep()方法会清除中断标记,故再次设置中断标记位。

2.2.4 等待(wait)和通知(notify)

  • 任何对象都可以调用这两个方法。
public final void wait() throws InterruptedException
public final native void notify()
  • 当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。一直等到其他线程调用了该对象的notify()方法为止。

  • 图2.5展示了两者的工作过程。

  • 强调一点,Object.wait()方法必须包含在对应的synchronized语句中,无论是wait()或者notify都需要首先获得目标对象的一个监视器。如图2.6所示,显示了wait()和notify()的工作流程细节。

  • 为了方便大家理解,这里给出了一个简单地使用wait()和notify()的案例:

public class SimpleWN {
final static Object object = new Object();
public static class T1 extends Thread {
public 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();
}
}
}
}
public static class T2 extends Thread {
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) { }
}
} public static void main(String[] args) {
Thread t1 = new T1();
Thread t2 = new T2();
t1.start();
t2.start();
}
}
  • 上述代码中,开启了两个线程T1和T2。T1执行了object.wait()方法。在T1中执行wait()方法前,T1先申请object的对象锁。因此,在执行object.wait()时,它是持有object的锁的。wait()方法执行后,T1会进行等待,并释放object的锁。T2在执行notify()之前也会获得object的对象锁。这里为了让实验效果明显,特意安排在notify()通知后,让T2休眠2秒钟,这样做可以更明显地说明,T1得到notify()通知后,还是会先尝试重新得到object的对象锁。
T1 start!
T1 wait for object
T2 start! notify one thread
T2 end!
T1 end!
  • 注意:Object.wait()和Thread.sleep()方法都可以让线程等待若干时间。除了wait()可以被唤醒外,另外一个主要的区别是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源。

2.2.5 挂起(suspend)和继续执行(resume)线程

  • 这两个操作是一对相反的操作,被挂起的线程,必须要等到resume()操作后,才能继续指定。

  • 不推荐使用suspend()去挂起线程的原因,是因为suspend()在导致线程暂停的同时,并不会去释放任何锁资源。此时,其他任何线程想要访问被它暂用的锁时,都会被牵连,导致无法正常运行(如图2.7所示)。直到对应的线程上进行了resume()操作,被挂起的线程才能继续,从而其他所有阻塞在相 关锁上的线程也可以继续执行。但是,如果resume()操作意外地在suspend()前就执行了,那么被挂起的线程可能很难有机会被继续执行。并且,更严重的是:它所占用的锁不会被释放,因此可能会导致整个系统工作不正常。而且,对于被挂起的线程,从它的线程状态上看,居然还是Runnable。

  • 为了方便大家理解suspend()的问题,这里准备一个简单的程序。演示了这种情况:

public class BadSuspend() {
public static Object u = new Object();
static ChangeOjectThread t1 = new ChangeObjectThread("t1");
static ChangeOjectThread t2 = new ChangeObjectThread("t2"); public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
@Override
public void run() {
synchronized (u) {
System.out.println("in " + getName);
Thread.currentThread().suspend();
}
}
} public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
t1.resume();
t2.resume();
t1.join();
t2.join();
}
}
 in t1
in t2

2.2.6 等待线程结束(join)和谦让(yield)

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
  • 第一个join()方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。第二个方法给出了最大等待时间,如果超出给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。
public class JoinMain {
public volatile static int i = 0;
public static class AddThread extends Thread {
@Override
public void run() {
for (i = 0; i < 10000000; i++);
}
}
public static void main(String[] args) throws InterruptedException {
AddThread at = new AddThread();
at.start();
at.join();
System.out.println(i);
}
}
  • 主函数中,如果不使用join()等待AddThread,那么得到的i很可能是0或者一个非常小的数字。因为AddThread还没开始执行,i的值就已经被输出了。但在使用join()方法后,表示主线程愿意等待AddThread执行完毕,故在join()返回时,AddThread已经执行完毕,故i总是10000000。
  • join()的本质是让调用线程wait()在当前线程对象实例上。
while (isAlive()) {
wait(0);
}
  • 可以看到,它让调用线程在当前线程对象上进行等待。
  • 另外一个有趣的方法,是Thread.yield(),它的定义如下:
public static native void yield();
  • 这是一个静态方法,一旦执行,它会使当前线程让出CPU。
  • 如果你觉得一个线程不那么重要,或者优先级非常低,而且又害怕它会占用太多的CPU资源,那么可以在适当的时候调用Thread.yield()。

第2章 Java并行程序基础(一)的更多相关文章

  1. 第2章 Java并行程序基础(三)

    2.8 程序中的幽灵:隐蔽的错误 2.8.1 无提示的错误案例 以求两个整数的平均值为例.请看下面代码: int v1 = 1073741827; int v2 = 1431655768; Syste ...

  2. 第2章 Java并行程序基础(二)

    2.3 volatile 与 Java 内存模型(JMM) volatile对于保证操作的原子性是由非常大的帮助的(可见性).但是需要注意的是,volatile并不能代替锁,它也无法保证一些复合操作的 ...

  3. Java并发程序设计(二)Java并行程序基础

    Java并行程序基础 一.线程的生命周期 其中blocked和waiting的区别: 作者:赵老师链接:https://www.zhihu.com/question/27654579/answer/1 ...

  4. JAVA并行程序基础

    JAVA并行程序基础 一.有关线程你必须知道的事 进程与线程 在等待面向线程设计的计算机结构中,进程是线程的容器.我们都知道,程序是对于指令.数据及其组织形式的描述,而进程是程序的实体. 线程是轻量级 ...

  5. JAVA并行程序基础二

    JAVA并行程序基础二 线程组 当一个系统中,如果线程较多并且功能分配比较明确,可以将相同功能的线程放入同一个线程组里. activeCount()可获得活动线程的总数,由于线程是动态的只能获取一个估 ...

  6. JAVA并行程序基础一

    JAVA并行程序基础一 线程的状态 初始线程:线程的基本操作 1. 新建线程 新建线程只需要使用new关键字创建一个线程对象,并且用start() ,线程start()之后会执行run()方法 不要直 ...

  7. Java并行程序基础。

    并发,就是用多个执行器(线程)来完成一个任务(大任务)来处理业务(提高效率)的方法.而在这个过程中,会涉及到一些问题,所以学的就是解决这些问题的方法. 线程的基本操作: 1.创建线程:只需要new一个 ...

  8. 到头来还是逃不开Java - Java13程序基础

    java程序基础 没有特殊说明,我的所有学习笔记都是从廖老师那里摘抄过来的,侵删 引言 兜兜转转到了大四,学过了C,C++,C#,Java,Python,学一门丢一门,到了最后还是要把Java捡起来. ...

  9. Spring MVC + Spring + Mybitis开发Java Web程序基础

    Spring MVC + Spring + Mybitis是除了SSH外的另外一种常见的web框架组合. Java web开发和普通的Java应用程序开发是不太一样的,下面是一个Java web开发在 ...

随机推荐

  1. spring boot介绍

    spring boot简介 1.spring boot是spring家族中的一个全新的框架,它用来简化spring应用程序的创建和开发过程,也可以说spring boot能简化我们之前采用ssm框架进 ...

  2. ACM北大暑期课培训第四天

    今天讲了几个高级搜索算法:A* ,迭代加深,Alpha-Beta剪枝   以及线段树 A*算法 启发式搜索算法(A算法) : 在BFS算法中,若对每个状态n都设定估价函数 f(n)=g(n)+h(n) ...

  3. docker发布.net core程序的坑

    docker发布遇到的两个问题 1:Could not resolve CoreCLR path. For more details, enable tracing by setting COREHO ...

  4. TensorFlow——批量归一化操作

    批量归一化 在对神经网络的优化方法中,有一种使用十分广泛的方法——批量归一化,使得神经网络的识别准确度得到了极大的提升. 在网络的前向计算过程中,当输出的数据不再同一分布时,可能会使得loss的值非常 ...

  5. 【开源】后台权限管理系统升级到aspnetcore3.1

    *:first-child { margin-top: 0 !important; } .markdown-body>*:last-child { margin-bottom: 0 !impor ...

  6. 区间dp - codeforces

    题意 : 给你 n 个数字,相邻的数字如果相同,则代表他们是一个块的,每次操作可以将一个块的数字变成任意一种数字,求最小操作次数,将整个区间的所有数字变成相同的 思路分析 : 定义 dp[i][j][ ...

  7. scratch3.0二次开发scratch3.0基本介绍(第一章)

    为什么要自己开发而不使用官方版本? 这个问题要看我们的做少儿编程教育的需求是怎么样的. scratch本身提供了离线版本以及官网在线平台供我们使用,这足以满足我们对于编程教学模块的需求.但是对于一些教 ...

  8. ORM基础5

    一.一对一 场景:字段多,且一部分字段使用率高 优点:提高效率 实质:唯一的外键 # Person表 class Person(models.Model): id = models.AutoField ...

  9. BigInteger的权限设计

    通过储存菜单权限的一个字段(id自定义也是可以的) 1 将选中菜单树的id转换成字符数组的形式, 进行BigInteger对权限进行2的权的和计算 public static BigInteger s ...

  10. .net core webapi搭建(3)Code first+拆层三层+仓储

    将项目拆层 我们要 将项目拆分成 Infrastructure     基础层 Core                   核心层 Utility                  工具 我们想在就 ...