java生命周期、线程通讯

一、生命周期

有关线程生命周期就要看下面这张图,围绕这张图讲解它的方法的含义,和不同方法间的区别。

   1、yield()方法

yield()让当前正在运行的线程回到就绪,以允许具有相同优先级的其他线程获得运行的机会。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

同时yield()不会放弃锁资源,所以有可能会出现死锁。

   2、wait和sleep方法的区别

1)第一个很重要的区别就是,wait方法必须正在同步环境下使用,比如synchronized方法或者同步代码块。如果你不在同步条件下使用,会抛出IllegalMonitorStateException异常。另外,sleep方法不需要再同步条件下调用,你可以任意正常的使用。

2)第二个区别是,wait方法用于和定义于Object类的,而sleep方法操作于当前线程,定义在java.lang.Thread类里面。

3)第三个区别是,调用wait()的时候方法会释放当前持有的锁,而sleep方法不会释放任何锁。

   3、wait和sleep方法使用场景

(1)wait方法定义在Object类里面,所有对象都能用到,一般wait()和notify()方法或notifyAll使用于线程间的通信。

(2)sleep()方法用于暂停当前线程的执行。

   4、join方法()

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。

比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

这里有个待思考的案例?应该是自己对join没有理解透,留在以后再来回顾。

/*
* 有关join第一个疑惑就是我在t2.join()后,发现它是无效的,同样还是交叉输出。
*/
class ThreadTesterA implements Runnable { private int counter; public void run() {
while (counter <= 10) {
System.out.print("Counter = " + counter + " ");
counter++;
}
System.out.println();
}
}
class ThreadTesterB implements Runnable {
private int i;
public void run() {
while (i <= 10) {
System.out.print("i = " + i + " ");
i++;
}
System.out.println();
}
} public class ThreadTester {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new ThreadTesterA());
Thread t2 = new Thread(new ThreadTesterB());
t1.start();
t2.start();
t2.join(); //无效
}
}

5、stop方法

线程启动完毕后,在运行可能需要终止,Java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题:

1)stop方法是过时的。

从Java编码规则来说,已经过时的方式不建议采用.

2)stop方法会导致代码逻辑不完整

stop方法是一种"恶意" 的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的.

3)stop方法会破坏原子逻辑

多线程为了解决共享资源抢占的问题,使用了锁的概念,避免资源不同步,但是正是因为此原因,stop方法却会带来更大的麻烦,它会丢弃所有的锁,导致原子逻辑受损

二、线程通讯小案例

1、如何让两个线程依次执行?

题目:假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们希望 B 在 A 全部打印完后再开始打印。

关键方法:join()

//题目:假设有两个线程,一个是线程 A,另一个是线程 B,我们希望 B 在 A 全部打印完后再开始打印。
public class TestJoin {
public static void main(String[] args) {
demo2();
} private static void demo2() {
Thread A = new Thread(new Runnable() {
@Override
public void run() {
printNumber("A");
}
});
Thread B = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("B 开始等待 A");
try {
A.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
printNumber("B");
}
});
B.start();
A.start();
} private static void printNumber(String threadName) {
int i=0;
while (i++ < 3) {
System.out.println(threadName + "print:" + i);
}
}
}
/*运行结果
* B 开始等待 A
* Aprint:1
* Aprint:2
* Aprint:3
* Bprint:1
* Bprint:2
* Bprint:3
*/

2、如何让两个线程按照指定方式有序交叉运行呢?

题目:假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们希望 A和B交替打印

关键方法:wait()和notify()或者notifyAll()

