本文档包含以下内容:
- 线程和进程的基本概念
- 进程与线程的区别与联系
- 如何实现多线程
- 多线程中的常用方法
- 什么是守护线程
一、线程是什么?进程是什么?
(1)线程是CPU独立运行和独立调度的基本单位;
(2)进程是资源分配的基本单位;进程是系统中正在运行的一个程序,程序一旦运行就是进程。
二、二者有什么区别和联系?
两者的联系:
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。(线程属于进程)
- (资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
- 处理机分给线程,即真正在处理机上运行的是线程。
- 处理机包括中央处理器(cpu),主存储器,输入-输出接口。处理机加接外围设备就构成完整的计算机系统
- 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
进程和线程的区别体现在以下几个方面:
(1)地址空间和其他资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其他进程内不可见。
- 通信:进程间通信IPC(管道,信号量,共享内存,消息队列),线程间可以直接独写进程数据段(如全局变量)来进程通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
- 调度和切换:线程上下文切换比进程上下文切换快得多。
- 进程具有独立的空间地址,一个进程崩溃后,在保护模式下不会对其它进程产生影响。
保护模式下,程序地址为虚拟地址,然后由OS系统管理内存访问权限,这样每个进程只能访问分配给自己的物理
- 线程只是一个进程的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。
三、进程和线程的选择取决以下几点:
1.需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程的代价是很大的。
2.线程的切换速度快。所以在需要大量计算,切换频繁时使用线程,还有耗时的操作时用使用线程可提高应用程序的响应。
3.因为对CPU系统的效率使用上线程更占优势,所以可能要发展到多机分布的用进程,多核分布用线程。
4.并行操作时用线程,如C/S架构的服务器端并发线程响应用户的请求。
5.需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。
四、如何实现多线程
多线程的优点和缺点:
多线程的好处:
(1)使用多线程可以把程序中占据时间长的任务放到后台去处理,如图片、视屏的下载
(2)发挥多核处理器的优势,并发执行让系统运行的更快、更流畅,用户体验更好
多线程的缺点:
(1)大量的线程降低代码的可读性;
(2)更多的线程需要更多的内存空间
(3)当多个线程对同一个资源出现争夺时候要注意线程安全的问题。也就是线程访问共享资源的时候
五、守护线程
1.什么是守护线程
守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种:
(1)守护线程,比如垃圾回收线程,就是最典型的守护线程。
(2)用户线程,就是应用程序里的自定义线程。
通过 thread.setDaemon(true)将当前的线程设置为守护线程。如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。
2.守护线程的使用场景
守护线程的典型代表是垃圾回收,这是很多人说守护进程非常有用的理由,但实际上守护进程在用户开发上的应用场景几乎用处不大,可能的应用场景:
内存资源或者线程的管理,但是非守护线程也可以做
守护线程负责一个可以将当前的JVM退出的功能,即将非damon的线程都退出,然后jvm自动退出,感觉用的也非常少,可以直接通知相关线程退出不就可以了,考虑设计上优雅一些,可能有点好处。
3.注意事项
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一IllegalThreadStateException异常。
你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。
(4)写java多线程程序时,一般比较喜欢用java自带的多线程框架,比如ExecutorService,但是java的线程池会将守护线程转换为用户线程,所以如果要使用后台线程就不能用java的线程池。
如下,线程池中将daemon线程转换为用户线程的程序片段:
注意到,这里不仅会将守护线程转变为用户线程,而且会把优先级转变为Thread.NORM_PRIORITY。
六、多线程中的常用方法
序号 | 方法描述 |
1 | public void start() 使该线程开始执行;Java 虚拟机调用该线程的run方法。 |
2 | public void run() 如果该线程是使用独立的Runnable运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name) 改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) 更改线程的优先级。 |
5 | public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt() 中断线程。 |
8 | public final boolean isAlive() 测试线程是否处于活动状态。 |
测试线程是否处于活动状态。 上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法。
序号 | 方法描述 |
1 | public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
2 | public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
3 | public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回true。 |
4 | public static Thread currentThread() 返回对当前正在执行的线程对象的引用。 |
5 | public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。 |
线程优先级的一些特性:
- 线程优先级的继承特性:也就是如果线程A启动线程B,那么线程A和B的优先级是一样的;
- 线程优先级的规则性:即线程会优先级的大小顺序执行,但是不一定是优先级较大的先执行完,因为线程的优先级还有下面第三个特性:
- 线程优先级的随机特性
总结:
- 线程优先级越高说明优先级越高。
- 线程的默认级别是1,最高级别是10;
- 优先级越高,大部分情况下在多个线程的情况下优先级高的先完成所有的任务。
- 但是优先级并不是衡量那个线程运行结果顺序的标准,因为cpu只是尽量将执行资源交给优先级高的线程。优先级高的线程不一定每一次都先执行完run方法中的任务,也就是说线程优先级与打印顺序无关,他们的关系具有不确定性和随机性。