目录
3、实现callable接口,重写call()方法,并使用Future来获取call()方法的返回结果
多个线程并发执行,可以充分利用CPU资源
线程的创建
java为多线程开发提供了技术支持,可以通过三种方式实现多线程
1、继承Thread类,重写run()方法
Thread是java的一个线程类,用于实现多线程
Thread实现多线程的主要步骤如下:
1、创建Thread这个线程类的子类,并重写run()方法
2、创建该子类的实例对象,调用start()方法启动线程
//创建Thread类的子类,并重写run()方法
class Mythread extends Thread {
//构造方法
public Mythread(String name) {
super(name);
}
@Override
public void run() {
int i = 0;
while (i++ < 5)
System.out.println(Thread.currentThread().getName() + "调用重写的run()方法");
//Thread类的currentThread()方法:静态方法,返回对当前正在执行的线程对象的引用。
//getName得到线程的名称
}
}
public class demo1 {
public static void main(String[] args) {
Mythread mythread=new Mythread("mythread"); //创建子类的实例
mythread.start();//调用start方法
Mythread mythread1=new Mythread("mythread1"); //创建子类的实例
mythread1.start();//调用start方法
}
}
main方法中创建了两个线程,线程名称是mythread和mythread1,两个线程通过start方法开启了线程,观察结果发现,两个进程交叉执行run()方法,实现了并发运行
缺点:java只支持单继承,如果一个类已经继承了一个父类,就无法继承Thread类
start()方法和run()方法的区别
重写run()方法,run()方法内部是线程执行的内容
start()方法是启动线程,调用这个方法,线程才会进入运行态
public class demo7 {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("run方法执行");
}
}
};
thread.start();
while (true) {
System.out.println("main方法执行");
}
}
}
结果:两个线程并发执行
public class demo8 {
public static void main(String[] args) {
Thread thread=new Thread(()->{
while(true)
System.out.println("run方法执行");
});
thread.run();
while(true){
System.out.println("main方法执行");
}
}
}
结果:只有thread内的run()方法执行:run方法不会创建新的线程,也就是run()方法在main线程内运行,单线程情况下顺序执行,而run方法内是一个死循环的代码
2、实现Runnable接口,重写run()方法
Runnable接口实现多线程的主要步骤如下:
1、创建Runnable接口的实现类对象
2、使用Thread类有参构造方法实现创建线程实例,并将Runnable接口的实现类对象作为参数传入
3、调用线程的start()方法启动线程
//1、创建Runnable的实现类
class MyRunnable implements Runnable{
@Override
public void run() {
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+"正在调用run()方法");
}
}
}
public class demo2 {
public static void main(String[] args) {
MyRunnable myRunnable=new MyRunnable(); //2、创建Runnable的实现类对象
Thread thread=new Thread(myRunnable,"thread");//3、实例化Thread类
thread.start();//4、开启线程
Thread thread1=new Thread(myRunnable,"thread1");//3、实例化Thread类
thread1.start();//4、开启线程
}
}
3、实现callable接口,重写call()方法,并使用Future来获取call()方法的返回结果
使用以上两种办法,重写run()方法,但是这个方法没有返回值,使用callable接口还可以得到返回结果
FutureTask类是Runnable类的子类,创建FutureTask类对象时,将callable接口的实现类作为参数,将运行这个参数
callable接口实现多线程的主要步骤如下:
1、创建callable接口的实现类,同时重写call()方法
2、创建callable接口的实现类对象
3、创建FutureTask线程处理结果类的有参构造方法来封装Callable接口实现类对象
4、使用参数是FutureTask类对象的Thred有参构造方法创建Thread线程实例
5、调用start()方法开启线程
//1、创建Callable接口的实现类,重写call()方法
class Mythread3 implements Callable<Object>{
@Override
public Object call() throws Exception {
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+"正在调用run()方法");
}
return i;
}
}
public class demo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//2、创建Callable接口的实现类的实例化对象
Mythread3 mythread3 = new Mythread3();
//3、使用FutureTask类封装Callable接口 FutureTask是泛型类
FutureTask<Object> futureTask = new FutureTask<Object>(mythread3);
//4、使用Thread类的(Runnable,name)的构造方法创建线程
Thread thread = new Thread(futureTask, "thread");
//5、开启线程
thread.start();
//FutureTask类的get()方法,在线程运行结束前一直阻塞,直到线程结束后返回结果
FutureTask<Object> futureTask1 = new FutureTask<Object>(mythread3);
Thread thread2 = new Thread(futureTask1, "thread1");
thread2.start();
System.out.println("thread的返回结果:" + futureTask1.get());
System.out.println("thread的返回结果:" + futureTask.get());
}
}
后台线程
后台线程不会影响到进程退出
前台线程影响进程退出
对于java程序来说,只要还有一个前台线程运行,这个进程就不会结束,如果一个进程中全都是后台进程,那么进程就会结束
public class demo6 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true)
System.out.println(Thread.currentThread().getName() +"调用run()方法");
});
thread.start();
for (int i = 0; i <5 ; i++) {
System.out.println("main方法正在运行");
}
}
}
主线程main,前台线程thread并发运行,因为thread是前台线程,前台线程不结束,进程不会退出,程序死循环
public class demo6 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true)
System.out.println(Thread.currentThread().getName() +"调用run()方法");
});
thread.setDaemon(true);//在线程开启之前,设置线程是后台线程
thread.start();
for (int i = 0; i <5 ; i++) {
System.out.println("main方法正在运行");
}
}
}
主线程main,后台线程thread并发运行,当主线程main运行结束,进程也就结束了,此时JVM通知后台线程结束,由于后台进程从接受指令到做出响应需要时间,因此后台线程thread执行一小段时间后结束
运行结果的最后一部分:
线程的生命周期和状态转化
当Thread对象创建完成时,线程的生命周期就开始了,当线程任务中的代码运行结束或者抛出异常、错误时,线程的生命周期结束
Java官方API将线程的整个生命周期分为六个状态,分别是NEW(新建状态),RUNNABLE(可运行状态),BLOCKED(阻塞状态),WAITING(等待状态),TIMED_WAITING(定时等待状态),TERMINATED(终止状态)
1、NEW(新建状态):创建Thread子类对象,就是完成了NEW(新建状态)
2、RUNNABLE(可运行状态):调用start方法,线程就会从NEW状态进入RUNNABLE(可运行状态),RUNNABLE(可运行状态)可以分为就绪态(没有得到CPU)和运行态(得到CPU)
3、BLOCKED(阻塞状态):处于运行状态的线程因为一些原因失去了CPU,暂时停止运行进入阻塞态
线程一般会在两种情况下进入阻塞态:
- 线程A运行过程中,试图获取同步锁时,却被线程B截获,此时JVM把当前线程A锁到对象的锁池中,线程A就会进入阻塞态
- 线程A运行过程中,发出I/O请求时,线程A也会陷入阻塞态
4、WAITING(等待状态)
Thread类:
Object类:
处于运行状态的线程因为调用了无时间参数限制的方法后如wait(),join()等方法,就会转化为等待态
处于等待状态的线程不能立即争夺CPU,必须等其他进程执行特定的操作后才可以争取CPU,进入运行态
例如:调用wait()方法而陷入等待状态的线程,必须等待其他线程调用notify()方法或者notifyAll()方法唤醒当前等待中的线程;
调用join()方法而陷入等待状态的线程,必须等待其他加入的线程终止
A线程中启动B线程,A、B线程并发运行,在线程A中执行B.jion(),那么A线程阻塞,让B线程插队运行
5、TIMED_WAITING(定时等待状态)
和等待状态一样,处于运行状态的线程只不过是调用了有时间参数限制的方法后陷入定时等待状态,如wait(long millis),join(long millis),sleep(long millis)
处于定时等待状态的线程不能立即争夺CPU,必须等其他进程执行特定的操作后才可以争取CPU,进入运行态
调用wait(long millis)方法而陷入等待状态的线程,必须等待其他线程调用notify()方法或者notifyAll()方法唤醒当前等待中的线程,或者等待限定时间结束之后也可以进行状态转化
6、TERMINATED(终止状态)
线程的run()方法或者call()方法,正常运行结束或者抛出异常、错误时,线程进入终止态,线程的生命周期结束
对于main这个主线程,就是main方法执行结束之后,main线程才结束
中断线程
1、设置标志
public class demo9 {
static boolean isquit = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (isquit) {
System.out.println(Thread.currentThread().getName() + "正在运行");
}
}
};
thread.start();
Thread.sleep(1000);
isquit = false;
System.out.println("终止thread线程");
}
}
但是这个方法并不严谨
2、
public class demo10 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {//检查线程是否被中断
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在运行");
}
});
thread.start();
//main线程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
抛出异常:
调用这个方法:如果线程处于运行态,那么可以终止;如果线程已经处于阻塞态或则等待态,那么就无法终止线程,抛出异常
捕捉到异常,就跳出循环
public class demo11 {
public static void main(String[] args) {
Thread thread=new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println(Thread.interrupted());
//调用类的静态方法,删除标志位
}
});
thread.start();
thread.interrupt();//设置线程中断
}
}
public class demo12 {
public static void main(String[] args) {
Thread thread=new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().isInterrupted());
//调用对象的方法,不删除标志位
}
});
thread.start();
thread.interrupt();//设置线程中断
}
}
Thread.interrupted()是Thread类的static方法,一个程序有一个标志位 Thread.currentThread().isInterrupted()这个是每一个实例化对象都有自己的标志位,会更加安全
线程调度
java虚拟机默认抢占式的调度方式,优先级高的线程更容易得到CPU
线程的优先级
优先级越高,那么进程被调度的可能性越大
Thread类中,设置了三个静态常量表示优先级:最高是10,中等是5,最低是1
程序运行过程中,每一个线程都具有优先级,但是每一个线程的优先级不是固定不变的,可以通过setPriority()方法设置
public class demo14 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 4; i++) {
System.out.println(Thread.currentThread().getName());
}
});
thread.setName("a");
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 4; i++) {
System.out.println(Thread.currentThread().getName());
}
});
thread1.setName("b");
thread.setPriority(10);//设置优先级10
thread1.setPriority(1);//设置优先级1
thread.start();
thread1.start();
}
}
可以发现,优先级越高,获取CPU的可能性就越大。但是,java中提供的10个优先级需要操作系统的支持,不同的操作系统对优先级的支持是不一样的,不能和提供的10个优先级一一对应
因此:多线程的功能实现一定不能依赖于线程的优先级
进程插队---join()方法
当在某一个线程中,调用其他线程的jion()方法时,调用的线程将会被阻塞,直到被join()方法加入的线程执行完成后,它才会继续运行
A线程中启动B线程,A、B线程并发运行,在线程A中执行B.jion(),那么A线程阻塞,让B线程插队运行
public class demo13 {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"正在运行"+i);
}
}
};
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("main()方法执行"+i);
if(i==2) {
try {
thread.join();//thread线程调用join()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
main线程内开启了一个thread线程,这两个线程并发运行
当main线程中i=2时,thread线程执行jion方法,这时,main线程就会陷入阻塞,直到thread线程运行结束,main线程才可以继续执行
public class demo13 {
public static void main(String[] args) {
Thread thread = new Thread("thread0") {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行" + i);
}
}
};
thread.start();
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行" + i);
if(i==2) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
thread1.start();
}
}
main线程内有两个线程thread0和thread1,在thread1中当i=2时,调用thread0的join()方法,那么thread1陷入阻塞,thread0这个线程插队运行,直到thread0这个线程结束之后,thread1才可以继续执行
Thread类除了提供无参数的join()方法外,还提供了带有时间参数的join(long millis)方法,表示最多等待线程多长时间
jion(1000)表示1s内,如果线程结束了,直接结束这个线程;1s内没有结束,也直接结束线程,
线程休眠---sleep()方法
如果想要人为地控制线程的执行顺序,让正在运行的线程暂停,从而其他线程就有了可以获取cpu的机会,就可以调用sleep()方法
sleep(200),哪怕其他线程在200ms内终止了,这个线程也仍需要等待至200ms结束后才可以转化为就绪态
public class demo15 {
public static void main(String[] args) {
Thread thread=new Thread("A"){
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
};
Thread thread1=new Thread("B"){
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
if(i==2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
thread.start();
thread1.start();
}
}
线程B在i=2时调用sleep()方法,线程B陷入阻塞,线程A可以去获取CPU,在500ms过了之后,线程B进入就绪态,如果得到CPU,就可以运行