- 一、基础知识
- 1. 进程、线程、协程
- 1.1 进程
- 1.2 线程
- 1.3 协程
- 2. 串行、并发、并行
- 2.1 串行
- 2.2 并发
- 2.3 并行
- 二、线程的创建
- 1. 继承Thread类
- 1.1 实现步骤
- 1.2 特点
- 2. 实现Runnable接口
- 2.1 实现步骤
- 2.2 与Thread相比
- 3. 实现Callable接口(JDK5.0新增)
- 3.1 实现步骤
- 3.2 与Runnable接口相比
- 三、线程的常用方法
- 四、线程的生命周期
- 1. 基本概念
- 2. 六个状态
- 五、线程的优缺点
- 1. 优点
- 2. 缺点
- 基本概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是 *** 作系统进行资源分配与调度的基本单位。简单来说,就是 *** 作系统中正在运行的一个程序。在 *** 作系统中是以进程为单位分配资源,如虚拟存储空间、文件描述符等。
- 基本概念:线程是进程的基本执行单元。一个线程就是进程中一个单一顺序的控制流,是进程的一个执行分支。进程是线程的容器,一个进程中至少有一个线程。每个线程都有各自的线程栈、寄存器环境、本地存储。
- 主线程:JVM启动时会创建一个主线程,该主线程负责执行main方法,主线程就是运行main方法的线程。
- 父线程和子线程:Java中的线程并不孤立,线程之间存在着联系,如果在A线程中创建了B线程,则称B线程为A线程的子线程,相应的,A线程就是B线程的父线程。
- 基本概念:协程是更轻量级的线程,它不被 *** 作系统内核所管理,而是完全由程序控制。一个线程可以拥有多个协程,每个协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时,恢复先前保存的寄存器上下文和栈,直接 *** 作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
- 基本概念:所有线程依次执行。
- 基本概念:一个处理器,多个线程在逻辑上的同时运行,实际上时采用时间片轮转的技术进行的逻辑同时运行。
- 基本概念:多个处理器,多个线程在物理上的同时运行,真正的实现了多个线程的同时运行。
- 创建一个继承于Thread类的子类。
- 重写Thread的run()方法 —> 将此线程的方法声明在run()中。
- 创建Thread子类的对象。
- 通过此对象调用start()方法。
- 调用线程的start()方法来启动线程,实际上是通知JVM当前线程已经准备好了,请求JVM运行相应的线程,线程具体在什么时候运行由线程的调度器来决定,start()方法调用结束并不意味着子线程立刻开始运行。
- 调用start()方法,实际上将会完成两个过程:① 启动当前线程 ② 调用当前线程的run()方法。
- 如果开启了多个线程,JVM调用的顺序并不一定是线程启动的顺序,具体谁先谁后,由线程调度器决定。也就是说,多线程的运行结果与线程的调用顺序无关。
- 我们不可以通过调用run()方法的方式启动线程。
- 如果还需要一个线程做相同的 *** 作我们必须重新创建一个线程的对象,让新的对象执行新的线程。
- 创建一个实现了Runnable接口的类 。
- 实现类去实现Runnable中的抽象方法:run() 。
- 创建实现类的对象。
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 。
- 通过Thread类的对象调用start()。
1.区别
- 只创建了一个Runnable接口的实现类,每新建一个线程,只需要新创建一个Thread类的实例,并将Runnable的接口实现类作为参数传入即可。
- 实现的方式没有类的单继承性的局限性。
- 实现的方式更适合来处理多个线程有共享数据的情况。
2.联系
- 实际上Thread类也是实现了Runnable接口,public class Thread implements Runnable。
- 两种方式都要重写run()方法。
- 创建一个实现Callable的实现类。
- 实现call方法,将此线程需要执行的 *** 作声明在call()中。
- 创建Callable接口实现类的对象。
- 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象。
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()。
注:可以选择使用futureTask.get()方法获取Callable中的call方法的返回值。(get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值)
3.2 与Runnable接口相比- call()可以有返回值的。
- call()可以抛出异常,被外面的 *** 作捕获,获取异常的信息。
- Callable是支持泛型的。
- 需要借助FutureTask类,比如获取返回结果。
注:以下解释中如果方法被某个线程调用,则“当前线程”代表调用该方法的线程。如果方法无需调用,则当前方法在哪个线程中使用,“当前线程”就代表哪个方法。
- start():启动当前线程,调用当前线程的run()。
- run():通常需要重写Thread类中的此方法,将创建的线程要执行的 *** 作声明在此方法中。
- currentThread():静态方法,返回执行当前代码的线程。
- getName():获取当前线程的名字。
- setName(String name):设置当前线程的名字。
- yield():释放当前cpu的执行权(只是有更大可能分配给其他线程,也有可能再分配回来)(会抛异常)。
- join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
- join(long n):在线程a中调用线程b的join(long n),此时线程a就进入阻塞状态,直到线程b完全执行完以后或者等待了指定时间n之后,线程a才结束阻塞状态。
- stop():已过时。当执行此方法时,强制结束当前线程。
- sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒,在指定的millitime毫秒时间内,当前的线程是阻塞状态,注意,睡眠结束后该线程未必会立刻执行,需要CPU进行分配。
- isAlive():判断当前线程是否存活。
- getPriority():获取线程的优先级
- setPriority(int p):设置线程的优先级
- interrupt():打断当前线程,有三种情况:① 打断sleep、wait、join的线程:当前线程处于阻塞状态,则该方法会让当前线程抛出异常,然后被捕获。当前线程中存在一个打断标记,如果当前线程曾经被interrput()打断过,则为true,否则为false。但是处于阻塞状态的线程被打断抛出异常后,会将该标志清空。② 打断正常运行的线程:打断后将当前线程的打断标记置为true,表示当前线程被打断过,但是当前线程是否进入阻塞,需要由当前线程决定。 ③ 打断park线程:当前线程处于阻塞状态,则该方法会将打断标记置为true,并直接唤醒该方法,且不会清空打断标志。当打断一次park的阻塞状态后,如果再运行park()方法,当前线程也不会阻塞了,此时如果设置打断标记为false,则再次调用park()方法会进入阻塞。
- isInterrupted():返回打断标记的值。
- interrupted():返回打断标记,并在之后设置打断标记为false
- setDaemon(true):设置当前线程为守护线程。Java中的线程还可以分为用户线程和守护线程,守护线程是为其他线程提供服务的线程,如垃圾回收器(GC)就是一个典型的守护线程。守护线程不能单独运行,当JVM中没有其他用户线程,只有守护线程时,守护线程会自动销毁,JVM会退出。注意,应该在线程A启动前,设置线程A为守护线程,否则设置不成功。
- getState():获取当前线程的运行状态。
- 线程的生命周期,就是线程生老病死的过程。
我们可以通过getState()方法获得线程的当前状态,线程状态是枚举类型,有以下几种状态:
- NEW(新建):创建了线程对象,但是还未调用start()方法。
- RUNNABLE(可运行):是一个复合状态,包含READY和RUNNING状态。
- READY(准备):该线程准备执行,即调用了start()方法但是还未调用run()方法。可以通过调用run()方法来使其进入RUNNING状态。
- RUNNING(运行):RUNNING表示该线程正在执行,即正在执行run()方法。可以通过调用yield()方法来使其进入READY状态。
- BLOCKED(阻塞):当前线程申请由其他线程占用的独占资源,则当前线程就会转入BLOCKED状态。此状态的线程不会占用CPU资源。当前线程获得了器申请的资源,则当前线程可以结束BLOCKED状态,转换为RUNNABLE状态。
- WAITING(等待):① 当前线程A执行了A.wait(),则会将当前线程转换为WAITING状态,当其他线程(比如线程B)执行B.notify()方法或B.notifyAll()方法后,则会将当前线程转换为RUNNABLE状态;② 当前线程A执行了其他线程(比如线程B)的B.join()方法,则当前线程会进入WAINTING状态,等到执行了join()方法的线程(比如线程B)执行结束,当前线程会进入RUNNABLE状态;③ 在当前线程(比如线程A)中,执行了LockSupport工具类的park()方法,则当前线程A会进入WATING状态,等到其他线程(比如线程B)执行了LockSupport工具类的B.unpark(A)方法,则当前线程A会重新进入RUNNABLE状态。
- TIME_WAITING(超时等待):类似于WAINTING,但处于TIME_WAITING状态的线程不会无限的等待,如果线程没有在指定的时间范围内完成期望的 *** 作,则该线程自动转换为RUNNABLE。① 当前线程A执行了A.sleep()方法,则线程A进入TIME_WAITING状态,等到达指定时间后,线程A重回RUNNABLE状态。② 当前线程A执行了带参数的,有时间限制的A.wait(long),或者其他线程(比如线程B)执行了B.join(long)方法,则进入TIMIE_WAITING状态,如果到达指定时间,或者其他线程(比如线程C)执行了C.notify()、C.notifyAll()、其他线程(比如线程B)执行完毕,则当前线程进入RUNNABLE状态;③ 在当前线程(比如线程A)中,执行了LockSupport工具类的parkNanos(long time)方法或者parkUntil(long time)方法,则当前线程A会进入TIME_WATING状态,等到其他线程(比如线程B)执行了LockSupport工具类的B.unpark(A)方法,或者到达指定时间,则当前线程A会重新进入RUNNABLE状态。
- TERMINATED(终止):线程结束,处于终止状态。
- 提高系统的吞吐率:多线程编程可以使一个进程有多个并发的 *** 作。
- 提高响应性:web服务器会采用一些专门的线程负责用户的请求处理,缩短了用户的等待时间。
- 充分利用多核资源:通过多线程可以充分利用CPU资源。
- 线程安全问题:多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能会产生数据的一致性问题,如脏数据(过期数据)、丢失数据更新。
- 线程活性问题:由于程序自身缺陷或者资源稀缺,导致线程长期处于非RUNNABLE状态,致使资源的浪费。常见的活性故障包括死锁(互相争夺且皆不放手)、锁死(永不开锁)、活锁、饥饿(一个线程多次抢夺则其他线程抢夺不到)。
- 上下文切换:处理器从执行一个线程切换到执行另外一个线程,需要性能消耗。
- 可靠性:可能会有某一个线程问题导致JVM意外终止,此时其他线程也无法执行。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)