要想掌握多线程编程,线程的基本操作必不可少,下面就列举一下线程的一些基本操作
一、创建线程
创建线程的方式广泛来说一共有五种
- 通过继承Thread类来创建
public class Practice {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(("哈哈"));
}
}
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
}
}
Thread类可以理解为用户层的PCB,重写里面的run方法可以指定线程去做什么事,并且Thread里面也有id,name,state,优先级,是否后台进程等等。
需要注意的是必须要调用start方法才会在内核中真正创建出一个线程出来。此时才是并发执行的。
- 通过匿名内部类的方式继承Thread
public class Practice {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
System.out.println(("我是一个新线程"));
}
};
t.start();
}
}
- 显示创建一个类,然后实现Runnable接口,然后把实例关联到Thread中
public class Practice {
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(("我是一个新线程"));
}
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
- 也可通过匿名内部类的方式将Runnable实例关联到Thread中
public class Practice {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(("我是一个新线程"));
}
};
Thread t = new Thread(r);
t.start();
}
}
- 通过拉姆达表达式创建实例
public class Practice {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println(("我是新线程"));
});
t.start();
}
}
以上五种方式本质上是没有区别的,都是依靠Thread类,只不过指定线程执行任务的方式不同。
二、中断线程
- 让线程的任务被动做完,然后主动结束线程
public class Practice {
public static boolean flg = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!flg){
System.out.println(("我是新线程"));
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
System.out.println((t.getState()));//打印一次线程的状态,此时会发现该线程处于运行状态
sleep(1000);//为了让线程成功启动需要让新创建的线程先执行,不然的话主线程里面就已经把
//判定符置为true了
flg = true;
sleep(1000);//主线程置为true之后可能还会继续往下执行,此时新线程里面有可能还是处于循环中的。所以需要
//停止一会再去看新线程的状态
System.out.println((t.getState()));//将判定符置为true之后
}
}
这种方式比较温和,因为当把判定条件置为true之后,新线程里面可能还是处于循环中的,会把这次循环执行完才结束run方法,进而销毁线程。而不是直接就销毁线程
- 调用interrupt方法去主动中断
**public class ThreadDemo8 {//终止线程的第二种方式
static class MyThread extends Thread {
@Override
public void run() {
while ( !Thread.currentThread().isInterrupted()){//刚开始线程被创建出来的时候,此时线程没有被中断,也就是flase
System.out.println(("我在转账,稍等"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println(("转账被终止"));
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.start();
Thread.sleep(5000);
System.out.println(("有内鬼"));
t.interrupt();//强制中断当前线程
System.out.println(("结束"));
}
}**
如果新线程处于阻塞状态的时候切换到了主线程,然后主线程中调用interrupt方法之后,会向新线程中抛出异常,此时我们只需要在catch中break循环就可以直接销毁线程了。如果不是处于阻塞状态切换到主线程调用interrupt方法的话,会和第一种方式一样,将判断条件置为true。这里可以把判定条件换成Thread.interrupted,不过这样的话置为true之后又会还原为false,也就是只能一次性作为中断该线程的条件。后面去查看线程是否被中断的话返回结果仍然是false。
三、线程休眠
public class Practice {
static class MyThread extends Thread {
@Override
public void run() {
for(int i = 0;i<10;i++){
System.out.println(("我在运行"));
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.start();
Thread.sleep(1000);
System.out.println(("我在等待中"));
}
}
sleep方法可以让当前线程主动放弃CPU资源,等到时间到了之后再去参与调度,放弃的那段时间也就是处于阻塞队列中。并且休眠时间往往会大于指定时间,因为调度的时候也会有时间消耗。
四、线程等待
public class ThreadDemo9 {//线程等待
static class MyThread extends Thread {
@Override
public void run() {
for(int i = 0 ;i < 10;i++){
System.out.println(("我是线程1"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class MyThread2 extends Thread {
@Override
public void run() {
for(int i = 0; i<10;i++){
System.out.println(("我是线程2"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new MyThread();
Thread t2 = new MyThread2();
t1.start();
t1.join();//当程序执行到这行代码时,程序就阻塞了,也就是其他线程就暂时停止了,一直等到该线程执行完。
t2.start();//如果把这两个join放在一起的话,就会线程1和线程2同时运行,主要保证的是前两个线程执行完毕之后,主线程才执行
t2.join();
for(int i = 0; i<10;i++){
System.out.println(("我是主线程"));
Thread.sleep(1000);
}
}
}
线程之间是并发执行的,所以哪个线程先执行哪个线程后执行我们是感知不到的,只依赖于操作系统内核中的调度器。这是抢占式调度的一个重要特点。不过我们可以借助一些方法来让哪个线程处于阻塞队列中去,等条件结束之后再回到等待队列中来参与调度。join方法就是其中的一种,如果在主线程中调用了线程1的join方法,此时主线程就会进入阻塞队列,需要等线程1执行完毕才会重新进入等待队列。
五、查看线程状态
public class ThreadDemo10 {//线程状态
public static void main(String[] args) {
for(Thread.State state : Thread.State.values()){
System.out.println((state));
}
}
//可以把所以的线程状态一一列举出来
}
Java中线程的状态是通过枚举来表示的,大致可以分为六个状态
- NEW,处于这个状态的线程只是有一个Thread对象,内核中还没有真正创建出一个线程,也就是一个PCB实例。
- RUNNABLE,处于这个状态的进程代表正在执行或者处于等待队列中将要执行。
- BLOCKED,处于这个状态的进程代表正在处于等待加锁的状态,也是阻塞状态。
- TIMED_WAITING,这个状态是sleep方法所致,也是阻塞状态
- WAITING,这个状态是wait方法所致,也是阻塞状态
- TERMINATED,这个状态代表PCB已经被销毁了,但是Thread对象还是i存在的。也进一步说明了Thread对象和PCB的生命周期并不一样
其中,除了第一种和最后一种状态之外,线程都是处于存活状态。