JAVA多线程的实现方式是JAVA基础的一个重点,看过好多次,但不经常用就容易忘记,今天做一个总结,算是把JAVA基础再夯实一下。
Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。
1、继承Thread类创建线程
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:
public class MyThread extends Thread {
public void run() {
while(true) {
System.out.println(this.getName()+" running...");
try {
Thread.sleep(1000); // 休息1000ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[]args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
//执行结果
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...
Thread-1 running...
Thread-0 running...
注意:此时除了创建的两个线程外,还有main线程也就是主线程在执行,另外还有负责垃圾回收的线程同样在执行,这里不予研究。线程的名字在创建线程是默认配置,也可以在创建线程时传入线程名字,例如
MultiThreadDemo td1 = new MultiThreadDemo("t1"); // 指定线程的名字
2、实现Runnable接口
实现Runnable接口也是一种常见的创建线程的方式。使用接口的方式可以让我们的程序降低耦合度。Runnable接口中仅仅定义了一个方法,就是run。
其实Runnable就是一个线程任务,线程任务和线程的控制分离,这也就是上面所说的解耦。我们要实现一个线程,可以借助Thread类,Thread类要执行的任务就可以由实现了Runnable接口的类来处理。 这就是Runnable的精髓之所在!
例如:
//集成Runnable接口定义任务类
public class ThreadTask implements Runnable {
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName()+" is running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//在main方法中创建线程去完成该任务
public class Main extends ThreadTask {
public static void main(String[]args) {
ThreadTask task = new ThreadTask();//新建任务类
Thread t1 = new Thread(task);
t1.start();
Thread t2 = new Thread(task);
t2.start();
}
}
下面通过多个售票员同时执行售票任务的例子来认识多线程
//创建售票任务
public class TicketSailTask implements Runnable{
public static int number = 100;//总共有100张票
public Object lock = new Object();//创建锁,防止多个线程同时卖同一张票
@Override
public void run() {
while(true) {
//进行同步
synchronized(lock) {
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
}
if(number>0) {
System.out.println(Thread.currentThread().getName()+"正在卖第"+number+"张票");
number--;
}else {
break;
}
}
}
}
//创建多个线程,执行任务
public class Main extends ThreadTask {
public static void main(String[]args) {
TicketSailTask task = new TicketSailTask();//新建任务类
Thread t1 = new Thread(task,"小王");
t1.start();
Thread t2 = new Thread(task,"小刘");
t2.start();
Thread t3 = new Thread(task,"小李");
}
}
//执行结果
小王正在卖第100张票
小刘正在卖第99张票
小刘正在卖第98张票
小王正在卖第97张票
小刘正在卖第96张票
小王正在卖第95张票
小刘正在卖第94张票
小刘正在卖第93张票
小王正在卖第92张票
小刘正在卖第91张票
小王正在卖第90张票
小刘正在卖第89张票
小王正在卖第88张票
小刘正在卖第87张票
。。。。
3、实现Callable接口实现多线程
与实现Runnable接口实现多线程类似,也有不同
相同点:
两者都可用来编写多线程程序;
两者都需要调用Thread.start()启动线程;
不同点:
两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
package com.callable.runnable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableImpl implements Callable {
public CallableImpl(String acceptStr) {
this.acceptStr = acceptStr;
}
private String acceptStr;
@Override
public String call() throws Exception {
// 任务阻塞 1 秒
Thread.sleep(1000);
return this.acceptStr + " append some chars and return it!";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable callable = new CallableImpl("my callable test!");
FutureTask task = new FutureTask<>(callable);
long beginTime = System.currentTimeMillis();
// 创建线程
new Thread(task).start();
// 调用get()阻塞主线程,反之,线程不会阻塞
String result = task.get();
long endTime = System.currentTimeMillis();
System.out.println("hello : " + result);
System.out.println("cast : " + (endTime - beginTime) / 1000 + " second!");
}
}
//执行结果
hello : my callable test! append some chars and return it!
cast : 1 second!
Process finished with exit code 0
4、通过线程池来实现多线程
public class ThreadDemo05{
private static int POOL_NUM = 10; //线程池数量
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i = 0; i
这里增加一点线程状态的知识
线程总共有5大状态,通过上面第二个知识点的介绍,理解起来就简单了。
新建状态:新建线程对象,并没有调用start()方法之前
就绪状态:调用start()方法之后线程就进入就绪状态,但是并不是说只要调用start()方法线程就马上变为当前线程,在变为当前线程之前都是为就绪状态。值得一提的是,线程在睡眠和挂起中恢复的时候也会进入就绪状态哦。
运行状态:线程被设置为当前线程,开始执行run()方法。就是线程进入运行状态
阻塞状态:线程被暂停,比如说调用sleep()方法后线程就进入阻塞状态
死亡状态:线程执行结束