共计 8813 个字符,预计需要花费 23 分钟才能阅读完成。
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();}
}
本文由
传智教育博学谷狂野架构师
教研团队公布。如果本文对您有帮忙,欢送
关注
和点赞
;如果您有任何倡议也可留言评论
或私信
,您的反对是我保持创作的能源。转载请注明出处!