线程方法详解
前言
Github:https://github.com/HealerJean
1、概念
1.1、并行和并发
1、并行:多个任务同时执行
2、并发:多个任务交替进行,肉眼观察和并行是一样的 ( 重点:多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发)
1.2、同步和异步
1、同步:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;
2、异步:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。
区别:一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。
1.3、临界区
表示一种公共的资源或者说是共享数据,可以被多个线程使用,但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要是想使用这个资源,就必须等待。
1.4、线程的几种状态
package com.hlj.moudle.thread.dm001线程状态;
import org.junit.Test;
/**
* 作者 :HealerJean
* 日期 :2019/2/16 下午4:19.
* 类描述:
*/
public class Dm01Status {
/**
* 打印线程的集中状态
*/
@Test
public void printThreadStatus(){
for(Thread.State state:Thread.State.values()){
System.out.println(state);
}
}
/** 打印结果
NEW //新建状态 Thread thread = new Thread()
RUNNABLE //可运行状态 Thread.start() ;
BLOCKED //阻塞状态 synchronized对象或者类锁
WAITING //等待状态
1、synchronized /调用object.wait(),,如果将它变成RUNNABLE状态调用Object.notify()
2、thread.join()
3、lock.lock
4、condition.await()
5、Thread.sleep()
TIMED_WAITING //有限等待状态 这个和wainting有点类似,但是这个是有时间限制的。如果超过了时间,自动变成RUNNABLE
TERMINATED //被终止追昂头
1、正常退出
2、 因为一个没有捕获的异常终止了run()方法二意外死亡
*/
}
1.4.1、NEW
新建状态
Thread thread = new Thread()
/**
* 1、NEW 新建状态
*/
@Test
public void testNEW(){
Thread thread = new Thread(()->{
while (true){
}
});
System.out.println(thread.getState()); //NEW
}
1.4.2.1、Thread
的#run()
与start()
的区别
1、Thread.start()
方法(native)
启动线程,使之进入可运行状态,当cpu分配时间该线程时,由JVM调度执行run()方法。 相当于玩游戏机,只有一个游戏机(cpu),可是有很多人要玩,于是,start是排队!等CPU选中你就是轮到你,你就run(),当CPU的运行的时间片执行完,这个线程就继续排队,等待下一次的run()。
2、start()
方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于可运行状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
3、run()
方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
1.4.2、RUNNABLE
可运行状态
Thread thread = new Thread()
thread.start() ;
/**
* 2、RUNNABLE 可运行状态
*/
@Test
public void testRUNNALBE(){
Thread thread = new Thread(()->{
while (true){
}
});
thread.start();
System.out.println(thread.getState()); //RUNNABLE
}
1.4.3、阻塞状态
阻塞状态 synchronized
对象或者类锁 可以造成线程阻塞状态
package com.hlj.moudle.thread.dm001线程状态;
public class Dm02阻塞状态 {
public static void main(String[] args) {
TaskBlockedObject blockedTask = new TaskBlockedObject() ;
Thread thread1 = new Thread(()->{
blockedTask.offerThread1();
},"线程1");
Thread thread2 = new Thread(()->{
blockedTask.offerThread1();
},"线程2");
thread1.start();
thread2.start();
System.out.println(thread1.getName()+":"+thread1.getState()); // 线程1:RUNNABLE
System.out.println(thread2.getName()+":"+thread2.getState()); // 线程2:BLOCKED
}
}
class TaskBlockedObject {
private static final Object object = new Object();
/**
* 提供给线程1进行调用
*/
public void offerThread1(){
synchronized (object){
while (true){
}
}
}
/**
* 提供给线程2进行调用
*/
public void offerThread2(){
synchronized (object){
while (true){
}
}
}
}
1.5、死锁
死锁就是,线程A占用资源,线程B也占用资源,但是都不释放
活锁,就是A和B 都占用资源,但是又同时释放,这样来来回回就是活锁
饥饿。比如高优先级的线程,一直占用线程,低优先级的一直不能得到线程
1.5.1、死锁的条件
1) 互斥条件: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2) 请求与保持条件:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3) 不剥夺条件 :别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4) 循环等待:一直等待自己需要的资源
package com.hlj.moudle.thread.d02死锁;
public class D01DeadLockThread implements Runnable {
/**
注意的的这里的final是 常亮。这样即使new两个对象,启动线程也是访问同一个资源
线程thread1占有资源objectA,线程thread2占有资源objectB,
当两个线程发出请求时,由于所请求的资源都在对方手中,从而发生线程阻塞,造成了线程的死锁。
*/
private static final Object objectA = new Object();
private static final Object objectB = new Object();
private boolean flag;
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("当前线程 为:" + threadName + "\tflag = " + flag);
if (flag) {
synchronized (objectA) {
try {
Thread.sleep(1000); //等待其他线程执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(threadName + "已进入同步代码块objectA,准备进入objectB");
synchronized (objectB) {
System.out.println(threadName + "已经进入同步代码块objectB");
}
}
} else {
synchronized (objectB) {
try {
Thread.sleep(1000); //等待其他线程执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(threadName + "已进入同步代码块objectB,准备进入objectA");
synchronized (objectA) {
System.out.println(threadName + "已经进入同步代码块objectA");
}
}
}
}
public static void main(String[] args) {
D01DeadLockThread deadlock1 = new D01DeadLockThread();
D01DeadLockThread deadlock2 = new D01DeadLockThread();
deadlock1.flag = true;
Thread thread1 = new Thread(deadlock1);
deadlock2.flag = false;
Thread thread2 = new Thread(deadlock2);
thread1.start();
thread2.start();
}
}
当前线程 为:Thread-1 flag = false
当前线程 为:Thread-0 flag = true
Thread-0已进入同步代码块objectA,准备进入objectB
Thread-1已进入同步代码块objectB,准备进入objectA
1.5.2、测试结果
发现这里的程序并没有执行,说明死锁成功了
1.6、线程优先级
main的优先级,是普通的优先级 ,首先说 线程优先级,并不能保证优先级高的先运行,也不保证优先级高的更多的分配CPU时间,只是对系统的建议而已,到底运行哪个,是操作系统决定的,都不是java说了算的。
但这些优先级需要操作系统的支持。不同操作系统上优先级并不相同
package com.hlj.moudle.thread.d03线程优先级;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicLong;
/**
* 作者 :HealerJean
* 日期 :2019/2/16 下午5:47.
*
* 1、main的优先级,是普通的优先级
* 2、线程优先级别thread1.setPriority
* 3、线程优先级,并不能保证优先级高的先运行,也不保证优先级高的更多的分配CPU时间
* 只是对系统的建议而已,到底运行哪个,是操作系统决定的,都不是java说了算的。
* 线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。
* 4、少量的执行看不出来效果只有数量特别多的时候才会有效果
*
*/
public class D01Priority {
@Test
public void testPriority() {
Thread thread1 = new Thread(()->{
System.out.println("正常优先级 counter1:");
},"线程1");
Thread thread2 = new Thread(()->{
System.out.println("最高优先级 counter2:");
},"线程2");
Thread thread3 = new Thread(()->{
System.out.println("最低优先级 counter3:");
},"线程3");
thread1.setPriority(Thread.NORM_PRIORITY); //线程1 正常优先级
thread2.setPriority(Thread.MAX_PRIORITY); //线程2 最高优先级
thread3.setPriority(Thread.MIN_PRIORITY ); //线程3 最低优先级
thread1.start();
thread2.start();
thread3.start();
}
}
1.7、组合方式
拿山治烧水举例来说,(山治的行为好比用户程序,烧水好比内核提供的系统调用),这两组概念翻译成大白话可以这么理解:
⬤ 同步/异步关注的是水烧开之后需不需要我来处理。
⬤ 阻塞/非阻塞关注的是在水烧开的这段时间是不是干了其他事。
1.7.1、同步阻塞(效率最低)
单任务顺序执行: 点火后,傻等,不等到水开坚决不干任何事(阻塞),水开了关火(同步)。
1.7.2、同步非阻塞
多任务轮询: 点火后,去看电视(非阻塞),时不时看水开了没有,水开后关火(同步)。
1.7.3、异步阻塞
单任务等通知: 点火后,傻等水开(阻塞),水开后自动断电(异步)。
1.7.4、异步非阻塞【效率最高】
多任务等通知:按下开关后,该干嘛干嘛 (非阻塞),水开后自动断电(异步)。
2、线程方法
2.1、谦让 Thread.yield()
Java线程中有一个Thread.yield( )
方法,把自己CPU
执行的时间让掉,让自己或者其它的线程运行。(也就是谁先抢到谁执行, 记得:相当于原来的线程从来没有执行过哦)
相当于是将run()
运行状态转变为start()
状态
package com.hlj.moudle.thread.d04yield谦让;
import org.junit.Test;
public class D01Yield {
@Test
public void testYield(){
Thread thread1 = new Thread(()->{
task();
},"线程1");
Thread thread2 = new Thread(()->{
task();
},"线程2");
thread1.start();
thread2.start();
}
public void task(){
for (int i = 1; i <= 50; i++) {
// 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i == 30) {
Thread.currentThread().yield();
//下面的将不会打印,相当于把当前线程让掉了
System.out.println("" + Thread.currentThread().getName() + "-----" + i);
}else {
System.out.println("" + Thread.currentThread().getName() + "-----" + i);
}
}
}
}
2.2、终止线程 #stop
(弃用了)
一般来说,线程执行完毕会自动结束,无需手动关闭
线程提供给了一个Stop方法,可以即使终止一个线程,但是会发现stop已经被弃用了。Thread.stop()方法在结束线程的时候,会直接终止线程,这个线程就会立即释放掉所有的锁,而这些锁恰恰是用来维护对象的一致性的,如果此时写到了一半,强行中止,那么对象就会被破坏,或者线程可能在操作数据库,强⾏中断导致数据不一致,从而混乱的问题。所以现在不使用了
2.3、线程中断 #thread.interrupt
严格 讲,线程中断不会使线程立即退出,而是给线程发送一个通知(而使用Threasd.sleep()
抛出了异常,然后中断状态被清空),告诉目标线程,有人希望你退出, 后续目前线程接到通知怎么处理,是线程自己的事情了
1、中断异常的抛出:如果此线程处于阻塞状态(比如调⽤了wait方法,io等待),则会立刻退出阻塞,并抛出InterruptedException异常(中断标志位已经被清空了),线程就可以通过捕获InterruptedException来做⼀定的处理,然后让线程退出。
2、中断标记状态的判断: 如果此线程正处于运行之中,则线程不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以线程要在适当的位置通过调用isInterrupted方法来查看自⼰是否被中断,并做退出操作。
2.3.1、简单测试
像下面这种需要捕获中断异常的,中断标志位已经被清空了,也就是说在catch中捕获的 查看状态为false
package com.hlj.moudle.thread.d06Interupt线程中断;
import org.junit.Test;
/**
严格 讲,线程中断不会使线程立即退出,而是给线程发送一个通知(抛出了异常),告诉目标线程,有人希望你退出,
后续目前线程接到通知怎么处理,是线程自己的事情了
像下面这种需要捕获中断异常的,中断标志位以及被清空了了,也就是说在catch中捕获的 查看状态为false
*/
public class D01简单测试 {
@Test
public void test简单测试(){
Thread thread = new Thread(()->{
try {
System.out.println("1、线程开始执行");
Thread.sleep(5000);
System.out.println("2、线程结束休眠");
} catch (InterruptedException e) {
System.out.println("3、线程中断休眠");
System.out.println(Thread.currentThread().getState() + "中断状态" + Thread.currentThread().isInterrupted());
return;
}
System.out.println("4、线程正在运行");
});
thread.start();
try {
Thread.sleep(1000);//准备进行中断,让上面的线程确保已经执行
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1、线程开始执行
3、线程中断休眠
RUNNABLE中断状态false
2.3.2、new
和terminated
下中断
毫无意义,返回中断状态为false
package com.hlj.moudle.thread.d06Interupt线程中断;
import org.junit.Test;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午3:42.
* 类描述: 测试中断状态
*/
public class D02New中断 {
@Test
public void 测试new中断(){
Thread thread = new Thread(()->{
});
//new新建状态 返回 false
System.out.println("线程状态:"+thread.getState()+"-中断状态"+thread.isInterrupted());
//此时线程还没启动 就进行中断,其实毫无意义
thread.interrupt();
System.out.println("中断状态"+thread.isInterrupted());
//启动线程
thread.start();
try {
Thread.sleep(3000);//确保线程已经执行完毕
System.out.println("线程状态:"+thread.getState()+"-中断状态"+thread.isInterrupted());
} catch (InterruptedException e) {
e.printStackTrace();
}
/** 打印结果
线程状态:NEW-中断状态false
中断状态false
线程状态:TERMINATED-中断状态false
*/
}
}
2.3.3、RUNNABLE
状态下中断
处于
RUNNABLE
状态的线程在遭遇中断操作的时候只会设置该线程的中断标志位,并不会让线程实际中断,而是会继续执行,不受影响
package com.hlj.moudle.thread.d06Interupt线程中断;
import org.junit.Test;
public class D03RUNNABLE中断 {
@Test
public void testRUNNABLE中断测试(){
Thread thread = new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.print(i+""+Thread.currentThread().isInterrupted()+"___");
}
});
thread.start();
try {
Thread.sleep(1); //确保上面的线程在执行过程中进行中断
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 打印结果
0false___1false___2false___3false___4false___5false___6false___7false___8false___9false___10false___11false___12false___13false___14false___15false___16false___17false___18false___19false___20false___21false___22false___23false___24false___25false___26false___27false___28false___29false___30false___31false___32false___33false___34false___35false___36false___37false___38false___39false___40false___41false___42false___43false___44false___45false___46false___47false___48false___49false___50false___51false___52false___53false___54false___55false___56false___57false___58false___59false___60false___61false___62false___63false___64false___65false___66false___67false___68false___69false___70false___71false___72false___73false___74false___75false___76true___77true___78true___79true___80true___81true___82true___83true___84true___85true___86true___87true___88true___89true___90true___91true___92true___93true___94true___95true___96true___97true___98true___99true___ */
}
}
2.3.3.2、合适的场景结束中断
程序让我们已经执行的线程进行中断,它又不是赶着去投胎,我们在合适场景下if判断中断标志位是否被设置,结束任务执行不就完事了
package com.hlj.moudle.thread.d06Interupt线程中断;
import org.junit.Test;
public class D03RUNNABLE中断 {
@Test
public void testRUNNABLE中断测试(){
Thread thread = new Thread(()->{
for (int i = 0; i < 100; i++) {
if(Thread.currentThread().isInterrupted()){
System.out.println("\n程序已经被中断了,结束程序运行");
return;
}
System.out.print(i+""+Thread.currentThread().isInterrupted()+"___");
}
});
thread.start();
try {
Thread.sleep(1); //确保上面的线程在执行过程中进行中断
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
0false___1false___2false___3false___4false___5false___6false___7false___8false___9false___10false___11false___12false___13false___14false___15false___16false___17false___18false___19false___20false___21false___22false___23false___24false___25false___26false___27false___28false___29false___30false___31false___32false___33false___34false___35false___36false___37false___38false___39false___40false___41false___42false___43false___44false___45false___46false___47false___48false___49false___50false___51false___52false___53false___54false___55false___56false___57false___58false___59false___60false___61false___62false___63false___64false___65false___
程序已经被中断了,结束程序运行
*/
}
}
2.3.4、BLOCKED
状态下中断
阻塞状态下中断线程,并不会结束阻塞线程的运行,运行状态下的场景是一样的,需要我们对阻塞线程的状态进行判断,然后去修改让它结束运行
package com.hlj.moudle.thread.d06Interupt线程中断;
import org.junit.Test;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午4:17.
* 类描述: 阻塞状态下中断线程,并不会结束阻塞线程的运行,
* 其实和运行状态下的场景是一样的,需要我们对阻塞线程的状态进行判断,然后去修改让它结束运行
*/
public class D03BLOCKED中断 {
public static synchronized void task(){
while (true){
}
}
@Test
public void BLOCKED中断(){
Thread thread1 = new Thread(()->{
task();
});
Thread thread2 = new Thread(()->{
task();
});
thread1.start();
try {
Thread.sleep(1000); //确保线程1已经启动
thread2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000); //确保上面的两个线程已经执行
System.out.println("线程1状态"+thread1.getState()+"————————————");
System.out.println("线程2状态"+thread2.getState()+"————————————");
thread2.interrupt();//这里中断不会对上面的抛出异常,因为不是sleep抛出的异常
System.out.println("线程1状态"+thread1.getState()+"————————————"+thread1.isInterrupted());
System.out.println("线程2状态"+thread2.getState()+"————————————"+thread2.isInterrupted());
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 线程1状态RUNNABLE————————————
* 线程2状态BLOCKED————————————
* 线程1状态RUNNABLE————————————false
* 线程2状态BLOCKED————————————true
*
*
*/
}
}
2.3.5、WAITING
/TIMED_WAITING
下中断
这两种状态本质上是同一种状态,只不过TIMED_WAITING
在等待一段时间后会自动释放自己,而WAITING
则是无限期等待,需要其他线程调用notify
方法释放自己。但是他们都是线程在运行的过程中由于缺少某些条件而被挂起在某个对象的等待队列上。
当这些线程遇到中断操作的时候,会抛出一个InterruptedException
异常(只有主动抛出的时候,别忘记了,造成WAITING的情况有很多种哦),
并清空中断标志位 ,也就是说在当前线程中查看中断状态为flase,而不是像上面的Runnalbe
中是false
了,因为它捕获了异常,当然可以继续执行线程任务啦,所以才false,和Thread.sleep
清空中断标志位是一样的,都需要抛出异常
package com.hlj.moudle.thread.d06Interupt线程中断;
import org.junit.Test;
/**
这两种状态本质上是同一种状态,只不过TIMED_WAITING在等待一段时间后会自动释放自己,
而WAITING则是无限期等待,需要其他线程调用notify方法释放自己。
但是他们都是线程在运行的过程中由于缺少某些条件而被挂起在某个对象的等待队列上。
当这些线程遇到中断操作的时候,会抛出一个InterruptedException异常,并清空中断标志位。例如:
*/
public class D05WAIT中断 {
@Test
public void BLOCKED中断() {
Thread thread = new Thread(() -> {
try {
synchronized (this){
System.out.println(Thread.currentThread().getState() + "中断状态" + Thread.currentThread().isInterrupted());
wait();
System.out.println("wait");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getState() + "中断状态" + Thread.currentThread().isInterrupted());
}
});
thread.start();
try {
Thread.sleep(1000); //确保线程1已经启动
thread.interrupt();
} catch (InterruptedException e) {
}
/**
RUNNABLE中断状态false
RUNNABLE中断状态false
*/
}
}
2.3.6、总结:
NEW
和TERMINATED
对于中断操作几乎是屏蔽的,毫无意义
RUNNABLE
和BLOCKED
类似,对于中断操作只是设置中断标志位并没有强制终止线程,对于线程的终止权利依然在程序手中
WAITING
/TIMED_WAITING
状态下的线程对于中断操作是敏感的,他们会抛出异常并清空中断标志位。
2.4、join
等待线程执行结束
很多时候,一个线程的输入,非常依赖别的线程的输出,此时,需要等待线程执行完毕,才能继续执行,所以出现了join方法,有两个join方法
public final void join() throws InterruptedException
public final synchronized void join(long millis)
package com.hlj.moudle.thread.d07Join等待程序执行结束;
import org.junit.Test;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午4:51.
主线程,等待异步线程执行结束
Join等待线程执行结束
*/
public class D01Join {
@Test
public void testJoin(){
Thread thread = new Thread(()->{
for (int i = 0; i < 10 ; i++) {
System.out.print(i+"_");
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n主程序结束运行");
/**
*
0_1_2_3_4_5_6_7_8_9_
主程序结束运行
*/
}
}
2.5、Daemon
守护进程
守护线程,是在后台默默的完成一些系统的服务,比如垃圾回收线程,与之相对于的就是用户线程,
用户线程可以认为是系统的工作线程,如果用户线程全部结束,也就意味着,这个应用程序无事可做了,那么守护线程要守护的对象也就不存在了,整个应用程序也就结束了,
必须在start之前设置
thread.setDaemon(true)
;否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程
2.5.1、注意事项
不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。因为不可能知道在所有的User完成之前,Daemon是否已经完成了预期的服务任务。一旦User退出了,可能大量数据还没有来得及读入或写出,计算任务也可能多次运行结果不一样。这对程序是毁灭性的。造成这个结果理由已经说过了:一旦所有User Thread离开了,虚拟机也就退出运行了。
主线程结束了,daemon结束并不是会马上结束,毕竟人家还需要一点点时间嘛,是不是
package com.hlj.moudle.thread.d08Daemon守护线程;
import org.junit.Test;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午5:00.
* 类描述:
守护线程,是在后台默默的完成一些系统的服务,比如垃圾回收线程,与之相对于的就是用户线程,
用户线程可以认为是系统的工作线程,如果用户线程全部结束,也就意味着,这个应用程序无事可做了,
那么守护线程要守护的对象也就不存在了,整个应用程序也就结束了,必须在start之前设置 thread.setDaemon(true);否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
注意事项 :
(2) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。因为不可能知道在所有的User完成之前,Daemon是否已经完成了预期的服务任务。一旦User退出了,可能大量数据还没有来得及读入或写出,计算任务也可能多次运行结果不一样。这对程序是毁灭性的。造成这个结果理由已经说过了:一旦所有User Thread离开了,虚拟机也就退出运行了。
*
*
*/
public class D01Daemon {
@Test
public void testJoin(){
Thread thread = new Thread(()->{
int i = 1 ;
while (true){
System.out.printf("_"+(i++));
}
});
thread.setDaemon(true);//进程守护,主线程执行完毕,它就结束运行吧
thread.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n主程序结束运行");
/**
_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23
主程序结束运行
_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40_41_42_43_44_45_46_47_48_49_50_51_52_53
Process finished with exit code 0
_54_55_56_57_58_59_60_61_62_63_64_65_66_67_68_69_70_71_72_73_74_75_76_77_78_79_80_81_82_83_84_85
*/
}
}
2.6、wait
、notify
、notifyAll
1)
wait()
、notify()
和notifyAll()
方法是本地方法,并且为final
方法,无法被重写。2)等待和唤醒必须是同一个锁,调用某个对象的
wait()
方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor
(即锁),调用某个对象的notify()
方法,当前线程也必须拥有这个对象的monitor
,因此调用notify()
方法必须在同步块或者同步方法中进行(synchronized
块或者synchronized
方法)。3)调用某个对象的
notify()
方法能够唤醒一个正在等待这个对象的monitor
的线程,如果有多个线程都在等待这个对象的monitor
,则只能唤醒其中一个线程;具体唤醒哪个线程则不得而知。4)调用
notifyAll()
方法能够唤醒所有正在等待这个对象的monitor
的线程; 一个线程被唤醒不代表立即获取了对象的
monitor
,只有等调用完notify()
或者notifyAll()
并退出synchronized
块,释放对象锁后,其余线程才可获得锁执行wait
后面的剩余代码,不是从头开始哦
*/
public class D01WatitNotify {
public final static Object object = new Object() ;
@Test
public void testWati(){
Thread thread = new Thread(()->{
try {
System.out.println("线程1");
synchronized (object){
object.wait(1000);
System.out.println("wait");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(()->{
synchronized (object){
object.notify(); //唤醒持有该对象的其他线程,执行完本同步代码块才会执行线程1
System.out.println("notify");
}
System.out.println("线程2释放了锁");
});
thread.start();
thread2.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程1
notify
线程2释放了锁
wait
2.6.1、Thread
和 wait
区别
1、这两个方法来自不同的类分别是Thread
和Objec
t
2、sleep
方法没有释放锁,而 wait
方法释放了锁,使得其他线程可以使用同步控制块或者方法 ,如果线程拥有某个或某些对象的同步锁,那么在调用了wait()
后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()
方法的对象。wait()
方法也同样会在wait
的过程中有可能被其他对象调用interrupt()
方法中断
3、wait
,notify
和notifyAll
只能在同步控制方法或者同步控制块里面使用,而sleep
可以在 任何地方使用(使用范围)
object.wait. 让其他的线程先执行
object.notify 唤醒这个线程
synchronized(x){
x.notify()
//或者wait()
}
4、wait
属于Object
的成员方法,一旦一个对象调用了wait
方法,必须要采用notify()
和notifyAll()
方法唤醒该进程;
5、sleep
和wait
方法必需捕获异常,而notify
和notifyAll
不需要捕获异常。
2.6.2、生产者和消费者
package com.hlj.moudle.thread.d09wait;
import org.junit.Test;
import java.util.PriorityQueue;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午7:58.
* 类描述:
*/
public class D01生产者和消费者 {
private int queueSize = 100;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
@Test
public void test() {
Thread producer = new Thread(()->{
while(true){
synchronized (queue) {
while(queue.size() == queueSize){
try {
System.out.println("队列满,等待有空余空间");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1); //每次插入一个元素
queue.notify(); //有数据了,防止消费者一直在等待没有数据获取
System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
}
}
});
Thread consumer = new Thread(()->{
while(true){
synchronized (queue) {
while(queue.size() == 0){
try {
System.out.println("队列空,等待数据");
queue.wait(); //持续执行while,等待当前线程
} catch (InterruptedException e) {//如果当前线程出现了中断
e.printStackTrace();
}
}
queue.poll(); //每次移走队首元素
queue.notify(); //数据消耗了,防止生产者队列满了数据获取不到
System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
}
}
});
producer.start();
consumer.start();
}
}
2.7、线程组
如果没有显示指定属于哪个线程组,那么该线程就属于默认线程组(即main线程组)。默认情况下,子线程和父线程处于同一个线程组。 只有在创建线程时才能指定其所在的线程组,线程运行中途不能改变它所属的线程组
作用:线程组可以用来管理一组线程,通过
activeCount()
来查看活动线程的数量。其他没有什么大的用处,线程组是为了方便线程的管理 线程池是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。
package com.hlj.moudle.thread.D10线程组;
import org.junit.Test;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午8:13.
* 类描述:线程组
*/
public class D01ThreadGroup {
@Test
public void testThreadGrpup(){
/**
* 每个线程都有线程组,默认是 main
*/
System.out.println(Thread.currentThread().getThreadGroup().getName());
ThreadGroup tg = new ThreadGroup("PrintGroup");
Thread thread1= new Thread(tg,()->{
while (true){
}
},"线程1");
Thread thread2 = new Thread(tg,()->{
while (true){
}
},"线程2");
thread1.start();
thread2.start();
System.out.println(tg.activeCount()); //2 返回此线程组中活动线程的估计数。
}
}
8、Callable
计算完成后只能使用
get
方法来获取结果,如果线程没有执行完,Future.get()方法会阻塞当前线程的执行
8.1、与Runnable
区别
1、两者最大的不同点是:实现 Callable
接口的任务线程能返回执行结果;而实现Runnable
接口的任务线程不能返回结果;
2、Callable
接口的 call()
方法允许抛出异常;而Runnable
接口的 run()
方法的异常只能在内部消化,不能继续上抛;
3、行Callable
任务可以拿到一个Future
对象,Future
表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
package com.hlj.util.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import org.junit.Test;
public class CallableTest {
@Test
public void testStart() throws InterruptedException, ExecutionException{
Callable<String> callable = new CallableTaskWithResult(45);
//这个既是线程又是相当于是一个任务的返回
FutureTask<String> task = new FutureTask<String>(callable);
new Thread(task).start();
String result = task.get();
System.out.println(result);
}
class CallableTaskWithResult implements Callable<String> {
private int id;
public CallableTaskWithResult(int id) {
this.id=id;
}
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return "id为:"+id;
}
}
}
3、Lock
3.1、ReentrantLock
可重入锁
开发人员必须手动选择何时加锁(
lock
本身就是锁),何时释放锁,冲入锁的灵活性,远远高原synchronized
,但是一定要记得,退出临界区是,一定要释放锁,否则其他线程没有机会再使用临界区(多个线程交替使用,不像synchronize
,线程单独霸占)
3.1.1、#lock.lock()
, #lock.unlock()
的简单使用
说明:下面的输出结果永远都是 40
package com.hlj.moudle.thread.D11ReentrantLock;
import org.junit.Test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午8:57.
* 类描述:reentrantlock 可重入锁
*/
public class D01ReentrantLock {
public Lock lock = new ReentrantLock() ;
int i = 0 ;
@Test
public void testReentrantlock(){
Thread thread = new Thread(()->{
for (int j = 1; j <= 20 ; j++) {
lock.lock();
i++ ;
lock.unlock();
}
});
Thread thread2 = new Thread(()->{
for (int j = 1; j <= 20 ; j++) {
lock.lock();
i++ ;
lock.unlock();
}
});
thread.start();
thread2.start();
try {
Thread.sleep(1000);
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.1.2、中断锁 #lockInterruptibly
lock
不会抛出异常,lockInterruptibly
会抛出异常
// 不可以用下面的接口喽,因为它没有lockInterruptibly 方法
// public Lock lock = new ReentrantLock() ;
lockInterruptibly() 获取锁
isHeldByCurrentThread() 当前线程是否保持锁锁定,线程的执行lock方法的前后分别是false和true
测试,两个锁各互相需要对方的锁,但是都不释放,这里中断一个,让对方获取,让对方执行
package com.hlj.moudle.thread.D11ReentrantLock;
import org.junit.Test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午9:11.
* 类描述:
*/
public class D02中断lockInterruptibly {
// 不可以用下面的接口喽,因为它没有lockInterruptibly 方法
// public Lock lock = new ReentrantLock() ;
public ReentrantLock lock1 = new ReentrantLock() ;
public ReentrantLock lock2= new ReentrantLock() ;
int i = 0 ;
@Test
public void 线程中断(){
Thread thread1 = new Thread(()->{
i = 1 ;
task();
},"线程1");
Thread thread2 = new Thread(()->{
i = 2 ;
task();
},"线程2");
thread1.start();
thread2.start();
try {
Thread.sleep(1000); //等待线程1和线程2分到到了获取各自锁的地方,然后执行下面的中断
thread2.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void task( ) {
try {
if ( i== 1) {
lock1.lockInterruptibly();
Thread.sleep(500); //等待线程2执行
lock2.lockInterruptibly();
System.out.println("111111");
} else {
lock2.lockInterruptibly();
Thread.sleep(500);//等待线程1执行
lock1.lockInterruptibly();
System.out.println("2222222");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"中断了,但是这里跑出了InterruptedException异常,所以释放了");
} finally {
//判断是否拥有锁,先进来的先把锁解开了
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.out.println(Thread.currentThread().getName() + ":线程退出");
}
}
线程2中断了,但是这里跑出了InterruptedException异常,所以释放了
线程2:线程退出
111111
线程1:线程退出
}
3.1.3、锁申请等待限时lock.tryLock(5, TimeUnit.SECONDS)
限时等待,比如,约好朋友,他如果2个小时没来,那么,就不等了,也就是说不会执行里面的代码了
public ReentrantLock lock = new ReentrantLock() ;
package com.hlj.moudle.thread.D11ReentrantLock;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午9:37.
* 类描述:
*/
public class D03锁申请等待限时 {
public ReentrantLock lock = new ReentrantLock() ;
public void task() {
try {//等待时长,计时单位
if (lock.tryLock(5, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName());
System.out.println("get lock success");
Thread.sleep(6000); //占用线程6秒,哈哈,肯定大于5秒了,所以两个
} else {
System.out.println(Thread.currentThread().getName());
System.out.println("get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
@Test
public void testReentrantlock(){
Thread thread = new Thread(()->{
task();
},"线程1");
Thread thread2 = new Thread(()->{
task();
},"线程2");
thread.start();
thread2.start();
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
线程2
get lock success
线程1
get lock failed
*/
都有可能出现
线程1
get lock success
线程2
get lock failed
3.1.4、公平锁 new ReentrantLock(true);
默认情况下,锁都是非公平的,
synchronized
关键字进行的锁控制,是非公平的,重入锁,允许我们进行公平性的设置,如果没有特别需求,还是不用公平锁公平锁的一个特点是:不会产生饥饿现象,只要排队最终都会得到资源,但是实现公平锁要求系统维护一个有序队列,因此公平锁的实现成本较高,性能相对低下.
public static ReentrantLock fairLock = new ReentrantLock(true);//设置true指定锁是公平的,也可以不设置,分别运行观察
package com.hlj.moudle.thread.D11ReentrantLock;
import org.junit.Test;
import java.util.concurrent.locks.ReentrantLock;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午10:05.
* 类描述:
*/
public class D05公平锁True {
public ReentrantLock lock = new ReentrantLock(true) ;
public void task() {
while (true){
lock.lock();
System.out.println(Thread.currentThread().getName());
lock.unlock();
}
}
@Test
public void test(){
Thread thread = new Thread(()->{
task();
},"线程1");
Thread thread2 = new Thread(()->{
task();
},"线程2");
thread.start();
thread2.start();
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
发现整整齐齐
线程1
线程2
线程1
线程2
线程1
线程2
线程1
线程2
线程1
线程2
线程1
3.1.5、Condition
条件
和
wait
和notify
有点像1、
await()
方法会让当前线程等待,同时释放当前锁,当其他线程使用signal
(或者signalAll()
方法时,先回重新获得锁,并继续执行2、
singal()
方法用于唤醒一个在线程中等待的线程,,这方法在使用的时候,也要求线程,先获得相关锁,然后唤醒线程,在singal
之后,要释放掉相关锁(unlock
),谦让给被唤醒的线程,让它可以继续执行。
package com.hlj.moudle.thread.D11ReentrantLock;
import org.junit.Test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class D06Condition {
public Lock lock = new ReentrantLock(true) ;
public Condition condition = lock.newCondition();
public void task() {
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
lock.unlock();
}
@Test
public void test(){
Thread thread = new Thread(()->{
task();
},"线程1");
thread.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
condition.signal();
lock.unlock();
}
}
/**
* 打印结果
* 线程1
*/
3.6、读写锁reentrantReadWriteLock.readLock()
对于
Synchronized
、ReentrantLoc
k,同一时间内只能有一个线程访问被锁定的代码,那么读写锁则不同,其本质是分离两个锁,即读锁和写锁。
ReadWriteLock
是JDK5
开始提供的读写分离锁。读写分离开有效的帮助减少锁的竞争,以提升系统性能。用锁分离的机制避免多个读操作线程之间的等待。在读锁下,多个线程呢个可以并发的进行访问,但是在写锁的时候,只能顺序的访问。
比如线程
A1
,A2
,A3
进行写操作,B1,B2,B3进行读操作,这样就能让B1,B2,B3,之间并行,如果系统中读操作次数远远大于写操作,这种方式就会很有用了
读读不互斥,读读不阻塞
写写互斥,写写阻塞,
读写互斥 读阻塞写 ,写阻塞读
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = reentrantReadWriteLock.readLock();
private static Lock writeLock = reentrantReadWriteLock.writeLock();
package com.hlj.moudle.thread.D11ReentrantLock;
import org.junit.Test;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 作者 :HealerJean
* 日期 :2019/2/17 下午10:31.
* 类描述:
*/
public class D07读写锁 {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = reentrantReadWriteLock.readLock();
private static Lock writeLock = reentrantReadWriteLock.writeLock();
private static int value;
public static Object handleRead(Lock lock) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);// 模拟读操作
System.out.println("读操作:" + value);
return value;
} finally {
lock.unlock();
}
}
public static void handleWrite(Lock lock, int index)
throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);// 模拟写操作
System.out.println("写操作:" + value);
value = index;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws Exception {
TestReadThread testReadThread = new TestReadThread();
TestWriteThread testWriteThread = new TestWriteThread();
for (int i = 0; i < 18; i++) {
new Thread(testReadThread).start();
}
for (int i = 18; i < 20; i++) {
new Thread(testWriteThread).start();
}
}
private static class TestReadThread extends Thread {
@Override
public void run() {
try {
//Test.handleRead(lock);
D07读写锁.handleRead(readLock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class TestWriteThread extends Thread {
@Override
public void run() {
try {
//Test.handleWrite(lock,new Random().nextInt(100));
D07读写锁.handleWrite(writeLock,new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
时间上观察,发现读操作一些字全部执行完成了,说明不互斥,也不阻塞,不影响,但是写操作是顺序访问的
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
读操作:0
写操作:0
写操作:7