线程的创建和使用
方法一,直接使用Thread
import lombok.extern.slf4j.Slf4j;
/**
* @author:小关同学爱吃汉堡
* @date: 2021/4/1 23:27
*/
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
//以匿名内部类的方法创建一个线程对象
Thread thread = new Thread(){
@Override
public void run(){
log.debug("running");
}
};
//指定线程的名字
thread.setName("线程1");
//启动线程
thread.start();
log.debug("running");
}
}
运行结果:
方法二,使用Runnable配合Thread
优点:
- 用Runnable更容易与线程池等高级API配合
- 把“线程”和“任务”(要执行的代码)分开,让任务类脱离了Thread继承体系,更灵活
- Thread表示线程
- Runnable表示可运行任务(线程要执行的代码)
import lombok.extern.slf4j.Slf4j;
/**
* @author:小关同学爱吃汉堡
* @date: 2021/4/1 23:36
*/
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
//不使用lambada表达式的写法
Runnable runnable = new Runnable() {
@Override
public void run() {
log.debug("running");
}
};
//使用lambada表达式简化式子
// Runnable runnable = ()->{
// log.debug("running");
// };
//也可以使用Thread的构造方法直接设置线程的名字
Thread thread = new Thread(runnable,"线程2");
thread.start();
}
}
方法三,FutureTask配合Thread
FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况。
为什么FutrueTask可以返回执行结果呢?
我们来查看FutureTask的源码,可以发现它实现了一个RunnableFuture接口,如下图:
RunnableFuture接口又继承了Runnable接口和Future接口,所以FutureTask间接实现了Runnable接口和Futue接口
Runnable接口就是线程相关的接口,而Future接口是用来返回任务的执行结果的
FutureTask怎么配合Thread创建线程?
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author:小关同学爱吃汉堡
* @date: 2021/4/2 0:52
*/
@Slf4j(topic = "c.Test3")
public class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//使用泛型来指定返回值的类型
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() { //接收一个Callable参数
@Override
public Integer call() throws Exception {
log.debug("running");
Thread.sleep(5000); //线程休眠5秒
return 666;
}
});
Thread thread = new Thread(task,"线程3");
thread.start();
log.debug("{}",task.get()); //这里get方法获取到返回值
}
}
运行结果:
对于前两种创建线程方式的源码分析
第一种方法创建线程对象:
创建Thread的子类对象,通过子类对象直接重写了Thread类里面的run方法
第二种方法创建线程对象:
1.首先Runnable对象进入Thread的构造方法
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
2.进入之后它这里将Runnable对象传给了init方法,然后它这里又将Runnable对象传给了另一个重载的init方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
3.在重载的init方法里面我们找到了Runnable对象被赋值给了一个成员变量
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
4.这个成员变量在Thread自己的run方法中被使用了
@Override
public void run() {
if (target != null) {
target.run();
}
}
如果这个Runnable对象不为null,则调用Runnable的run方法(即我们实现Runnable接口的那个方法)
关于Lambada表达式
我们可以使用Lambada表达式对一些操作进行简化。
如何判断能否使用Lambada表达式?
在jdk中,会把只有一个抽象方法的接口使用@FunctionalInterface注解来进行标记。
而带有@FunctionalInterface注解的接口就可以被Lambada表达式简化(个人认为,Lambada表达式最好看情况使用,在对Lambada表达式不是很熟悉、追求可读性的情况下,最好还是不要使用)。