本文共 4164 字,大约阅读时间需要 13 分钟。
进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。详细解释:
程序:为了完成特定任务,用某种语言编写的一组指令集合(一组静态代码) 进程:运行中的程序,系统调度与资源分配的一个独立单位,操作系统会 为每个进程分配一段内存空间!程序的依次动态执行,经历代码的加载,执行, 执行完毕的完整过程! 线程:比进程更小的执行单元,每个进程可能有多条线程,线程需要放在一个 进程中才能执行,线程由程序负责管理,而进程则由系统进行调度! 多线程的理解:并行执行多个条指令,将CPU时间片按照调度算法分配给各个 线程,实际上是分时执行的,只是这个切换的时间很短,用户感觉到"同时"而已!线程的创建有三种:
1、继承Thread类代表线程 定义Thread类的子类,并重写run()方法,run()方法称为线程的执行体; 创建Thread类的子类的实例,即创建线程的对象; 调用线程对象的start()方法启动线程。 代码示例:package thread;public class TestThread { public static void main(String[] args) { SubThread1 st1 = new SubThread1(); SubThread1 st2 = new SubThread1(); st1.start(); st2.start(); }}class SubThread1 extends Thread{ public void run(){ for(int i = 0;i< 14;i++) { if(i % 2 == 0) { System.out.println(Thread.currentThread().getName()+" "+i); } } }}
运行结果:
Thread-0 0Thread-1 0Thread-1 2Thread-1 4Thread-0 2Thread-1 6Thread-0 4
2、实现Runnable接口创建线程:
定义Runnable接口实现类,并重写run()方法; 创建Runnable实现类的实例,并将实例对象传给Thread类的target来创建线程对象; 调用线程对象的start()方法启动线程。 代码示例:package thread;public class TestRunnable { public static void main(String[] args) { SubThread2 st = new SubThread2(); new Thread(st,"thread1").start(); new Thread(st,"thread2").start(); }}class SubThread2 implements Runnable{ public void run() { for(int i = 0;i < 8; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName()+" "+i); } } }}
结果:
thread1:0thread2:0thread2:2thread2:4thread2:6thread1:2
3、使用Callable接口和Future接口创建线程:
定义Callable接口实现类,指定返回类型,并重写call()方法; 创建Callable接口实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值; 使用FutureTask对象作为Thread对象的target创建并启动新线程; 调用FutureTask 对象的get()方法来获得子线程执行结束后的返回值。代码示例:
package thread;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class TestCallable { public static void main(String[] args) { CallablemyCallable = new SubThread3(); FutureTask ft = new FutureTask (myCallable); for(int i = 0;i < 4;i ++) { System.out.println(Thread.currentThread().getName()+" "+i); if(i % 2 == 1) { Thread thread = new Thread(ft); thread.start(); } } System.out.println("主线程for循环执行完成"); try { int sum = ft.get(); System.out.println("sum ="+sum); }catch(InterruptedException e){ e.printStackTrace(); }catch(ExecutionException e) { e.printStackTrace(); } }}class SubThread3 implements Callable { private int i = 0; public Integer call()throws Exception{ int sum = 0; for(i = 0;i<3;i++) { System.out.println(Thread.currentThread().getName()+" "+i); sum += i; } return sum; }}
结果:
main 1main 2main 3主线程for循环执行完成Thread-1 0Thread-1 1Thread-1 2sum =3
总结:
创建线程必须要通过Thread类的实例或Thread类子类的实例,然后调用start()方法启动线程,上述三种方式可以分为两种:1是调用无参的构造函数Thread()来实例化对象;2、3是调用Thread(Runnable target)构造方法,其中参数为Runnable类的实例化对象,在3中使用了FutureTask(FutureTask实现了Runnable接口),因此可以看做FutureTask的实例化对象是Runnable类型的。创建线程的三种方式的对比:
采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
使用Callable 接口的方式有返回值,可以抛出异常。
线程的生命周期和状态转换 1、新建状态: 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。此时线程对象在堆空间中分配了一块内存,但还不能运行。2、就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,java虚拟机会 为它创建调用栈和程序计数器,处于这个状态 的线程位于可运行池中,等待获得CPU的使用权。3、运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态,每个CPU只能被一个线程占用。只有处于运行状态的线程才可以转换到运行状态,处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。4、阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用CPU等资源之后,该线程就从运行状态进入阻塞状态,进入阻塞状态java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态才有可能分配到CPU,再次进入运行状态。可以分为三种:等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
5、死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。如改线程的run()方法再次执行完成,线程正常结束;线程抛出异常或错误;调用线程的stop()方法结束该线程。一旦线程转换为死亡状态就不能运行且不能转换为其他状态。线程的调度:
由于一个CPU只能执行一个线程,在运行池中会有多个处于就绪状态的线程在等待CPU,Java虚拟机负责线程的调度,即按照特定的机制为多个线程分配CPU使用权。调度模型分为分时调度模型和抢占式调度模型两种。转载地址:http://hypvn.baihongyu.com/