一.线程创建
1)继承Thread类
创建一个类,同时继承自标准库的Thread类,在类中重写run方法。
然后,在main函数中实列化刚才创建的类,并通过实例.start()创建并运行线程。
例:
class MyThread extends Thread{
@Override
public void run() {
System.out.println("Hello Thread");
}
}
public class Demo1 {
public static void main(String[] args) {
MyThread t1=new MyThread();
t1.start();
}
}
注意!!上述代码中run方法并没有手动调用,但最终也执行了。像这样用户手动定义,却没有手动调用,最终被系统/库/框架进行调用的方法被称为“回调函数”(callback)。
2)实现Runnable接口
创建一个类,实现Runnable接口,在里面重写run方法。
在main函数中,实列化创建的类,并将实列填入Thread()中。
例:
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("Hello Thread");
}
}
public class Demo2 {
public static void main(String[] args) {
MyRunnable myRunnable=new MyRunnable();
Thread t1=new Thread(myRunnable);
t1.start();
}
}
其中,Runnable就是用来描述要执行的任务是什么。
线程要执行的任务通过Runnable来描述,而不是通过Thread自己来描述。
通过Runnable,有利于代码解耦合。
3)通过匿名内部类来实现
public class Demo3 {
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run() {
System.out.println("Hello Thread");
}
};
t.start();
}
}
匿名内部类,就是“一次性使用的类”,用完就丢。
4)使用lambda创建线程
public class Demo4 {
public static void main(String[] args) {
Thread t1=new Thread(()->{
System.out.println("Hello Thread");
});
t1.start();
}
}
本质上还是匿名内部类,不过更为简化。
二.线程的启动
启动线程一般用start()方法即可
public static void main(String[] args) {
Thread t1=new Thread();
t1.start();
}
注:start与run的区别
作用功能不同:
- run方法的作用是描述线程具体要执行的任务;
- start方法的作用是真正的去申请系统线程
运行结果不同:
- run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
- start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。
三.获取当前线程引用
currentThread()是Thread类的静态方法,能获取到调用这个方法的实列。
public class Demo2 {
public static void main(String[] args) {
Thread t1=new Thread(()->{
System.out.println("t1线程名:"+Thread.currentThread().getName());
});
t1.start();
System.out.println("main线程:"+Thread.currentThread().getName());
}
}
四.中断线程
线程中断指让一个线程结束。
列如有两个线程A和B,B在运行,A想让B结束,核心就是A想办法让B的run方法执行完毕,而不是当B运行一半,A强制让其结束。
JAVA中,结束线程是一个"温柔"的过程,而不是直接粗暴的。
以下为中断线程的一种方法:
public class Demo3 {
private static boolean isQuite=false;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
while (!isQuite){
System.out.println("Hello t1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程t1结束");
});
t1.start();
Thread.sleep(3000);
System.out.println("main线程尝试结束t1线程");
//修改变量,实现中断线程
isQuite=true;
}
}
Thread类里有个成员变量interruptued,bool类型,初始值为false
通过使用isInterrupted()方法就会设置以上标准位
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
Thread currentThread=Thread.currentThread();
while (!currentThread.isInterrupted()){
System.out.println("Hello t1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
});
t1.start();
Thread.sleep(3000);
t1.interrupt();
}
}
//运行时报错
运行时发现,代码抛出异常。
由于判断isInterrupted()和执行打印时间太快,整个循环大多数时间都花在sleep上。
main函数调用interrupt时,t1线程大概率在sleep状态
此时Interrupt不仅能设置标准位,还会唤醒sleep操作(会抛出异常 )
注:当sleep被唤醒后,会清空刚才设置的interrupted标准位
因此,我们可以在catch中调整代码,取消抛出异常,并添加break结束循环。
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
Thread currentThread=Thread.currentThread();
while (!currentThread.isInterrupted()){
System.out.println("Hello t1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("调整catch部分");
break; //退出循环
}
}
System.out.println("t1线程结束");
});
t1.start();
Thread.sleep(3000);
t1.interrupt();
}
}
上述规则,可以让程序员有更多操作空间。
五.线程的属性与获取方法
1)获取线程的ID
可通过getID()方法获得线程的ID
public class Demo1 {
public static void main(String[] args) {
Thread t1=new Thread(()->{
});
t1.start();
System.out.println("线程t1id为:"+t1.getId());
System.out.println("main线程id为"+Thread.currentThread().getId());
}
}
运行结果:
2)获取线程的名称
通过getName()方法获取线程的名字
public class Demo1 {
public static void main(String[] args) {
Thread t1=new Thread(()->{
});
t1.start();
System.out.println("t1线程名称:"+t1.getName());
}
}
如不主动定义,线程名字默认为Thread-0,我们可以在线程里命名线程。
public class Demo1 {
public static void main(String[] args) {
Thread t1=new Thread(()->{
},"自定义名字");
t1.start();
System.out.println("t1线程名称:"+t1.getName());
}
}
3)获得线程的状态
通过getState()方法获取线程的状态
public class Demo1 {
public static void main(String[] args) {
Thread t1=new Thread(()->{
});
t1.start();
System.out.println("t1线程状态:"+t1.getState());
}
}
在JAVA中,线程状态分为六种
-
新建(NEW):创建未被使用的线程处于这种状态。
public class Demo1 { public static void main(String[] args) { Thread t1=new Thread(()->{ }); //t1线程没有start()创建 System.out.println("t1线程状态:"+t1.getState()); } }
- 运行(RUNNABLE):调用start()后,线程就绪(没运行,但随时可以运行)或正在运行的状态都为RUNNABLE。
public class Demo1 { public static void main(String[] args) { Thread t1=new Thread(()->{ }); t1.start(); System.out.println("t1线程状态:"+t1.getState()); } }
- 无限期等待(WAITING):线程不会被分配cpu执行时间,无限的等待。使用无参数的join()时可能会进入此状态。
public class Demo1 { public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(()->{ while (true){ } }); Thread t2=new Thread(()->{ try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t2.start(); Thread.sleep(2000); System.out.println("t2线程状态:"+t2.getState()); } }
- 限期等待(TIMED_WAITING):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式地唤醒,在一定时间之后它们会由系统自动唤醒。(sleep()和带参数的join()可使线程进入此状态)
public class Demo1 { public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(()->{ while (true){ } }); Thread t2=new Thread(()->{ try { t1.join(3000); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t2.start(); Thread.sleep(2000); System.out.println("t2线程状态:"+t2.getState()); } }
- 阻塞(BLOCKED):线程被阻塞了
-
结束(TERMINATED):已终止线程的线程状态,线程已经结束执行。
4)获取进程优先级
线程通过getPriority()方法获取线程优先级。
设置不同优先级会影响系统调度。
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
});
t1.start();
System.out.println("线程t1优先级:"+t1.getPriority());
System.out.println("main线程优先级:"+Thread.currentThread().getPriority());
}
}
5)获取线程是否为后台线程
何为前台线程与后台线程
再介绍方法前,先介绍何为前台线程与后台线程
前台线程:某个线程在运行过程中,能够阻止进程结束,为"前台线程"(main线程)。
后台线程(守护进程):线程在运行过程中,不能阻止进程结束,随着进程一起结束,为“后台线程”。
简单来说,如果当main线程结束后,进程还在进行,那该线程为前台线程
如以下代码
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
while(true){
System.out.println("Hello Thread");
}
});
t1.start();
System.out.println("main线程结束");
}
}
main线程已经结束,t1线程却还在运行,此时t1为前台线程
当我们用setDaemon()将t1线程设为后台线程时,main线程结束将带走t1线程
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
while(true){
System.out.println("Hello Thread");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//将t1设为后台进程
t1.setDaemon(true);
t1.start();
System.out.println("main线程结束");
}
}
获取是否为后台线程
通过isDaemon()来获取线程是否为后台进程,若是返回true,否返回false
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
while(true){
System.out.println("Hello t1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2=new Thread(()->{
while(true){
System.out.println("Hello t2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.setDaemon(true);
t1.start();
t2.start();
System.out.println("t1线程是否为后台线程"+t1.isDaemon());
System.out.println("t2线程是否为后台线程"+t2.isDaemon());
}
}
6)获取线程是否存活
代码中,创建的NEW Thread对象,生命周期和内核实际中的线程是不一定一样的。
可能会出现Thread对象仍然存在,但内核中的线程不存在这种情况(不会出现Thread对象不存在,但内核线程存在)。
通过isAlive()获取线程是否存活
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("Hello t1");
}
});
System.out.println("t1线程创建前的存活状态"+t1.isAlive());
t1.start();
System.out.println("t1线程创建后的存活状态"+t1.isAlive());;
Thread.sleep(4000);
System.out.println("t1线程结束后的存活状态"+t1.isAlive());
}
}
六.线程休眠
通过Thread.sleep()可以使线程休眠(阻塞)
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
long start=System.currentTimeMillis(); //获取线程开始时间
Thread.sleep(2000); //休眠2000ms
long end=System.currentTimeMillis();
System.out.println("线程运行时间:"+(end-start));
}
}
sleep 的原理就是让原本在 就绪队列 中的线程,转移到 阻塞队列 中去,等 sleep 的时间到了,再转移到 就绪队列 中。
七.线程等待
多线程并发编程,一般情况下线程的调度顺序是不确定的。
通过join()可以干预线程的结束顺序
以下代码,主线程等待t1线程结束后再结束
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("Hello t1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1线程结束");
});
t1.start();
System.out.println("主线程开始等待");
t1.join();
System.out.println("主线程结束等待");
}
以上代码join不带参数,会等到特定线程结束后继续运行,为"死等"。
join还有带参数的版本,在()内填写参数,超过时间后,等待线程也会执行。
以上便是全部内容,若有不对,欢迎指正