java线程的应用
1. Java多线程概述
上面咱们看下Java的多线程
- 作者: 博学谷狂野架构师
-
GitHub:GitHub地址 (有我精心筹备的130本电子书PDF)
只分享干货、不吹水,让咱们一起加油!😄
1.1 java天生就是多线程的
一个Java程序从main()办法开始执行,而后依照既定的代码逻辑执行,看似没有其余线程参加,但实际上Java程序天生就是多线程程序,因为执行main()办法的是一个名称为main的线程。
1.1.1 代码案例
执行上面的代码
package chapter01;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class ThreadDemo {
/**
* 打印出java中所有的线程
* @param args
*/
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo info : threadInfos) {
System.out.println("[" + info.getThreadId() + "]" + info.getThreadName());
}
System.out.println(Thread.activeCount());
}
}
执行后咱们会发现打印了如下的线程信息,阐明Java自身就是多线程的
- [6] Monitor Ctrl-Break //监控Ctrl-Break中断信号的
- [5] Attach Listener //内存dump,线程dump,类信息统计,获取零碎属性等
- [4] Signal Dispatcher // 散发解决发送给JVM信号的线程
- [3] Finalizer // 调用对象finalize办法的线程
- [2] Reference Handler//革除Reference的线程
- [1] main //main线程,用户程序入口
1.2 线程的生命周期
Thread类提供了六种状态
1.2.1 新建状态(NEW)
当线程对象对创立后,即进入了新建状态,如:
Thread thread1 = new MyThread();
1.2.2 运行状态(RUNNABLE)
Java线程中将就绪(ready)和运行中(running)两种状态抽象的称为“运行”。
线程对象创立后,其余线程(比方main线程)调用了该对象的start()办法。该状态的线程位于可运行线程池中,期待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在取得CPU工夫片后变为运行中状态(running)。
1.2.3 阻塞状态(BLOCKED)
处于运行状态中的线程因为某种原因,临时放弃对CPU的使用权,进行执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态
1.2.4 期待状态(WAITING)
进入该状态的线程须要期待其余线程做出一些特定动作(告诉或中断)
1.2.5 超时期待(TIMED_WAITING)
该状态不同于WAITING,它能够在指定的工夫后自行返回。
1.2.6 终止状态(TERMINATED)
线程执行完了或者因异样退出了run()办法,该线程完结生命周
2. 线程的创立形式
创立线程的形式有两种
2.1 继承Thread类
咱们能够通过继承Thread类来应用Java的多线程
package chapter01.create;
/**
* 创立一个线程并运行
*/
public class threadTest extends Thread {
@Override
public void run() {
System.out.println("线程运行");
}
public static void main(String[] args) {
threadTest threadCreate = new threadTest();
threadCreate.start();
}
}
2.2 实现 Runnable 接口
实现Runnable 接口并交给Thread进行运行
package chapter01.create;
public class RunnableTest implements Runnable {
@Override
public void run() {
System.out.println("线程运行");
}
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread thread = new Thread(runnableTest);
thread.start();
}
}
2.3 Thread和Runnable的区别
Thread才是Java里对线程的惟一形象,Runnable只是对工作(业务逻辑)的形象,Thread能够承受任意一个Runnable的实例并执行。
2.3.1 注意事项
有些面试官会说实现线程的形式有三种 Thread、Runnable 以及Callable,然而依照java源码中Thread类中的正文说的实现类的避免只有两种,咱们能够看下啊Thread类的源码
3. 线程的终止形式
上面咱们看下线程的终止形式有哪些
3.1 线程天然终止
要么是run执行实现了,要么是抛出了一个未解决的异样导致线程提前结束。
3.2 stop终止
暂停、复原和进行操作对应在线程Thread的API就是suspend()、resume()和stop(),然而这些API是过期的,也就是不倡议应用的 。
3.2.1 为什么不倡议应用
不倡议应用的起因次要有:以suspend()办法为例,在调用后,线程不会开释曾经占有的资源(比方锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。
同样,stop()办法在终结一个线程时不会保障线程的资源失常开释,通常是没有给予线程实现资源开释工作的机会,因而会导致程序可能工作在不确定状态下,正因为suspend()、resume()和stop()办法带来的副作用,这些办法才被标注为不倡议应用的过期办法。
3.3 中断终止
举荐应用中断的形式来终止线程
平安的停止则是其余线程通过调用某个线程A的interrupt()办法对其进行中断操作,,中断好比其余线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立刻进行本人的工作,同样的A线程齐全能够不理睬这种中断请求,因为java里的线程是合作式的,不是抢占式的,线程通过查看本身的中断标记位是否被置为true来进行响应。
线程通过办法isInterrupted()来进行判断是否被中断,也能够调用静态方法Thread.interrupted()来进行判断以后线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。
如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait等),则在线程在查看中断标示时如果发现中断标示为true,则会在这些阻塞办法调用处抛出InterruptedException异样,并且在抛出异样后会立刻将线程的中断标示位革除,即从新设置为false。
3.3.1 代码案例
package chapter01.stop;
/**
* 应用Runable的中断
*/
public class ThreadInterrupted implements Runnable {
private int i = 0;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
i++;
System.out.println("线程正在运行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i > 10) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) {
new Thread(new ThreadInterrupted()).start();
}
}
3.3.2 注意事项
不倡议自定义一个勾销标记位来停止线程的运行。
因为run办法里有阻塞调用时会无奈很快检测到勾销标记,线程必须从阻塞调用返回后,才会查看这个勾销标记。这种状况下,应用中断会更好,状态位如果跨线程扭转状态必须应用volatile来保障可见性。
- 个别的阻塞办法,如sleep等自身就反对中断的查看。
- 查看中断位的状态和查看勾销标记位没什么区别,用中断位的状态还能够防止申明勾销标记位,缩小资源的耗费。
留神:处于死锁状态的线程无奈被中断
3.4 状态位终止
状态位就是用一个变量来标识线程的运行状态,如果须要进行了就就扭转状态位的状态,然而状态位肯定要应用 volatile 关键字,否在可能造成多线程状态下的不可见
3.4.1 代码案例
3.4.1.1 应用volatile
应用volatile能够失常中断线程
package chapter01.stop;
public class ThreadFlag implements Runnable {
protected long i = 0;
private volatile boolean flag = false;
@Override
public void run() {
while (!flag) {
i++;
if (i > 100000) {
flag = true;
}
System.out.println(i);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadFlag threadFlag = new ThreadFlag();
new Thread(threadFlag).start();
}
}
3.4.1.2 不应用volatile
不应用volatile,会导致线程不能失常中断
package chapter01.stop;
public class ThreadInvisible implements Runnable {
protected long i = 0;
/**
* 不加volatile 会造成多线程的变量不可见,判断不会进行
*/
public boolean flag = false;
@Override
public void run() {
while (!flag) {
i++;
}
}
public static void main(String[] args) throws InterruptedException {
ThreadInvisible threadFlag = new ThreadInvisible();
new Thread(threadFlag).start();
Thread.sleep(1000);
threadFlag.flag = true;
}
}
3. run和start的区别
Thread类是Java里对线程概念的形象,能够这样了解:咱们通过new Thread()其实只是new出一个Thread的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()办法后,才实现了真正意义上的启动线程。
start()办法让一个线程进入就绪队列期待调配cpu,分到cpu后才调用实现的run()办法,start()办法不能反复调用,如果反复调用会抛出异样。
而run办法是业务逻辑实现的中央,实质上和任意一个类的任意一个成员办法并没有任何区别,能够反复执行,也能够被独自调用。
4. 其余的线程相干办法
上面咱们看下线程的其余办法有哪些
4.1 sleep办法
使以后线程(即调用该办法的线程)暂停执行一段时间,让其余线程有机会继续执行,但它并不开释对象锁,也不开释占用的资源。
也就是说如果有synchronized同步快,其余线程依然不能访问共享数据,留神该办法要捕获异样。
例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()办法,只有高优先级的线程执行结束后,低优先级的线程才可能执行;然而高优先级的线程sleep(500)后,低优先级就有机会执行了。
总之,sleep()能够使低优先级的线程失去执行的机会,当然也能够让同优先级、高优先级的线程有执行的机会。
4.1.1 代码案例
package chapter01.method;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadSleep {
public static void main(String[] args) {
sleep1();
sleep2();
}
public static void sleep1() {
Thread thread = new Thread(() -> {
System.out.println("xxxxxxxxxxxxxxxxxx");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
//thread.setPriority(100);
thread.start();
}
public static void sleep2() {
Thread thread = new Thread(() -> {
System.out.println("xxxxxxxxxxxxxxxxxx");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
//thread.set
thread.start();
}
}
4.2 join办法
把指定的线程退出到以后线程,能够将两个交替执行的线程合并为程序执行 。
比方在线程B中调用了线程A的Join()办法,直到线程A执行结束后,才会继续执行线程B
留神: t.join()办法只会使主线程进入期待池并期待t线程执行结束后才会被唤醒。并不影响同一时刻处在运行状态的其余线程
4.2.1 代码案例
package chapter01.method;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadJoin {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
for(int i=0;i<10;i++) {
System.out.println("111111111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
Thread thread2 = new Thread(()->{
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<10;i++){
System.out.println("2222222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
thread1.start();
thread2.setDaemon(true);
thread2.start();
}
}
4.3 yield办法
yield()应该做的是让以后运行线程回到可运行状态,以容许具备雷同优先级的其余线程取得运行机会。
因而,应用yield()的目标是让雷同优先级的线程之间能适当的轮转执行。然而,理论中无奈保障yield()达到退让目标,因为退让的线程还有可能被线程调度程序再次选中。
yield()是将线程从运行状态变更为就绪状态,不会变为期待/睡眠/阻塞状态,留神:yeid办法是不开释资源的。
4.3.1 代码案例
package chapter01.method;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadYield {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
// Thread.yield();
System.out.println("1111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
Thread thread2 = new Thread(()->{
System.out.println("22222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
thread1.start();
thread2.start();
}
}
5 线程的优先级
在Java线程中,通过一个整型成员变量priority来管制优先级,优先级的范畴从1~10
在线程构建的时候能够通过setPriority(int)办法来批改优先级,默认优先级是5,优先级高的线程调配工夫片的数量要多于优先级低的线程。
设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程须要设置较高优先级,而并重计算(须要较多CPU工夫或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占,在不同的JVM以及操作系统上,线程布局会存在差别,有些操作系统甚至会疏忽对线程优先级的设定
5.1 代码案例
package chapter01.priority;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
/**
* 执行的时候优先级越高 越容易执行到该线程
*/
public class ThreadPriority {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
System.out.println("1111111111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
Thread thread2 = new Thread(() -> {
while (true) {
System.out.println("22222222222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
thread1.setPriority(1);
thread2.setPriority(7);
thread1.start();
thread2.start();
}
}
6. 守护线程
Daemon(守护)线程是一种反对型线程,因为它次要被用作程序中后盾调度以及支持性工作。
这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出,能够通过调用Thread.setDaemon(true)
将线程设置为Daemon线程,咱们个别用不上,比方垃圾回收线程就是Daemon线程
Daemon线程被用作实现支持性工作,然而在Java虚拟机退出时Daemon线程中的finally块并不一定会执行,在构建Daemon线程时,不能依附finally块中的内容来确保执行敞开或清理资源的逻辑,也能够了解为等程序的所有的用户线程完结后,守护线程也将完结。
留神:守护线程必须在start之前设置,否则会报错。
6.1 代码案例
package chapter01.daemon;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadDaemon {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
System.out.println("1111111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("22222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
/* Thread thread3 = new Thread(() -> {
while (true) {
System.out.println("3333333333333");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});*/
thread1.setDaemon(true);
thread1.start();
thread2.start();
//thread3.start();
}
}
本文由
传智教育博学谷狂野架构师
教研团队公布。如果本文对您有帮忙,欢送
关注
和点赞
;如果您有任何倡议也可留言评论
或私信
,您的反对是我保持创作的能源。转载请注明出处!
发表回复