1.继承Thread类
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务(执行体)。
创建Thread子类的实例,即创建了线程对象,调用线程对象的start()方法来启动该线程。
public class ThreadTest extends Thread {
int i = 0;
// 重写run方法,run方法的方法体就是现场执行体
public void run() {
for (; i < 100; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
if (i == 50) {
new ThreadTest().start();
new ThreadTest().start();
}
}
}
}
2.实现Runnable接口
重写该接口的run()方法,创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象,调用线程对象的start()方法来启动该线程。
用于自己的类已经extends另一个类(此时无法再extends Thread),所以可以实现一个Runnable接口。
public class RunnableThreadTest extends *** implements Runnable {
private int i;
public void run() {
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
//需要首先实例化一个Thread
RunnableThreadTest rtt = new RunnableThreadTest();
new Thread(rtt, "新线程1").start();
new Thread(rtt, "新线程2").start();
}
}
}
}
3.通过Callable和Future创建线程
有返回值的任务必须实现Callable接口(无返回值实现Runnable接口),执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
实现call()方法,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值,使用FutureTask对象作为Thread对象的目标创建并启动新线程。
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable<Integer> {
public static void main(String[] args) {
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> future = new FutureTask<>(ctt);
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "的循环变量i的值==" + i);
if (i == 5) {
new Thread(future, "有返回值的线程=").start();
}
}
try {
//获取Callable任务返回的Object
System.out.println("子线程的返回值:" + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}
4.基于线程池的方式
使用线程池创建线程就跟使用数据库连接池类似,都是为了节省资源。需要注意的是,Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是ExecutorService。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceTest {
public static void main(String[] args) {
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while (true) {
threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
>>>>>创建方式的对比<<<<<
使用继承Thread类的方式创建多线程
A.优势是:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
B.劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。
采用实现Runnable、Callable接口的方式创见多线程
优势是:
1)适合多个线程进行资源共享
2)避免java中单继承的限制
3)增加程序的健壮性,代码和数据独立
4)线程池只能放入Runable或Callable接口实现类,不能直接放入继承Thread的类
劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
Callable和Runnable有何区别?
1)Callable重写的是call()方法,Runnable重写的方法是run()方法
2)call()方法执行后有返回值(返回值是泛型),run()方法没有返回值(void)
3)call()方法可以抛出异常,run()方法不可以