最近开始了解多线程,发现内容太多,那就一点一点来吧。先了解最基础的,多线程有几种实现方式?从网上了解到,多线程有3种实现方式。
1、继承Thread类、
2、实现Runnable接口
3、Callable接口
来个小例子,具体实现如下:
通过继承 Thread 类,并重写它的 run 方法,我们就可以创建一个线程。
(线程创建后,使用start()
方法才是启动一个新的线程,不能直接调用Thread子类中重写的方法run()
。)
// 定义一个继承了Thread的类
class MyThread extends Thread{
private static int ticket =10;
// 重写run方法,实现自己的业务逻辑
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (this.ticket > 0) {
System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
}
}
}
}
public class ThreadDemo{
public static void main(String[] args){
// 直接 new 一个Thread对象,调用Thread对象的 start 方法,注意不能调用 run 方法
new MyThread().start();
}
}
此处用的是1个线程来调用。
new MyThread().start();
如果多个线程,那么多创建几个子类对象。
new MyThread().start(); new MyThread().start(); new MyThread().start();
通过实现 Runnable ,并实现 run 方法,也可以创建一个线程。
// 定义一个实现了 Runnable 接口的类
class RunnableTest3 implements Runnable{
// 重写run方法,实现自己的业务逻辑
private int ticket =10;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (this.ticket > 0) {
System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
}
}
}
}
public class RunnableDemo3 {
public static void main(String[] args){
//创建Runnable 对象
RunnableTest3 rt=new RunnableTest3();
// 将一个 Runnable对象作为Thread的构造参数
//调用Thread对象的 start 方法,注意不能调用 run 方法
new Thread(rt).start();
}
}
此处用的是1个线程来调用。
new Thread(new RunnableTest3()).start();
如果多个线程,那么多创建几个执行任务的线程(各干各的:每个线程各卖10张票)。
new Thread(new RunnableTest3()).start(); new Thread(new RunnableTest3()).start(); new Thread(new RunnableTest3()).start();
如果多个线程,那么多创建几个执行任务的线程(一起干:3个线程一起卖10张票)。
RunnableTest3 rt=new RunnableTest3(); new Thread(rt).start(); new Thread(rt).start(); new Thread(rt).start();
Runnable接口的run方法是没有返回值的。当我们需要子线程运行结束后提供一个返回值时,就需要用到Callable
接口。Callable
的返回值支持泛型,但因为它的返回值是异步返回的,因此无法直接在主线程中获取返回值,而是配合Future
接口或FutureTask
类来获取返回值。
import java.util.concurrent.*;
// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask implements Callable {
// 实现 call 方法,并返回指定类型的值
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
// Callable实现类不能直接作为Thread构造参数传入,这里使用线程池来提交一个Callable任务
ExecutorService exs = Executors.newSingleThreadExecutor();
CallableTask ca=new CallableTask();
// 通过submit方法向线程池提交Callable任务,submit方法返回的是Future对象
Future future = exs.submit(ca);
// 线程池不再接收新的任务
exs.shutdown();
try {
// future.get()获取子线程的运行结果,如果子线程此时尚未运行结束,则主线程在该步骤会等待直到子线程结束返回结果
System.out.println("和为:"+future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
运行结果:
和为:705082704
Future
接口是用来获取目标线程执行结果的接口,通常和Callable
一起通过线程池来使用。
一、实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
二、执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类(注意:FutureTask实现了Runnable接口和Future接口)
步骤:
示例一:
import java.util.concurrent.*;
// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask implements Callable {
// 实现 call 方法,并返回指定类型的值
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Callable实现类不能直接作为Thread构造参数传入,而是需要包装一层FutureTask将其转为Runnable接口
FutureTask task=new FutureTask<>(new CallableTask());
Thread t = new Thread(task);
t.start();
try {
// 在主线程中获取子线程执行结束后返回的结果,这里是LocalDateTime类型的时间戳。
// 要注意的是,如果子线程此时尚未运行结束,则主线程执行futureTask.get()时会等待,一直到子线程结束返回结果。
System.out.println("和为:"+task.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
FutureTask是一个实现类,它实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口和Future接口。所以FutureTask能够作为Thread的构造参数,同时也可以用来获取目标线程执行结果。Future本身只是接口,要实现它的方法比较复杂,而有了FutureTask就降低了使用Future的难度。Future要结合线程池来使用,而FutureTask既可以与线程池配合使用,也可以直接作为Thread的构造参数使用,更加方便。
运行结果:
和为:705082704
示例二(3个线程卖10张票)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask2 implements Callable {
private int ticket =10;
// 实现 call 方法,并返回指定类型的值
@Override
public String call(){
for (int i = 0; i < 10; i++) {
if (this.ticket > 0) {
System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
}
}
return "票已经卖完"+Thread.currentThread().getName();
}
}
public class CallableTest2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建Callable接口子类的对象
CallableTask2 ct=new CallableTask2();
// Callable实现类不能直接作为Thread构造参数传入,而是需要包装一层FutureTask将其转为Runnable接口
FutureTask task1=new FutureTask<>(ct);
FutureTask task2=new FutureTask<>(ct);
FutureTask task3=new FutureTask<>(ct);
//FutureTask对象作为Thread的构造参数,并调用Start()启动线程,同时也可以用来获取目标线程执行结果。
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
System.out.println(task1.get());
System.out.println(task2.get());
System.out.println(task3.get());
}
}
执行结果(3个线程一起执行卖10张票的任务。)
(3个线程各卖各的,每个卖10张)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask2 implements Callable {
private int ticket =10;
private int id;
public CallableTask2(int id){this.id =id;}
// 实现 call 方法,并返回指定类型的值
@Override
public String call(){
for (int i = 0; i < 10; i++) {
if (this.ticket > 0) {
System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
}
}
return "这是Callable返回值--->票已经卖完,ticket:"+ticket+","+Thread.currentThread().getName();
}
}
public class CallableTest2 {
public static void main(String[] args) throws ExecutionException,
//结合线程池
int threadNum=3;
//创建一个拥有固定线程数的线程池对象
ExecutorService exs = Executors.newFixedThreadPool(threadNum);
List resultList = new ArrayList<>();//创建3个任务并执行
for (int i = 0; i < threadNum; i++){
//创建Callable接口子类的对象
CallableTask2 ct=new CallableTask2(i);
FutureTask future= new FutureTask<>(ct);
//通过 ExecutorService 对象的 submit 方法执行任务
exs.submit(future);
//将任务执行结果存储到List中
resultList.add(future);
}
exs.shutdown();
for (FutureTask future:resultList){
System.out.println(future.get());
}
}
}
执行结果:(3个线程各卖各的,每个卖10张)
(10张票3个线程一起卖)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask2 implements Callable {
private int ticket =10;
// 实现 call 方法,并返回指定类型的值
@Override
public String call(){
for (int i = 0; i < 10; i++) {
if (this.ticket > 0) {
System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
}
}
return "这是Callable返回值--->票已经卖完,ticket:"+ticket+","+Thread.currentThread().getName();
}
}
public class CallableTest2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//结合线程池
//创建Callable接口子类的对象
CallableTask2 ct2=new CallableTask2();
ExecutorService exs = Executors.newFixedThreadPool(3);
//
FutureTask future1=new FutureTask<>(ct2);
FutureTask future2=new FutureTask<>(ct2);
FutureTask future3=new FutureTask<>(ct2);
exs.submit(future1);
exs.submit(future2);
exs.submit(future3);
System.out.println(future1.get());
System.out.println(future2.get());
System.out.println(future3.get());
exs.shutdown();
}
}
执行结果(10张票3个线程一起卖)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//定义一个 Runnable接口的实现类
class RunnableTest implements Runnable{
private int ticket =10;
// 重写run方法,实现自己的业务逻辑
@Override
public void run() {
for (int i = 0; i < 4; i++) {
if (this.ticket > 0) {
System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
}
}
}
}
public class RunnableDemo {
public static void main(String[] args){
//创建1个任务对象
RunnableTest rt=new RunnableTest();
int threadNum=3;
//创建一个拥有固定线程数的线程池对象
ExecutorService exs = Executors.newFixedThreadPool(threadNum);
for(int i=0;i
执行结果(3个线程一起执行卖10张票的任务。)
此处存在线程安全问题,有2张票两个线程同时卖...线程安全的锁控制,之后学习参考:
1.Java(75):多线程学习08-->CyclicBarrier+Runnable),实现线程安全
2.
总结:
创建线程有下面几种方式:
1.继承Thread类:定义子类继承Thread类,创建这个子类对象,子类对象调用start(),本质上也是调用Thread的的 start 方法来启动线程。
2.实现 Runnable 接口:调用的是 Thread 本类的start 方法,而 start 方法最终会调用 run 方法,会把创建的 Runnable 实现类对象赋值给 target ,并运行 target 的 run 方法()
3. 实现Callable接口结合 Future 和 FutureTask 的方式:Future方式使用线程池,FutureTask 也是通过 new Thread(task) 的方式构造 Thread 类,调用start()启动线程。
4. 线程池创建线程:把创建和管理线程的任务都交给了线程池。而创建线程是通过线程工厂类 DefaultThreadFactory 来创建的(也可以自定义工厂类),最后也是通过 new Thread() 的方式来创建线程。
学习以下博客
1、Java(72):多线程学习05-->解决多线程安全—synchronized
https://blog.csdn.net/fen_fen/article/details/121466128
2、Java(73):多线程学习06-->解决多线程安全-Lock
https://blog.csdn.net/fen_fen/article/details/121470551
3、Java(76)多线程学习07:Lock之ReentrantLock(1)
https://blog.csdn.net/fen_fen/article/details/121497940
4、Java(75):多线程学习08-->了解CyclicBarrier
https://blog.csdn.net/fen_fen/article/details/121473092
参考:Java多线程梳理之一_多线程开发入门
https://zhuanlan.zhihu.com/p/350440753
多线程在面试中基本上已经是必问项,你准备好了吗?
https://zhuanlan.zhihu.com/p/268337270