Java 多线程(二)多线程的优先级休眠礼让等
Java的调度方法
1)对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略
2)对高优先级,使用优先调度的抢占式策略
1、线程的优先级:
等级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
方法:
getPriority():返回线程优先级
setPriority(int newPriority):改变线程的优先级
线程不是优先级越高先执行。谁先执行取决于谁先去抢占CPU的资源,CPU的默认优先级是5
public class You implements Runnable{
@Override
public void run() {
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"运行"+i);
}
}
public static void main(String[] args) {
Thread thread1=new Thread(new You(),"一");
Thread thread2=new Thread(new You(),"二");
Thread thread3=new Thread(new You(),"三");
Thread thread4=new Thread(new You(),"四");
thread1.setPriority(8);
thread2.setPriority(5);
thread3.setPriority(1);
thread4.setPriority(6);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
2、获取线程名字
public class Test01 implements Runnable{
@Override
public void run() {
for (int i =0;i<4;i++){
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Test01 test01 = new Test01();
new Thread(test01,"线程一的名字").start();
new Thread(test01).start();
}
}
结果: 如果没有指定线程名字,Java会自动进行命名
另外要注意的是:mian方法也是一个线程。在Java中的线程是同时启动的,至于什么时候启动,那个先执行,完全看CPU的资源。
Java中,每次执行程序至少有两个线程启动。一个是main方法,一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候。实际上都会启动JVM,每个JVM实际上就是在操作系统启动了一个进程。
3、如何判断线程是否启动
public class Start implements Runnable{
@Override
public void run() {
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Start start = new Start();
Thread thread = new Thread(start);
System.out.println("启动线程前"+thread.isAlive());
thread.start();
System.out.println("线程启动后"+thread.isAlive());
}
}
结果:
主线程也有可能在子线程结束之前结束。并且子线程不受主线程结束而受到影响,子线程不会因为子线程结束而结束
4、线程的强制执行:
class Test02 implements Runnable{
@Override
public void run() {
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Test02 error = new Test02();
Thread thread = new Thread(error,"线程");
thread.start();
for (int i=0;i<50;i++){
if (i>0){
try {
thread.join(); //强制执行thread线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main方法强制执行线程-->" +i);
}
}
}
结果:执行多次结果可能不同
5、线程的休眠:
如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread的sleep方法。
sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。
public class Test03Sleep implements Runnable{
@Override
public void run() {
for (int i=0;i<3;i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+i);
}
}
public static void main(String[] args) {
Test03Sleep test03Sleep = new Test03Sleep();
Thread t = new Thread(test03Sleep,"线程");
t.start();
}
}
运行结果:(每隔一秒输出一个)
线程0
线程1
线程2
Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。
6、线程的礼让。
yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执行。
实际上,当某个线程调用了yield()方法暂停之后,优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程更有可能获得执行的机会,当然,只是有可能,因为我们不可能精确的干涉cpu调度线程。
在线程操作中,也可以使用yield()方法,将一个线程的操作暂时交给其他线程执行。让CPU重新调度,但礼让不一定成功
public class Li implements Runnable{
@Override
public void run() {
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"运行"+i);
if (i==3){
System.out.println("执行礼让");
Thread.yield();
}
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(new Li(),"一");
Thread thread2 = new Thread(new Li(),"二");
thread1.start();
thread2.start();
}
}
结果:
一运行0
二运行0
二运行1
一运行1
二运行2
一运行2
一运行3
执行礼让
二运行3
执行礼让
一运行4
二运行4
3)sleep和yield的区别
① sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态,所以有可能刚进入就绪状态,又被调度到运行状态。
② sleep方法声明抛出了InterruptedException,所以调用sleep方法的时候要捕获该异常,或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。
③ sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法来控制并发线程的执行。
7、线程的中断:
public class Duan implements Runnable{
@Override
public void run() {
System.out.println("执行run方法");
try {
Thread.sleep(10000);
System.out.println("线程休眠完成");
} catch (InterruptedException e) {
System.out.println("线程休眠被中断");
return; //返回程序的调用处
}
System.out.println("线程正常停止");
}
public static void main(String[] args) {
Duan duan = new Duan();
Thread thread = new Thread(duan,"线程");
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt(); //3s后中断线程
}
}
结果:
执行run方法
线程休眠被中断
在java程序中,只要前台有一个线程在运行,整个java程序进程不会消失,所以此时可以设置一个后台线程,这样即使java进程消失了,此后台线程依然能够继续运行。
public class Hou implements Runnable{
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+"运行中");
}
}
public static void main(String[] args) {
Hou hou = new Hou();
Thread thread = new Thread(hou,"线程");
thread.setDaemon(true);
thread.start();
}
}
虽然有个死循环,但是程序还是会终止的,因为死循环中的线程操作已经设置为后台运行。
8、线程合并---join
(1)概念
线程的合并的含义就是将几个并行线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能,注意,他不是静态方法。
换句话说就是:
当B线程执行到了A线程的 .join方法时,B线程就会等待,等A线程都执行完毕,B线程才会执行。join可以用来临时加入线程执行。
线程合并方法:。
他有三个重载方法:
当前线程等待加入该线程后面,等代该线程停止
void join()
当前线程等待该线程终止的时间最长为millis毫秒。
如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待CPU调度
void join(long millis)
等待该线程终止的时间最长为 millis 毫秒 + nanos
纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu
调度
void join(long millis,int nanos)
具体代码如下:
public class Threadjoin {
public static void main(String[] args) {
Threadjoin join = new Threadjoin();
Thread thread =join.new ZiThread();
thread.start();
for (int i=0;i<20;i++){
System.out.println("主线程第"+i+"次执行");
if (i>2)
try {
thread.join();
//thread线程合并到主线程中,主线程停止执行过程,转而执行thread线程,直到执行thread完毕后继续
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ZiThread extends Thread{
@Override
public void run() {
for (int i= 0;i<10;i++){
System.out.println("线程1第"+i+"次执行");
}
}
}
}
执行结果:
主线程第0次执行
主线程第1次执行
主线程第2次执行
主线程第3次执行
线程1第0次执行
线程1第1次执行
线程1第2次执行
线程1第3次执行
线程1第4次执行
线程1第5次执行
线程1第6次执行
线程1第7次执行
线程1第8次执行
线程1第9次执行
线程1第10次执行
线程1第11次执行
线程1第12次执行
线程1第13次执行
线程1第14次执行
线程1第15次执行
线程1第16次执行
线程1第17次执行
线程1第18次执行
线程1第19次执行
主线程第4次执行
主线程第5次执行
主线程第6次执行
主线程第7次执行
主线程第8次执行
主线程第9次执行
主线程第10次执行
主线程第11次执行
主线程第12次执行
主线程第13次执行
主线程第14次执行
主线程第15次执行
主线程第16次执行
主线程第17次执行
主线程第18次执行
主线程第19次执行
主线程第20次执行
主线程第21次执行
主线程第22次执行
主线程第23次执行
主线程第24次执行
9、后台(守护)线程
守护线程使用的情况较少,但是也并非是没有用,举例来说:在做数据库应用的时候,使用的数据库连接池,连接池本身也包含很多后台线程,监控连接的个数,超时时间以及状态等等。 还有JVM的垃圾回收以及内存管理等等都是守护线程。
默认情况下,Java进程需要等待所有线程都运行结束,才会结束,有一种特殊线程叫后台(守护)线程,只有所有非守护线程都结束之后,即使他没有结束,也会强制结束。
方法:
调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
将该线程标记为守护线程或者用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。
该方法必须在启动线程前调用。该方法首先调用该线程的checkAccess方法,且不带任何参数。这可能抛出SecurityException(在当前线程)。
public final void setDaemon(boolean on)
参数:
on - 如果为 true,则将该线程标记为守护线程。
抛出:
IllegalThreadStateException - 如果该线程处于活动状态。
SecurityException - 如果当前线程无法修改该线程。
守护线程的用途
守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。
java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了
10、线程阻塞
线程的阻塞分为多种,从操作系统层面和Java层面阻塞的定义可能不同,但是广义上使得线程阻塞方式有下面几种:
1、BIO阻塞,即使用了阻塞式IO流
2、sleep(long time)让线程休眠进入阻塞状态
3、a.joinI()调用该方法的线程进入阻塞,等待a线程执行完恢复运行
4、sychronized或ReentrantLock造成线程进未获得锁入阻塞状态
5、获得锁之后调用wait()方法,也会让线程进入阻塞状态
6、LockSupport.park()让线程进入阻塞状态
11、线程核心方法总结(摘自https://www.cnblogs.com/zhangyinhua/p/14512735.html)
11.1、六种线程状态和方法的对应关系以及方法总结
1)Thread类中的核心方法
方法名称 | 是否static | 方法说明 |
---|---|---|
start() | 否 | 让线程启动,进入就绪状态,等待cpu分配时间片 |
run() | 否 | 重写Runnable接口的方法,线程获取到cpu时间片时执行的具体逻辑 |
yield() | 是 | 线程的礼让,使得获取到cpu时间片的线程进入就绪状态,重新争抢时间片 |
sleep(time) | 是 | 线程休眠固定时间,进入阻塞状态,休眠时间完成后重新争抢时间片,休眠可被打断 |
join()/join(time) | 否 | 调用线程对象的join方法,调用者线程进入阻塞,等待线程对象执行完或者到达指定时间才恢复,重新争抢时间片 |
isInterrupted() | 否 | 获取线程的打断标记,true:被打断,false:没有被打断。调用后不会修改打断标记 |
interrupt() | 否 | 打断线程,抛出InterruptedException异常的方法均可被打断,但是打断后不会修改打断标记,正常执行的线程被打断后会修改打断标记 |
interrupted() | 否 | 获取线程的打断标记。调用后会清空打断标记 |
stop() | 否 | 停止线程运行 不推荐 |
suspend() | 否 | 挂起线程 不推荐 |
resume() | 否 | 恢复线程运行 不推荐 |
currentThread() | 是 | 获取当前线程 |
2)Object中与线程相关方法
方法名称 | 方法说明 |
---|---|
wait()/wait(long timeout) | 获取到锁的线程进入阻塞状态 |
notify() | 随机唤醒被wait()的一个线程 |
notifyAll() | 唤醒被wait()的所有线程,重新争抢时间片 |