多线程总结
(1). 线程是指程序在执行过程中,能够执行程序代码的一个执行单元;
(2). 多线程能满足程序员编写高效率的程序来达到充分利用CPU的目的,多线程是多任务的一种特别的形式,但相比多任务而言多线程使用了更小的资源开销;
(3). JAVA给多线程编程提供了内置的支持,一个多线程程序包含两个或多个能并发运行的部分,程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径。
(4). 每一个 JAVA线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。JAVA线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) 到 10 (Thread.MAX_PRIORITY )。默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(优先级为:5)。具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
(5). 尽量不要依赖优先级,如果确实要用,应该避免初学者常犯的一个错误。如果有几个高优先级的线程没有进入非活动状态,低优先级线程可能永远也不能执行。每当调度器决定运行一个新线程时,首先会在具有搞优先级的线程中进行选择,尽管这样会使低优先级的线程完全饿死。
进程与线程的区别
(1). 多线程,指的是这个程序(一个进程)运行时产生了不止一个线程;
(2). 进程,一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分,一个进程一直运行,直到所有的非守候线程都结束运行后才能结束;
(3). 进程,是指一段正在执行的程序,而线程有时也被成为轻量级的进程,他是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段、数据段和堆空间)及一些进程级的资源(例如打开的文件),但是各个线程都拥有自己的棧空间,
并行与并发的区别
(1). 并行,多个CPU实例或者多台机器同时执行一段处理逻辑,是真正的同时;
(2). 并发,通过CPU调度算法,让用户看上去同时执行,实际上从CPU操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力;
(3). 线程安全,经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,CPU是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果;
(4). 同步,JAVA中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。通常简单的加入@synchronized关键字来保证同步。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
创建多线程的方法
(1). 需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法(Thread本质上也是实现了Runnable接口的一个实例。需要注意的是调用start()方法后并不是立即的执行多线程的代码,而是使该线程变为可运行态,什么时候运行多线程代码是由操作系统决定的);
(2). 实现Runnalbe接口,重载Runnalbe接口中的run()方法;
(3). 实现Callable接口,重写call()方法。
为什么使用多线程?
(1). 更好的利用cpu的资源;
(2). 使用多线程可以减少程序的响应时间,如果某个操作很耗时,或者陷入长时间的等待,此时程序将不会响应鼠标和键盘等的操作,使用多线程后可以把这个耗时的线程分配到一个单独的线程去执行,从而使程序具备了更好的交互性;
(3). 与进程相比,线程创建和切换开销更小,同时多线程在数据共享方面效率非常高;
(4). 多CPU或者多核计算机本身就具备执行多线程的能力,如果使用单个进程,将无法重复利用计算机资源,造成资源的巨大浪费,在多CPU计算机使用多线程能提高CPU的利用率;
(5). 使用多线程能简化程序的结构,使程序便于理解和维护。
线程的状态
(1). 新建状态(New),新创建了一个线程对象;
(2). 就绪状态(Runnable),线程对象创建后,其他线程调用了该对象的start()方法以后进入了就绪状态。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权;
(3). 运行状态(Running),就绪状态的线程获取了CPU,执行程序代码;
(4). 阻塞状态(Blocked),阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
(5). 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
示例代码
(1). 继承Thread类创建线程的步骤<ByThread.java>:
- 定义一个Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体;
- 创建该子类的实例,即创建了线程对象;
- 调用该线程对象的start()方法来启动该线程;
(2). 实现Runnable接口创建线程的步骤<ByRunnable.java>:
- 定义一个Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体;
- 创建该实现类的实例,并依此实例作为Thread的target来创建一个Thread对象,该Thread对象才是真正的线程对象;
- 调用该线程对象的start()方法来启动该线程;
(3). 通过Callable和Future创建线程的步骤:
- 定义一个Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值;
- 创建该实现类的实例,使用FutureTask类来包装该实例,该FutureTask对象封装了该Callable对象的call()方法的返回值;
- 使用FutureTask对象作为Thread的target创建并启动新线程;
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值;
// ByThread.java代码如下:
public class ByThread extends Thread {
ByThread(String name){
super(name);
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
}
}
public static void main(String[] args) {
new ByThread("The first thread").start();
new ByThread("The second thread").start();
}
}
// ByRunnable.java代码如下:
public class ByRunnable implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
}
}
public static void main(String[] args) {
ByRunnable r = new ByRunnable();
new Thread(r, "The first thread").start();
new Thread(r, "The second thread").start();
}
}