线程的创建分为4种方式:
启动线程是调用start方法,这样会创建一个新的线程,并执行线程的任务。
如果直接调用run方法,这样会让当前线程执行run方法中的业务逻辑,但不能得到多线程的效果。
public class MiTest {
public static void main(String[] args) {
MyJob t1 = new MyJob();
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println("main:" + i);
}
}
}
class MyJob extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyJob:" + i);
}
}
}
public class MiTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main:" + i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("MyRunnable:" + i);
}
}
}
最常用的方式:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("匿名内部类:" + i);
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("lambda:" + i);
}
});
Callable一般用于有返回结果的非阻塞的执行方法
同步非阻塞。
FutureTask底层实现了Ruunable接口,run()中调用了call()
public class MiTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1. 创建MyCallable
MyCallable myCallable = new MyCallable();
//2. 创建FutureTask,传入Callable
FutureTask futureTask = new FutureTask(myCallable);
//3. 创建Thread线程
Thread t1 = new Thread(futureTask);
//4. 启动线程
t1.start();
//5. 做一些操作
//6. 要结果
Object count = futureTask.get();
System.out.println("总和为:" + count);
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 0; i < 100; i++) {
count += i;
}
return count;
}
}
线程是非常宝贵的计算资源,在每次需要时创建并在运行结束后销毁是非常浪费系统资源的。我们可以使用缓存策略并通过线程池来创建线程。具体实现过程为创建一个线程池并用该线程池提交线程任务。
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
//提交多个线程任务并执行
threadPool.execute(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"is running");
}
});
}
总结:这四种方法追其底层,就是只有一种,实现Runable接口
先看start()源码,底层调用start0(),它是一个本地方法
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0) // 判断线程的状态
throw new IllegalThreadStateException(); // 抛出异常
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0(); // 在start()方法中调用start0()
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0(); // 只定义方法名称没有实现,返回类型native
run()的源码
@Override
public void run() {
if (target != null) {
target.run();
}
}
原因就是Java以良好的跨平台可移植性著称,而在不同的操作系统之中他的资源调度算法是不同的,但操作系统会提供底层函数来进行资源调度。所以当我们在Java程序中调用start()方法时,然后start()方法会去调用start0(),而start0()只有定义没有实现,那么实现是由谁来做的呢?是JVM来做,JVM会根据用户系统类型实现相应的start0()方法,以此实现多线程。而直接调用run()方法并不能达到此效果。
run()源码并没有实现多线程,如果是直接调用run(),则是顺序执行的,不是多线程。
① Callable接口可以有返回值
② Callable接口可以抛出异常
③ 执行方法不同,call()方法和run()方法
如果需要获取一个线程执行完毕的结果, 相比其他方式,使用 Callable 更方便不易出错。比如计算0-100累加的和:
非Callable接口方式:
static class Result {
public int sum = 0;
public Object lock = new Object();
}
public static void main(String[] args) throws InterruptedException {
Result result = new Result();
Thread t = new Thread() {
@Override
public void run() {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
synchronized (result.lock) {
result.sum = sum;
result.lock.notify();
}
}
};
t.start();
synchronized (result.lock) {
while (result.sum == 0) {
// 等待 t 线程计算完之后唤醒当前阻塞状态
result.lock.wait();
}
System.out.println(result.sum);
}
}
虽然这种方式也获取到了线程执行完毕的结果, 但本质上并不是作为返回值的, 而是两个线程修改了同一个变量, 这个变量是 result 类的成员属性, 并且实现的过程比较复杂
所以如果需要获取一个线程执行完毕的结果, 使用 Callable 更方便不易出错
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
int result = futureTask.get();
System.out.println(result);