一、创建线程的方式
1、继承Thread类
2、实现Runnable接口
3、使用Callable和Future创建线程
4、基于线程池的方式
- 第一种方式:继承Thread类,重写run()方法,调用start()方法开启线程(start()方法是一个 native 方法,它将启动一个新线
程,并执行 run()方法)
//第一种方式:继承Thread类
public class MyThread01 extends Thread {
@Override
public void run() {
System.out.println("MyThread01 is running!");
}
public static void main(String[] args) {
MyThread01 myThread01 = new MyThread01();
myThread01.start();
}
}
- 第二种方式:实现Runnable接口,重写run()方法,在执行线程的时候放入runnable的实例对象,实际上Thread类也是继承了Runna接口
//第二种方式:继承Runnable接口
public class MyThread02 implements Runnable {
@Override
public void run() {
System.out.println("MyThread02 is running!");
}
public static void main(String[] args) {
//实例化线程
MyThread02 myThread02 = new MyThread02();
//调用start方法的时候,放入实例化对象
new Thread(myThread02).start();
}
}
- 第三种方式:使用Callable和Future创建线程
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 执行任务并获取 Future 对象
Future f = pool.submit(c);
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从 Future 对象上获取任务的返回值,并输出到控制台
System.out.println("res:" + f.get().toString());
}
或者是实现Callable接口,主要就是四步:创建执行服务、提交执行、获取结果、关闭服务
//第三种方式:重写Callable
public class TestCallable implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i=0;i<10;i++){
System.out.println("======"+i);
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable();
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(1);
//提交执行
Future<Boolean> r1 = ser.submit(t1);
//获取结果
boolean res1 = r1.get();
System.out.println(res1);
//关闭服务
ser.shutdown();
}
}
- 第四种:基于线程池创建
线程池的顶级接口为:Executor,真正的接口为:ExecutorService
四种线程池:
newCachedThreadPool:创建一个可根据需要创建新线程的线程池,在以前构造的线程可用时将重用这些线程。调用execute将重用以前构造的线程(如果线程可用)。如果线程不可用,则创建一个新线程并添加到池中。终止并从缓存中移除已有60秒未被使用的线程。
newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
newSingleThreadExecutor:单例线程,任意时间内只能有一个线程,这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。
二、线程状态
新建状态、就绪状态、运行状态、阻塞状态、死亡状态
- 第一种,新建状态
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态
- 第二种,就绪状态
当线程对象调用了start方法之后,该线程就处于就绪状态,JVM会为其创建方法调用栈和程序计数器,等待调度运行。
- 第三种,运行状态
当处于就绪状态的线程获得了CPU,开始执行run()方法的线程知兴替,该线程就进入了运行状态。
- 第四种,阻塞状态
阻塞状态指线程停止运行,释放出CPU的使用权。
阻塞分为以下三种情况:
①、等待阻塞:运行中的线程执行o.wait()方法,JVM会把该线程放入等待队列中。
②、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
③、其他阻塞:执行sleep()方法、o.join()等方法时,当等待超时之后,线程重新转入可运行(runnable)状态。
- 第五种,死亡状态,会用以下三种方式结束线程。
①、线程正常结束:run()方法或call()方法执行完成
②、线程异常结束:线程抛出一个未捕获的Exception或Error
③、调用stop()方法:该方法容易导致死锁,造成线程不安全
三、扩充知识
1、sleep()和wait()的区别
-
①、sleep()方法属于Thread类;而wait()方法属于Object类。
-
②、sleep()方法会让出CPU给其他线程,但是它的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。
-
③、调用sleep()方法的过程中,线程不会释放对象锁。
-
④、调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池。
2、start()和run()的区别
-
①、start()方法来启动线程,真正实现了多线程运行;此时无需等待run()方法体代码执行完毕,可以直接继续执行下面的代码。
-
②、通过调用Thread类的start()方法来启动一个线程,这时此线程就是处于就绪状态的,并没有运行。
-
③、run()方法称为线程体,它包含了只要执行的线程的内容,线程就进入了运行状态,开始运行run()方法当中的代码。
-
④、run()方法运行结束,此线程终止,然后CPU再调度其他的线程。