public class Main {
int i = 1; //i和istrue作为多线程的共享数据
boolean istrue = false; public static void main(String[] args) {
Main main = new Main();
ThreadA a = new ThreadA(main);
ThreadB b = new ThreadB(main);
Thread threada = new Thread(a);
Thread threadb = new Thread(b);
threada.start();
threadb.start(); }} class ThreadA implements Runnable {
Main main; public ThreadA(Main main) {
this.main = main;
} public void run() {
while (main.i <= 10) {
synchronized (main) { // 必须要用一把锁对象,这个对象是main
if (!main.istrue) {
try {
main.wait(); // 操作wait()函数的必须和锁是同一个
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("奇数:" + main.i);
main.i++;
main.istrue = false;
main.notifyAll();
}
}}}} class ThreadB implements Runnable {
Main main; public ThreadB(Main main) {
this.main = main;
} public void run() {
while (main.i <= 10) {
synchronized (main) { // 必须要用一把锁对象,这个对象是main
if (main.istrue) {
try {
main.wait(); // 操作wait()函数的必须和锁是同一个
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("偶数:" + main.i);
main.i++;
main.istrue = true;
main.notifyAll();
}
}}}} //梳理下流程
//首先传入一个 A 和 B 共享的对象锁main;
//当 A 得到锁后,直接交出锁的控制权,进入 wait 状态;
//对 B 而言,由于 A 最开始得到了锁,导致 B 无法执行;直到 A 调用wait() 释放控制权后, B 才得到了锁,同时输出:偶数:1,同时notifyAll让A又到就绪状态
//接下来A和B都有可能获得cpu时间碎片,当 A 得到锁后,那么打印奇数:2,如果B又获得cpu时间片,那么它会进入wait状态。
//就这样来去循环,最终就是交叉打印运行。

运行结果

3、四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的。

关键对象:CountdownLatch对象

最开始我们介绍了 thread.join(),可以让一个线程等另一个线程运行完毕后再继续执行,那我们可以在 D 线程里依次 join A B C,不过这也就使得 A B C 必须依次执行,而我们要的是这三者能同步运行。
或者说,我们希望达到的目的是:A B C 三个线程同时运行,各自独立运行完后通知 D;对 D 而言,只要 A B C 都运行完了,D 再开始运行。针对这种情况,我们可以利用 CountdownLatch 来实现这类通信方式。

/*CountdownLatch基本用法是:
* 1)创建一个计数器,设置初始值,CountdownLatch countDownLatch = new CountDownLatch(3);
* 2)在 等待线程 里调用 countDownLatch.await() 方法,进入等待状态,直到计数值变成 0;
* 3)在 其他线程 里,调用 countDownLatch.countDown() 方法,该方法会将计数值减小 1;
* 4)当 其他线程 的 countDown() 方法把计数值变成 0 时,等待线程 里的 countDownLatch.await() 立即退出,继续执行下面的代码。
*/ public class TestCountdownLatch { public static void main(String[] args) {
runDAfterABC();
} private static void runDAfterABC() {
int worker = 3;
CountDownLatch countDownLatch = new CountDownLatch(worker);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("D开始工作前先等ABC工作完成");
try { //因为worker初始值为3,所以在不等于0之前一直处于等待状态
countDownLatch.await();
System.out.println("ABC工作完成,D开始工作");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
for (char threadName='A'; threadName <= 'C'; threadName++) {
final String tN = String.valueOf(threadName);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(tN + "正在工作.....");
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(tN + "完成工作......"); //每调用一次worker值减一
countDownLatch.countDown();
}
}).start();
}
}
}

运行结果:

4、三个运动员各自准备,等到三个人都准备好后,再一起跑

关键对象:CyclicBarrier

上面的 CountDownLatch 可以用来倒计数,但当计数完毕,只有一个线程的 await() 会得到响应,无法让多个线程同时触发。

为了实现线程间互相等待这种需求,我们可以利用 CyclicBarrier 数据结构。

/* CyclicBarrier 基本用法
* 1)先创建一个公共 CyclicBarrier 对象,设置 同时等待 的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
* 2)这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用 cyclicBarrier.await(); 即可开始等待别人;
* 3)当指定的 同时等待 的线程数都调用了 cyclicBarrier.await();时,意味着这些线程都准备完毕好,然后这些线程才 同时继续执行。
*/
public class CyclicBarrierTest { public static void main(String[] args) {
runABCWhenAllReady();
} private static void runABCWhenAllReady() {
int runner = 3;
CyclicBarrier cyclicBarrier = new CyclicBarrier(runner); for (char runnerName='A'; runnerName <= 'C'; runnerName++) {
final String rN = String.valueOf(runnerName);
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(rN + " 已经准备好,等待其它线程准备");
cyclicBarrier.await(); // 当前运动员准备完毕,等待别人准备好
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(rN + "开始跑"); // 所有运动员都准备好了,一起开始跑
}
}).start();
}
}
}

运行结果:

 5、子线程完成某件任务后,把得到的结果回传给主线程

关键接口:Callable

public class CallableTest {

    public static void main(String[] args) {
doTaskWithResultInWorker();
} private static void doTaskWithResultInWorker() {
//看出 Callable 最大区别就是返回范型 V 结果
Callable<Integer> callable = new Callable<Integer>() { //这里需要重写call方法,而不是run方法
@Override
public Integer call() throws Exception {
System.out.println("Task starts");
Thread.sleep(1000);
int result = 0;
for (int i=0; i<=100; i++) {
result += i;
}
return result;
}
};
//Callable需要把对象放入FutureTask对象中,在把FutureTask对象放入Thread中,就可以启动一个线程
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
try {
System.out.println("Result: " + futureTask.get());
} catch (Exception e) {
e.printStackTrace();
} }
}
/*输出结果:
* Task starts
* Result: 5050
*/

这里我们可以学到,通过 FutureTask 和 Callable 可以直接在主线程获得子线程的运算结果,只不过需要阻塞主线程。当然,如果不希望阻塞主线程,可以考虑利用 ExecutorService,把 FutureTask 放到线程池去管理执行。

