简介:
线程(thread)就是进程中的一个执行线索。Java虚拟机允许进程中同时执行多个线程。每个线程都有一个优先级。具有较高优先级的线程先执行。
线程是操作系统分配 CPU 时间的基本实体。每一个应用程序至少有一个线程,也可以拥有多个线程。线程是程序中的代码流。多个线程可以同时运行并能共享资源。
线程与进程不同,每个进程都需要操作系统为其分配独立的地址空间,而同一进程中的各个线程是在同一块地址空间中工作。
线程存在一个生命周期,由以下方法体现:
(1) start()方法:启动一个线程。
(2) run()方法:定义该线程的动作。
(3) sleep()方法:使线程睡眠一段时间,单位为ms。
(4) suspend()方法:使线程挂起。
(5) resume()方法:恢复挂起的线程。
(6) yield()方法:把线程移到队列的尾部。
(7) stop()方法:结束线程生命周期并执行清理工作。
(8) destroy()方法:结束线程生命周期但不做清理工作。
其中最常用的是方法start()、run()、sleep()、stop()。
线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
-
新建:就是刚使用new方法,new出来的线程;
-
就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
-
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
-
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
-
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
JAVA 线程实现/创建方式:
1、继承 Thread 类
Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方 法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法,它将启动一个新线 程,并执行 run()方法。
1 public class MyThread extends Thread {
2 public void run() {
3 System.out.println("MyThread.run()");
4 }
5 }
6
7 MyThread myThread1 = new MyThread();
8 myThread1.start();
2、实现 Runnable 接口
如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个 Runnable 接口。
1 public class MyThread extends OtherClass implements Runnable { 2 public void run() { 3 System.out.println("MyThread.run()"); 4 } 5 } 6 7 //启动 MyThread,需要首先实例化一个 Thread,并传入自己的 MyThread 实例: 8 MyThread myThread = new MyThread(); 9 Thread thread = new Thread(myThread); 10 thread.start(); 11 12 //事实上,传入一个 Runnable target 参数给 Thread 后,Thread 的 run()方法就会调用target.run() 13 public void run() {
14 if (target != null) {
15 target.run();
16 }
17 }
3、通过Callable和Future创建有返回值线程
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行 Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务 返回的 Object 了。
1 /* 2 * 首先说一下步骤: 3 * 1.创建Callable接口的实现类,并实现call()方法,该call()方法将作为 4 * 线程执行体,并且有返回值。 5 * 2.创建Callable实现类的实例,使用FutureTask类来包装Callable对象, 6 * 该FutureTask对象封装了Callable对象的call()方法的返回值。 7 * 3.使用FutureTask对象作为Thread对象的target创建并启动新线程。 8 * 4.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。 9 */ 10 class MyCallable implements Callable<Integer> { 11 12 @Override 13 public Integer call() throws Exception { 14 // 线程需要完成的任务 15 return new Random().nextInt(100); 16 } 17 } 18 19 public static void main(String[] args) { 20 MyCallable myCallable = new MyCallable(); 21 FutureTask<Integer> futureTask = new FutureTask<>(MyCallable); 22 new Thread(futureTask).start(); 23 // 可能做一些其他操作 24 try { 25 System.out.println("子线程的返回值:" + futureTask.get()); 26 } catch (Exception e) { 27 System.out.println(e.getMessage()); 28 } 29 }
4、基于线程池的方式
上面三种方式的案例都是通过 new Thread 进行创建的,其弊端如下:
- 每次new Thread新建对象性能差。
- 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机。
- 缺乏更多功能,如定时执行、定期执行、线程中断。
线程资源是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。其优势如下:
- 重用存在的线程,减少对象创建、消亡的开销,性能佳。
- 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
- 提供定时执行、定期执行、单线程、并发数控制等功能
线程池结合Runnable接口,如下
1 // 创建线程池 2 ExecutorService threadPool = Executors.newFixedThreadPool(10); 3 while(true) { 4 threadPool.execute(new Runnable() { // 提交多个线程任务,并执行 5 @Override 6 public void run() { 7 System.out.println(Thread.currentThread().getName() + " is running .."); 8 try { 9 Thread.sleep(3000); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 } 14 }); 15 }
线程池结合Callable接口,如下:
1 //创建一个线程池 2 ExecutorService pool = Executors.newFixedThreadPool(taskSize); 3 // 创建多个有返回值的任务 4 List<Future> list = new ArrayList<Future>(); 5 for (int i = 0; i < taskSize; i++) { 6 Callable c = new MyCallable(i + " "); 7 // 执行任务并获取 Future 对象 8 Future f = pool.submit(c); 9 list.add(f); 10 } 11 // 关闭线程池 12 pool.shutdown(); 13 // 获取所有并发任务的运行结果 14 for (Future f : list) { 15 // 从 Future 对象上获取任务的返回值,并输出到控制台 16 System.out.println("res:" + f.get().toString()); 17 }
关于使用线程池管理线程,可单列一篇,详情可参考: