|
JavaEE
JavaEE——进程调度
JavaEE——进程与线程的关系
#
继承Thread类
1)继承Thread类来创建一个线程类
class MyThread extends Thread {
//重写父类Thread中的run方法,run里面的逻辑,就是这个线程需要执行的工作
@Override
public void run() {
System.out.println("hello thread");
}
}
2)创建MyThread类的实例,调用start方法启动线程
public class Demo1 {
public static void main(String[] args) {
MyThread t = new MyThread();//创建一个实例,并不会再系统中真的创建一个线程
t.start();//线程开始运行,调用start方法的时候才真正创建出一个新的线程
System.out.println("hello main");
}
}
# 注意事项 #
Java
程序,就是启动了一个进程main
方法所在的线程(也叫做主线程)"并发执行"
的关系(“并发= 并发 + 并行”)"随机"
的过程,main线程和和MyThread创建出来的新线程谁先打印是随机的一个多线程程序
class MyThread extends Thread {
@Override
public void run() {
while(true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);//sleep表示"休眠",让线程阻塞1000ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
while(true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用jconsole
命令观察线程
找到jconsole.exe,双击打开
选择本地进程,选择刚刚写的Demo1,点击链接,选择不安全的链接
点击线程,我们可以看到左下角有许多线程
#
实现Runnable接口
1)实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("hello thread");
}
}
2)创建Thread类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数, 并调用 start 方法.
public class Demo2 {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
System.out.println("hello main");
}
}
那么这样写的好处是什么呢?
将任务提取出来, 目的是为了解耦合.
上面一种继承Thread写法, 就把线程要完成的工作和线程本身, 耦合在一起了, 假设未来要对这个代码进行调整(不用多线程了), 代码改动就会比较大.
如果想搞多个线程, 都干一样的活, 这时也是更合适使用Runnable的.
对比上面两种方法
继承Thread类 | 实现Runnable接口 |
---|---|
工作和线程本身耦合在一起 | 低耦合 |
直接使用 this 就表示当前线程对象的引用 | this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread() |
#
匿名内部类创建Thread子类对象
public class Demo3 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("hello thread");
}
};
t.start();
}
}
#
匿名内部类创建 Runnable 子类对象
public class Demo4 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello thread");
}
});
t.start();
}
}
#
lambda 表达式创建 Runnable 子类对象
public class Demo5 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("hello thread");
});
t.start();
}
}
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
ID是Java中给Thread对象安排的身份标识, 身份标识可以有多个, 在不同的环境下, 使用不同的标识(比如说一个线程在JVM
中有一个id, 在操作系统的线程API
中有一个id, 在内核PCB
中还有一个id)
名称是在构造方法中, 指定的name, 在各种调试工具中用到(比如说jconsole
)
getState()获取线程的状态(下一篇博客会讲解)
getPriority()获取线程优先级
isDaemon()判断该线程是否是守护线程(后台线程), 我们默认创建的线程是 "前台线程"
(前台线程会阻止进程退出, 如果main运行完了, 前台线程还没完, 进程不会退出). 如果是后台线程
, 后台线程不阻止进程退出(如果main等其他前台线程执行完了, 即使后台线程没执行完,进程也会退出)
我们可以用setDaemo将默认进程设置成后台线程, 设置操作需要在
start
之前, 如果线程启动了, 就改不了t.setDaemon(true);
是否存活,简单的理解,即为 run
方法是否运行结束了
线程中断的问题, 下面进一步说明
public class Demo7 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"我的Thread");
t.start();
System.out.println(t.getId());//ID
System.out.println(t.getName());//名称
System.out.println(t.getPriority());//优先级
System.out.println(t.getState());//状态
System.out.println(t.isDaemon());//是否后台线程
System.out.println(t.isAlive());//是否存活
System.out.println(t.isInterrupted());//是否被中断
}
}
调用 start 方法, 才真的在操作系统的底层创建出一个线程.
如果我们不调用start方法, 只调用run
方法
class MyThread extends Thread {
@Override
public void run() {
while(true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.run();
//t.start();
while(true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果展示, 我们的运行结果只有"hello thread"
, 并没有"hello main"
, 只调用run方法并没有创建线程, 他只是在main这个线程里调用run方法.
run方法执行完, 线程就结束了. 那我们有什么办法可以让线程提前结束呢?
通过线程中断的方法来进行, 其本质仍是让run方法尽快结束, 而不是run执行一半, 强制结束.
1)自己定义一个标志位, 作为线程结束的标志
public class Test {
private static boolean isQuit = false;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(!isQuit) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t线程执行完了");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
System.out.println("设置让t线程结束!");
}
}
2)使用标准库中自带的标志位, 在主线程中,通过t.interrupt
中断线程
public class Demo8 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t线程执行完了");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();//中断线程,设置标志位为true
System.out.println("设置让t线程结束!");
}
}
运行该程序, 我们可以看到触发了异常, 但线程仍在运行
interrupt 方法的行为, 有两种情况:
- t 线程在
运行状态
.会设置标志位为true- t 线程在
阻塞状态
(比如说sleep), 不会设置标志位, 而是触发一个InterruptedException
, 这个异常会把sleep提前唤醒
由于代码中只是打印了日志, 并没有结束循环, 因此线程还是在继续执行, 所以我们需要由线程自身的代码来判定处理.
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
1.立即结束进程
break;
//2.不做理会,线程继续执行
//3.线程稍后处理
//Thread.sleep(1000);
//break;
}
}
System.out.println("t线程执行完了");
});
线程之间的调度顺序, 是不确定的. 我们可以通过一些特殊手段, 来对线程的执行顺序做出干预.
join方法, 可以控制线程之间的结束顺序
在main中调用t.join方法, 让main阻塞等待, 等到 t 执行完了, main才继续执行
public class Test {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
System.out.println("main线程 join之前");
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程 join之后");
}
}
运行结果展示
# 注意 #
在调用 jion 之前, 若 t 线程已经结束了, 此时 join 不需要阻塞等待
join的几种版本
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理, 但更高精度 |
在操作系统组织这些 线程的 PCB的时候, 是会有多个链表的.
A线程调用了 sleep .
则这个PCB 就会被移动到另外的 “阻塞队列” 中.
当A线程, sleep 的时间到了, 就会被移动到之前的就绪队列
# 注意 #
移回就绪队列, 不代表立即就能够上CPU执行. 还得看系统啥时候调度这个线程.(sleep(1000), 不一定是只休眠了1000, 一般要略多于1000)
public static Thread currentThread();//返回当前线程对象的引用
|
以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!