JUC并发编程系列文章
http://t.csdn.cn/UgzQi
文章目录
前言
一、创建和运行线程
1、使用匿名内部类的方式创建线程
使用匿名内部类的方式创建线程
: 创建线程后要重写 run 方法,但是这时任务调度器并不能调度到这个线程,需要调用该线程的 start( ) 方法才行。
2、使用Runnable配合Thread创建线程
Runnable还是一个函数式接口,标有 @FunctionalInterface 注解
使用Lambda表达式简化Runnable接口创建
package tanchishell.JUC;
public class testThread {
public static void main(String[] args) {
Runnable runnable = test();
Thread thread = new Thread(runnable,"nihao");
thread.start();
//main方法的线程信息
long id = Thread.currentThread().getId();
String name = Thread.currentThread().getName();
System.out.println(id+"---"+name);
//thread的线程信息
long id1 = thread.getId();
String name1 = thread.getName();
System.out.println(id1+"---"+name1);
}
public static Runnable test(){
Runnable runnable = () -> System.out.println("线程被调用了");
return runnable;
}
}
走进Runnable配合Thread创建线程底层源码
进入有参构造器
有参构造获取到Runnable接口和我们传入的线程名字,并调用了 init 初始化方法
init 方法里面又调用了 init 方法,但是这两个 init 方法的形参列表是不同的。
会先判断我们传入的线程名字是否为空,为空抛出异常。我们接着找 传入的 Runnabl接口。
把我们传入的 Runnable接口给了当前对象的成员变量。
成员变量是一个 Runnable接口。target现在是非空的了。
找到run方法,先判断我们是否传入 Runnable 接口,这里条件肯定成立,然后调用 run 方法执行我们重写后的 run 方法。
3、使用FutureTask 配合Thread创建线程
FutureTask可以获取到线程任务的执行结果,实现了RunnableFuture接口,而RunnableFuture接口又继承了 Runnable接口和Futrue接口,Future接口里面有get( )方法可以获取到线程任务的执行结果,Runnable接口只有一个 run 方法而且没有返回值,所以Runnable接口不能让两个线程之间进行通信,这时只能借助 Future接口来实现线程之间的通信,将某一个线程的结果返回,供其他线程使用。
Future接口可以接收Callable接口类型的参数,Callable接口也是一个函数式接口,和Runnable不同的是它的 call()方法有返回值,并且可以抛出异常。
代码示例
package tanchishell.JUC;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class testThread02 {
public static void main(String[] args) throws Exception{
FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(10000);
return 100;
}
});
//启动线程 futureTask
Thread thread = new Thread(futureTask);
thread.start();
//执行到get时会阻塞式等待结果的返回
Integer integer = futureTask.get();
System.out.println(integer);
Thread thread2 = new Thread("nihao"){
public void run(){
int i = integer + 1;
System.out.println("启动第二个线程打印futureTask的结果是"+integer+"加1等于"+i);
}
};
//启动第二个线程 thread2
thread2.start();
//主线程打印返回结果
System.out.println("主线程打印futureTask返回结果是"+integer);
}
}
二、线程的运行原理
观察多个线程同时运行
三、查看进程线程的方法
四、线程的运行原理
1、多线程的运行原理
虽然是两个线程但只是线程的执行顺序不同,但是大致的流程还是和单线程一致
2、线程上下文的切换
五、线程的常用方法
六、start( ) VS run( )
start( )方法才会正在的调用除主线外的一个线程,如果不通过 start( ) 方法调用线程,直接使用 run 方法,是无法达到多线程异步执行来提高效率的,使用 run 方法会在 mian 方法中进行方法的调用。
七、sleep与yield
yield方法被调用会让当前线程从 运行状态 Running,进入Runnable就绪状态,也就是让出抢到的cpu时间片,让其他线程去执行业务逻辑。但是要注意当让出cpu的时间片后如果没有其他线程去抢占cpu的时间片,任务调度器还是会把当前线程再次调度起来,也就造成了想让出cpu资源却让不出去的情况。
sleep的yield最大的区别就是,yield方法被调用后线程仍属于就绪状态 Runnable,还是会去再次争夺cpu资源,而sleep就不同了,它会让当前线程进入阻塞状态,只有睡眠的时间到了才会让线程重新进入 Runnable就绪状态再去抢占cpu时间片。sleep会有一段休眠时间,而yield没有任何的参数。
package tanchishell.JUC;
public class testThread03 {
public static void main(String[] args)throws Exception {
Thread thread1 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread1.start();
//打印 thread1 线程状态,这时 thread1还没有 sleep
System.out.println(thread1.getState()); //RUNNABLE
//等待500毫秒让 thread1进入睡眠
Thread.sleep(500);
//再次打印 thread1线程的状态
System.out.println(thread1.getState()); //TIMED_WAITING
}
}
打断线程的睡眠,会抛出InterruptedException中断异常也不会执行sleep后的业务
package tanchishell.JUC;
public class testThread03 {
public static void main(String[] args)throws Exception {
Thread thread1 = new Thread() {
@Override
public void run() {
try {
//睡眠 10秒之后输出你好
Thread.sleep(10000);
System.out.println("你好");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread1.start();
//打断 thread1 的睡眠,会抛出InterruptedException中断异常也不会输出 你好
thread1.interrupt();
//打印 thread1 线程状态,这时 thread1还没有 sleep
System.out.println(thread1.getState()); //RUNNABLE
//等待500毫秒让 thread1进入睡眠
Thread.sleep(500);
//再次打印 thread1线程的状态
System.out.println(thread1.getState()); //TIMED_WAITING
}
}
线程的优先级
package tanchishell.JUC;
/**
* 使用 yield 让出cpu资源和 使用线程优先级获取 cpu资源的v对比
*/
public class testThread04 {
public static void main(String[] args) {
Runnable runnable1 = new Runnable() {
@Override
public void run() {
int count = 0;
for (int i = 1; i<100;i++){
System.out.println("线程1="+count);
count++;
}
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
int count = 0;
for (int i = 1; i<100;i++){
//让线程 2执行时让出 cpu资源
// Thread.yield();
System.out.println("线程2="+count);
count++;
}
}
};
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
//设置线程的优先级
// thread1.setPriority(Thread.MIN_PRIORITY);
// thread1.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.start();
/**得出结论:
* 当线程1的优先级大于线程2,这时线程2调用yield方法让出,线程1会优先执行
* 当线程1的优先级小于线程2,这时线程2调用yield方法让出,线程2会优先执行
*
* 当线程1的优先级等于线程2,这时线程2调用yield方法让出,线程1会优先执行
* 当线程1的优先级等于线程2,这时线程2没有调用yield方法,两个线程会正常交替执行
*/
}
}
无论是yield还是线程的优先级,它们都不能决定线程的调度,只是给任务调度器一个提示,让任务调度器来根据这个提示进行各个线程的上下文切换。当cpu空闲时这两个家伙的作用都会失效。
案例:防止CPU占用100%
当我们的一个线程卡在那里一直做 死循环会让cpu的占有率极高,这时可以让这个线程睡眠一会,释放cpu资源给其他线程去使用。
八、join( )方法详解
1、为什么使用 join( )
join方法:当我们需要等带另一个线程的执行结果来进行业务逻辑的处理,可以让另外一个线程执行 join 方法,那么当前线程就会等待被调用的线程执行结束后再继续运行。
2、join( ) 应用的同步案例
如果调用方需要等待被调用方的结果返回,就是同步。
如果调用方不需要等待被调用方的结果返回,就是异步
让调用方等待两个被调用方执行完后,拿到结果后再执行
import lombok.extern.slf4j.Slf4j;
/**
* 让调用方等待两个被调用方执行完后,拿到结果后再执行
*/
@Slf4j(topic = "c.testThread01")
public class testThread01 {
static int r = 0;
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws Exception{
log.debug("main方法执行了");
test2();
log.debug("main方法结束了");
}
public static void test2() throws Exception{
Runnable runnable1 = ()->{
log.debug("runnable1开始");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
r1 = 10;
log.debug("runnable1结束");
};
Runnable runnable2 = ()->{
log.debug("runnable2开始");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
r2 = 20;
log.debug("runnable2结束");
};
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
log.debug("r1的结果是="+r1);
log.debug("r2的结果是="+r2);
}
}
有时效的 join( ) 方法
九、interrupt 方法详解
interrupt 可以打断处于阻塞状态的线程,也可以用来打断正在运行的线程,join方法的底层还是一个wait方法。每个线程被打断后都会有一个标记,是一个布尔值,但是当 sleep join wait 被打断时,会将这个标记清空为 false,而当线程正常运行时被打断了这个标记就会为 true。
正在运行的线程调用 thread.interrupt(); 并不会真正被打断,
只是打了一个 标记 在线程上面,如果被标记的线程需要进行停顿,可以通过这个标记进行判断,
如果该线程的打断标记为 true 就表示需要停下来,先放下手中的工作,而去执行另外的业务逻辑。
总之停与不停有线程自己决定
import lombok.extern.slf4j.Slf4j;
/**
* 正在运行的线程调用 thread.interrupt(); 并不会真正被打断
* 只是打了一个 标记 在线程上面,如果被标记的线程需要进行停顿,可以通过这个标记进行判断
* 如果该线程的打断标记为 true 就表示需要停下来,先放下手中的工作,而去执行另外的业务逻辑
*/
@Slf4j(topic = "c.testThread02")
public class testThread02{
public static void main(String[] args) throws Exception {
test();
}
//
public static void test() throws Exception{
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true){
System.out.println("nihao");
log.debug("线程正在运行");
//获取打断标记
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted){
log.debug("收到打断标记,线程停止");
return;
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
//打断当前正在运行的线程
Thread.sleep(100);
log.debug("打断当前正在运行的线程");
thread.interrupt();
}
}
1、设计模式:两阶段终止
两阶段终止设计模式的代码实现
import lombok.extern.slf4j.Slf4j;
/**
* 两阶段终止设计模式的代码实现
*/
@Slf4j(topic = "c.testThread03")
public class testThread03 {
public static void main(String[] args) throws Exception{
TwoStageTermination tst = new TwoStageTermination();
tst.start();
Thread.sleep(5000);
tst.end();
}
}
@Slf4j(topic = "c.TwoStageTermination")
class TwoStageTermination{
private Thread monitor;
public void start(){
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true){
//判断是否被打断
boolean interrupted = monitor.currentThread().isInterrupted();
if (interrupted){
log.debug("线程被打断...料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("没有异常执行监控记录");
} catch (InterruptedException e) {
//睡眠时会将标记清空,再次标记
monitor.interrupt();
e.printStackTrace();
}
}
}
};
monitor = new Thread(runnable,"monitor");
monitor.start();
}
public void end(){
//打断监控
log.debug("打断监控");
monitor.interrupt();
}
}
2、打断 park 线程
十、不推荐使用的方法
十一、守护线程
十二、线程的五种状态(操作系统层面)
十三、线程的六种状态(JavaAPI层面)
十四、本章总结
附:华罗庚《统筹方法》
统筹方法,是一种安排工作进程的数学方法。它的实用范围极广泛,在企业管理和基本建设中,以及关系复 杂的科研项目的组织与管理中,都可以应用。
怎样应用呢?主要是把工序安排好。
比如,想泡壶茶喝。当时的情况是:开水没有;水壶要洗,茶壶、茶杯要洗;火已生了,茶叶也有了。怎么办?
● 办法甲:洗好水壶,灌上凉水,放在火上;在等待水开的时间里,洗茶壶、洗茶杯、拿茶叶;等水开
了,泡茶喝。
● 办法乙:先做好一些准备工作,洗水壶,洗茶壶茶杯,拿茶叶;一切就绪,灌水烧水;坐待水开了,泡茶喝。
● 办法丙:洗净水壶,灌上凉水,放在火上,坐待水开;水开了之后,急急忙忙找茶叶,洗茶壶茶杯,泡茶喝。
哪一种办法省时间?我们能一眼看出,第一种办法好,后两种办法都窝了工。
这是小事,但这是引子,可以引出生产管理等方面有用的方法来。
水壶不洗,不能烧开水,因而洗水壶是烧开水的前提。没开水、没茶叶、不洗茶壶茶杯,就不能泡茶,因而这些又是泡茶的前提。它们的相互关系,可以用下边的箭头图来表示:
从这个图上可以一眼看出,办法甲总共要16分钟(而办法乙、丙需要20分钟)。如果要缩短工时、提高工作效率,应当主要抓烧开水这个环节,而不是抓拿茶叶等环节。同时,洗茶壶茶杯、拿茶叶总共不过4分钟,大可利用“等水开”的时间来做。
是的,这好像是废话,卑之无甚高论。有如走路要用两条腿走,吃饭要一口一口吃,这些道理谁都懂得。但稍有变化,临事而迷的情况,常常是存在的。在近代工业的错综复杂的工艺过程中,往往就不是像泡茶喝这么简单了。任务多了,几百几千,甚至有好几万个任务。关系多了,错综复杂,千头万绪,往往出现“万事俱备,只欠东风”的情况。由于一两个零件没完成,耽误了一台复杂机器的出厂时间。或往往因为抓的不是关键,连夜三班,急急忙忙,完成这一环节之后,还得等待旁的环节才能装配。
洗茶壶,洗茶杯,拿茶叶,或先或后,关系不大,而且同是一个人的活儿,因而可以合并成为:
看来这是“小题大做”,但在工作环节太多的时候,这样做就非常必要了。
这里讲的主要是时间方面的事,但在具体生产实践中,还有其他方面的许多事。这种方法虽然不一定能直接解决所有问题,但是,我们利用这种方法来考虑问题,也是不无裨益的。
代码示例
解法1
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.FutureTask;
/**
* 多线程模拟《《统筹方法》》
*/
@Slf4j(topic = "c.testThread04")
public class testThread04 {
private static FutureTask futureTask1;
private static FutureTask futureTask2;
public static void main(String[] args) throws Exception {
// //洗水壶,烧开水
Thread thread1 = new Thread(() -> {
log.debug("开始洗水壶");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("开始烧开水");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"老王");
//洗茶壶、拿茶杯、拿茶叶
Thread thread2 = new Thread(() -> {
log.debug("洗茶壶");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("拿茶杯");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("拿茶叶");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//等待线程 1 将水烧开
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("开始泡茶");
},"小王");
thread1.start();
thread2.start();
}
}
18:26:07 [小王] c.testThread04 - 洗茶壶
18:26:07 [老王] c.testThread04 - 开始洗水壶
18:26:08 [小王] c.testThread04 - 拿茶杯
18:26:08 [老王] c.testThread04 - 开始烧开水
18:26:09 [小王] c.testThread04 - 拿茶叶
18:26:18 [小王] c.testThread04 - 开始泡茶
解法1 的缺陷: ● 上面模拟的是小王等老王的水烧开了,小王泡茶,如果反过来要实现老王等小王的茶叶拿来了,老王泡茶呢?代码最好能适应两种情况 ● 上面的两个线程其实是各执行各的,如果要模拟老王把水壶交给小王泡茶,或模拟小王把茶叶交给老王泡茶呢?
当然这么一个简单的例子,要想做的完美,看来也并非易事。后续会给出完美的解决方案。