Java层面 创建线程
方法一:继承Thread类
java
package com.evan.juc.base.creadThread;
class ShareData extends Thread {
@Override
public void run() {
System.out.println("当前线程/创建出的线程为" + Thread.currentThread().getName());
}
}
public class MyThread {
public static void main(String[] args) {
// 写法一
Thread thread1 = new Thread(
new Runnable() {
@Override
public void run() {
System.out.println("当前线程/创建出的线程为" + Thread.currentThread().getName());
}
}
);
// 写法二
Thread thread2 = new Thread(
new Runnable() {
@Override
public void run() {
System.out.println("当前线程/创建出的线程为" + Thread.currentThread().getName());
}
}
);
// 写法三
Thread thread3 = new ShareData();
thread1.start();
thread2.start();
thread3.start();
}
}
方法二:实现Runnable接口
java
package com.thread.basic.creadThread;
/**
* 实现Runnable接口,并重写run方法,在run方法中打印当前线程的名字
*
*/
class ShareDataOfRunnalbe implements Runnable {
@Override
public void run() {
System.out.println("当前线程/创建出的线程为" + Thread.currentThread().getName());
}
}
public class MyRunnable {
public static void main(String[] args) {
ShareDataOfRunnalbe shareDataOfRunnalbe = new ShareDataOfRunnalbe();
Thread thread1 = new Thread(shareDataOfRunnalbe);
Thread thread2 = new Thread(shareDataOfRunnalbe);
thread1.start();
thread2.start();
}
}
通过实现 Runnable 接口实现多线程,如代码所示,首先通过 ShareDataOfRunnalbe 类实现 Runnable 接口,然后重写 run() 方法,之后只需要把这个实现了 run() 方法的实例传到 Thread 类中就可以实现多线程。
方法三:线程池创建线程
csharp
public class TestCreatThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10,
10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
try {
for (int i = 0; i < 10; i++) {
threadPool.submit(() -> {
System.out.println(Thread.currentThread().getName() + "线程 starting ....");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程 end....");
}
);
}
} finally {
threadPool.shutdown();
}
}
}
来看看线程池是怎么实现线程的?
scss
static class DefaultThreadFactory implements ThreadFactory {
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
对于线程池而言,本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFactory ,它会给线程池创建的线程设置一些默认值,比如:线程的名字、是否是守护线程,以及线程的优先级等。但是无论怎么设置这些属性,最终它还是通过 new Thread() 创建线程的 ,只不过这里的构造函数传入的参数要多一些,由此可以看出通过线程池创建线程并没有脱离最开始的那两种基本的创建方式,因为本质上还是通过 new Thread() 实现的。
在面试中,如果你只是知道这种方式可以创建线程但不了解其背后的实现原理,就会在面试的过程中举步维艰,想更好的表现自己却给自己挖了"坑"。
所以我们在回答线程实现的问题时,描述完前两种方式,可以进一步引申说"我还知道线程池和Callable 也是可以创建线程的,但是它们本质上也是通过前两种基本方式实现的线程创建。"这样的回答会成为面试中的加分项。然后面试官大概率会追问线程池的构成及原理,这部分内容会在后面的课时中详细分析。
方式四:实现Callable接口
csharp
class CallableDemo implements Callable<Integer> {
//和runnable的接口比起来,这是有返回值的接口
//还可以抛异常
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "****进入callable了!");
Thread.sleep(3000);
return 1024;
}
}
public class CallableTest {
public static void main(String[] args) throws Exception {
//把实现callable的接口当做参数传入futuretask
FutureTask<Integer> futureTask = new FutureTask<>(new CallableDemo());
//因为futureTask实现了Runnable接口,像普通的Runnable实现类一样传入Thread就可以了
Thread t1 = new Thread(futureTask, "t1");
//正常启动
t1.start();
//尝试获取返回结果
System.out.println("****** result=" + futureTask.get());
}
}
ini
t1****进入callable了!
****** result=1024
csharp
//把实现callable的接口当做参数传入futuretask
FutureTask<Integer> futureTask = new FutureTask<>(new CallableDemo());
//因为futureTask实现了Runnable接口,像普通的Runnable实现类一样传入Thread就可以了
Thread t1 = new Thread(futureTask, "t1");
//让两个相同的线程同时执行futureTask
Thread t2 = new Thread(futureTask, "t2");
//正常启动
t1.start();
t2.start();
//尝试获取返回结果
System.out.println("****** result=" + futureTask.get());
ini
t1****进入callable了!
****** result=1024
看,还是和刚才一样,也就是说一个FutureTask只能绑定一个Thread!绑定多了也是无效的!
java
package com.thread.basic.creadThread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableTest02 {
public static void main(String[] args) throws Exception {
// 创建一个 `ExecutorService`
ExecutorService executor = Executors.newSingleThreadExecutor();
// 创建一个 `Callable` 任务
Callable<Integer> task = () -> {
// 执行异步任务
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
// 返回结果
return sum;
};
// 提交任务并获取结果
Future<Integer> future = executor.submit(task);
Integer result = future.get();
// 打印结果
System.out.println("结果: " + result);
// 关闭 `ExecutorService`
executor.shutdown();
}
}
通过有返回值的 Callable 创建线程,Runnable 创建线程是无返回值的,而 Callable 和与之相关的 Future、FutureTask,它们可以把线程执行的结果作为返回值返回,如代码所示,实现了 Callable 接口,并且给它的泛型设置成 Integer,然后它会返回一个随机数。
无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,是需要被执行的,而不是说它们本身就是线程。它们可以放到线程池中执行,如代码所示, submit() 方法把任务放到线程池中,并由线程池创建线程,不管用什么方法,最终都是靠线程来执行的,而子线程的创建方式仍脱离不了最开始讲的两种基本方式,也就是实现 Runnable 接口和继承 Thread 类。
JVM层面实现线程
在JVM 层面实现线程只有一种方式就是通过Thread类创建线程
创建Java线程主要有两种方式,但实质上都是通过Thread
类实现:
- 实现
Runnable
接口 :这种方式中,你定义一个类实现Runnable
接口,并重写run()
方法来包含任务代码。然后,将这个Runnable
实例传递给Thread
类的构造器,通过Thread
对象的start()
方法启动线程,执行run()
方法里的代码。 - 继承
Thread
类 :直接继承Thread
类并重写其run()
方法,将任务代码放在重写的run()
方法中。通过创建该子类的实例并调用其start()
方法来启动线程。
这两种方式的核心都是通过Thread
类来创建线程,不同之处在于如何提供线程执行的代码------是通过外部Runnable
对象还是直接在Thread
子类中定义。