文章目录
在操作系统中,线程是由系统内核提供的一些api在应用程序去调用的,而Java对其进行了进一步的封装(可移植性),封装成了Thread类,放在了java.lang的包内(java.lang内的类可以直接使用,无需引用)。
创建线程的方法(重点)
1.继承thread,重写run
class MyThread extends Thread{
@Override
public void run(){
// 该方法是线程的入口方法
}
}
// 调用
public static void main(String[] args){
Thread t = new MyThread();
t.start();
}
面试题->start()和run()异同->是否在系统中创造了新线程
- start 和 run 都是 Thread 的成员。
- run 只是描述了线程的入口(线程要做什么任务)。
- 如果只执行 run,并没有创建新线程。
- start 则是真正调用了系统API,在系统中创建了线程,让线程再调用run。
2.(继承thread)重写run,使用匿名内部类
// 调用
public static void main(String[] args){
Thread t = new Thread(){
@Override
public void run(){
// 该方法是线程的入口方法
}
};
t.start();
}
3.实现Runnable,重写run
class MyRunnable implements Runnable{
@Override
public void run(){
// 该方法是线程的入口方法
}
}
// 调用
public static void main(String[] args){
Runnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
}
- Runnable表示的是一个“可以运行的任务”,这个任务是交给线程负责执行。还是交给其他的任务来执行,Runnable并不关心
- 使用Runnable的写法,和直接继承Thread之间的区别,就是解耦合->把任务提取了出来,这样可以随时改成让其他代码执行该任务
4.(实现Runnable)重写run,使用匿名内部类
// 调用
public static void main(String[] args){
Runnable runnable = new Runnable(){
@Override
public void run(){
// 该方法是线程的入口方法
}
};
Thread t = new Thread(runnable);
t.start();
}
甚至还可以写为下面的样子
// 调用
public static void main(String[] args){
Thread t = new Thread(new Runnable(){
@Override
public void run(){
// 该方法是线程的入口方法
}
});
t.start();
}
5.lambda表达式
Thread t = new Thread(()->{
//此处直接写具体实现
});
t.start();
其他事项
命名
线程可以指定名字name,name不影响线程的执行,可以使用下面了两种方式来指定名字:
- Thread(String name) :创建线程对象,并命名
- Thread(Runnable target, String name):使用 Runnable 对象创建线程对象,并命名
线程常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程* | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
线程中断(终止、打断)
注:此时的中断与操作系统中的概念"中断"不同
Java中指的是让一个线程停止运行(销毁),在Java中,要销毁/终止线程,做法是比较唯一的,是想办法让run方法尽快执行结束(而不是像C++那样线程运行了一半直接进行中断),而且中断也不会强制停止,线程可以自行决定如何响应中断请求。
在操作系统中,中断通常是硬件中断或软件中断,是由硬件设备、定时器或软件异常等触发的异步信号。且常用于I/O操作(例如键盘输入、硬盘读写)、进程间通信等地方。
通过共享的标记来通知
注意,要给标记加上volatile来保证操作的原子性。
这样具有局限性,一些功能无法实现,最好是使用下一个方式。
class MyThread extends Thread{
public volatile boolean isQuit = false;
@Override
public void run(){
// 该方法是线程的入口方法
while(!isQuit){
}
}
}
// 调用
public static void main(String[] args){
Thread t = new MyThread();
t.start();
t.isQuit = true;
}
interrupt()方法来通知
通过 t.interrupt() 来中断:此方法就是将Thread对象内部(是否被中断)的标志位设置为true,当线程中检测到后,线程停止。
为什么示例代码不直接使用t.isInterrupted()而是使用Thread提供的类方法currentThread()?
因为t还没进行初始化,直接使用会报错,报错信息为:Variable ‘t’ might not have been initialized。
public static void main(String[] args){
Thread t = new Thread(new Runnable(){
@Override
public void run(){
// 该方法是线程的入口方法
while(!Thread.currentThread().isInterrupted()){
try{
Thread.sleep(5000);
}catch(InterruptException e){
e.printStackTrace();
break;//直接结束线程,若不添加,则忽视中断请求
}
}
}
});
t.start();
t.intereupted();
}
interrupt() 和 Thread.sleep()的关系
- Thread.sleep()方法会让线程处于阻塞态,一般情况下,sleep休眠时间结束,才能唤醒。
- 但是当interrupt()被调用时,JVM会立刻将线程从阻塞状态唤醒并抛出InterruptedException异常,此时,线程就有机会处理中断请求。
- interrupt()不会强制终止线程,而是通过设置中断标志来改变通知线程改变行为或停止。
- sleep方法抛出异常后,同时会清除刚才设置的标志位。此时仿佛“设置标志位没有生效”。
- 但是Java这么设置是为了给程序员提供更多的选择:
- 直接无视该中断操作,继续执行。
- 在catch代码块中添加break,让线程直接结束。
- 添加其他代码,做一些其他的工作,完成后在结束。
线程等待
引入
线程之间的并发执行是无序的(随机的),这会给实际代码的允许带来一定的“麻烦”。比如在部分情况下,线程的结束必须按照规定的顺序来执行,在此问题下,Java引入了线程等待,其就是为了让线程的结束按照规定的顺序来执行。
实现
在Thread中,通过join()实现线程等待的效果,比如说由线程A调用B.join(),此时是A等待B先结束,随后A才能结束(注意顺序!)。一但调用B.join(),线程A就会阻塞,B执行完毕后,A才会结束阻塞,下面将进一步通过代码来说明:
Thread t = new Thread(()->{
//此处直接写具体实现
});
// (在主线程中:)
t.start();
// 其他代码...
t.join();
线程状态
- NEW:Thread对象已经有了,但start方法还未被调用。
- TERMINATED:Thread对象还在,内核中的线程已经没了。
- RUNNABLE:就绪状态——线程已经在CPU上执行了/正在排队等待上CPU执行。
- TIMED_WAITING:阻塞,由于固定时间(sleep)产生的阻塞。
- WAITING:阻塞,由于不固定时间(wait)产生的阻塞。
- BLOCKED:阻塞,由于锁竞争(synchronized)导致的阻塞。