想太多,做太少,中间的落差就是烦恼。想没有烦恼,要么别想,要么多做。少校【8】

java多线程(2)---生命周期、线程通讯的更多相关文章

  1. Java多线程:生命周期,实现与调度

    Java线程生命周期 Java线程实现方法 继承Thread类,重写run()方法 实现Runnable接口,便于继承其他类 Callable类替换Runnable类,实现返回值 Future接口对任 ...

  2. java多线程(五)线程通讯

    1.1. 为什么要线程通信 多个线程并发执行时,在默认情况下CPU是随机切换线程的,有时我们希望CPU按我们的规律执行线程,此时就需要线程之间协调通信. 1.2. 线程通讯方式 线程间通信常用方式如下 ...

  3. Java多线程(五)线程的生命周期

    点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...

  4. [转]JAVA虚拟机的生命周期

    JAVA虚拟机体系结构 JAVA虚拟机的生命周期 一个运行时的Java虚拟机实例的天职是:负责运行一个java程序.当启动一个Java程序时,一个虚拟机实例也就诞生了.当该程序关闭退出,这个虚拟机实例 ...

  5. Java多线程(一) —— 线程的状态详解

    一.多线程概述  1. 进程 是一个正在执行的程序.是程序在计算机上的一次运行活动. 每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控制单元. 系统以进程为基本单位进行系统资源的调度 ...

  6. Java 对象的生命周期

    Java对象的生命周期 在Java中,对象的生命周期包含下面几个阶段: 1.      创建阶段(Created) 2.      应用阶段(In Use) 3.      不可见阶段(Invisib ...

  7. Java虚拟机(三)垃圾标记算法与Java对象的生命周期

    前言 这一节我们来简单的介绍垃圾收集器,并学习垃圾标记的算法:引用计数算法和根搜索算法,为了更好的理解根搜索算法,会在文章的最后介绍Java对象在虚拟机中的生命周期. 1.垃圾收集器概述 垃圾收集器( ...

  8. JVM类加载器及Java类的生命周期

    预定义类加载器(三种): 启动(Bootstrap)类加载器: 是用本地代码实现的类装入器,它负责将<Java_Runtime_Home>/lib下面的类库加载到内存中(比如rt.jar) ...

  9. Java对象的生命周期与作用域的讨论(转)

    导读: Java对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除.因此,对象的生命周期长度可用如下的表达式表示:T = T1 + T2 +T3.其中T1表示对象的创建时间,T2表示对 ...

  10. 【转载】Java对象的生命周期

    Java对象的生命周期 在Java中,对象的生命周期包括以下几个阶段: 1.      创建阶段(Created) 2.      应用阶段(In Use) 3.      不可见阶段(Invisib ...

随机推荐

  1. 如何实现Activiti的分支条件的自定义配置(转)

    如何实现Activiti的分支条件的自定义配置 博客分类: Activiti Java SaaS   一.Activiti的流程分支条件的局限 Activiti的流程分支条件目前是采用脚本判断方式,并 ...

  2. svn2个小问题的解决

    Revision file (r615) lacks trailing newline /svndata/your_project/db/revs /svndata/your_project/db/r ...

  3. 解决 ERROR: missing Change-Id in commit message footer 问题

    提交代码操作 git push origin HEAD:refs/for/XXX,提示失败ERROR: missing Change-Id in commit message footer,丢失Cha ...

  4. 别人的Linux私房菜(18)认识系统服务(daemon)

    完成服务service的程序称为daemon.完成计划性的服务程序如crond是一个daemon. 早期的System V的init管理daemon操作中,系统内核首先调用init,然后init运行系 ...

  5. 数组,arrayList和List

    数组,arrayList和List (1)数组在C#中是最早出现的.它在内存中是连续的存储的,所以索引速度很快,而且赋值与修改元素也很简单.可以利用偏移地址访问元素,时间复杂度为O(1);可以用折半查 ...

  6. JPA学习-03

    一.单向一对多 特点:性能差,不怎么用,有集合默认懒加载 @OneToMany 1.private Set<T> t = new HashSet<>() 2.private L ...

  7. 2019.03.29 NOIP训练 友好国度(点分治+容斥)

    传送门 思路: 直接上点分治+容斥计算每个因数对应的贡献即可. 代码: #include<bits/stdc++.h> #define ri register int using name ...

  8. 15. Life Cycle of the Products 产品的生命周期

    15. Life Cycle of the Products 产品的生命周期 (1) We can see how the product life cycle works by looking at ...

  9. Python_day8

    多态 class Animal(object): def run(self): print('animal is running') class Dog(Animal): def run(self): ...

  10. 洛谷 质因子分 p2043

    #include <iostream>#include <algorithm>#include <cstring>using namespace std; cons ...