三种创建线程的方式
- 直接使用 Thread
```java
// 自定义线程对象
class Thread1 extends Thread {
@Override
public void run() {
}// 线程需要执行的任务
......
}
// 创建线程对象 Thread1 t1 = new Thread1();
- Thread + Runnable
```java
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程需要执行的任务
......
}
}
// 创建任务类对象
MyRunnable runnable = new MyRunnable();
// 创建线程对象
Thread t2 = new Thread(runnable);
// 指定线程名字
// Thread t2 = new Thread(runnable, "t2");
- Thread + Callable + FutureTask
虽然 Runnable 挺不错的,但是仍然有个缺点,那就是没办法获取任务的执行结果,因为它的 run 方法返回值是 void。这样,对于需要获取任务执行结果的线程来说,Callable 就成为了一个完美的选择。
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 要执行的任务
......
return 100;
}
}
// 将 Callable 包装成 FutureTask,FutureTask也是一种Runnable
MyCallable callable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(callable);
// 创建线程对象
Thread t3 = new Thread(task);
callable 一般和FutureTask 在一起使用。 FutureTask 提供了异步且可中断的计算,实现了RunnableFuture 接口 (集成了Runnable 和 Future接口)。
经典面试题(为什么用start创建线程而不用 run)
调用run 方法并不会重开一个线程而是在主线程下运行普通的重写方法。
Thread 管理了线程的生命周期,所以一般创建一个Runnable 任务然后传递给Thread运行,
控制线程的方法
- 原子性:一个操作是不可中断的,要么全部执行成功要么全部失败。(也可以说是提供互斥访问,同一时刻只能有一个线程对数据进行操作)
- 可见行:当程序修改了共享变量后,其他线程能够立即得知这个修改。
- 有序性:编译器和处理器为了优化程序性能,会对指令序列进行重新排序。由于重排序的存在,可能导致多线程环境下程序运行结果出错的问题。 使用happens-before 来保证有序性。
指令重排遵循的原则(数据依赖性和as-if-serial)
- 编译器和处理器在重排序时,会遵守数据依赖性,它们不会改变存在数据依赖性关系的两个操作的执行顺序
- as-if-serial 语义的意思是:不管怎么重排序,程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守 as-if-serial 语义
如何保证原子性,可见性和有序性。