进程与线程概叙

要想了解多线程必须要知道线程的原理,而线程是依赖于进程的存在故而要先把进程弄明白

何为进程?

通过任务管理器(win : ctrl + alt + .)可以看到进程的存在。
而通过观测发现,只有运行的程序才会出现进程
进程建议理解为正在运行的程序
进程是系统进行资源分配和调用的独立单位.每个进程都有它的内存空间与系统资源

多进程的意义

单进程的计算机只能做一件事,而现在的计算机可以做多件事:一边打游戏一边听音乐...
可以在一个时间段执行多个任务,CPU的占用会上升.
故而有个好处是提高CPU使用率。
问题 : (单核为例)
    一边玩游戏一边听音乐是同时进行的吗?
        不是,单cpu在某时间点只能做一件事
        在玩游戏或听音乐时,CPU做程序的高效切换让我们觉得是同时运行
        当然多核能保证同时进行(双核保证两个,类推...)

何为线程?

再一个进程内可以执行多个任务,而每个任务可以看成一个线程.以纸牌为例,游戏以开始就在不断计时
线程 : 是程序(进程)的执行单元,执行路径。是程序使用CPU的最基本单位
单线程 : 如果程序只有一条执行路径
多线程 : 如果程序有多条执行路径

多线程的意义

多线程的存在,不是提高程序的执行速度.其实是为了提高应用程序的使用率。
程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中某一个进程如果执行路径比较多,就有更高的几率抢到CPU执行权
无法保证哪个线程能在哪个时刻抢到,所以线程的执行有随机性

并行与并发

并行是逻辑上同时发生,指在某个时间段内同时运行多个程序
并发是物理上同时发生,指在某个时间点内同时运行多个程序

Java程序运行原理

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
思考 : jvm的启动是单线程还是多线程?
    多线程,以main方法为例.除了主线程应该还有gc等..

java如何实现多线程程序

由于线程依赖于进程,故而要先创建一个进程
而进程是由系统创建的,所以要调用系统功能创建一个进程.
Java是不能直接调用系统功能的,故而无法直接实现多线程程序
但Java可以调用C/C++写好的程序实现多线程程序
由C/C++调用系统功能创建进程,java再调用这样的东西,然后提供一些类供我们使用就可以实现

实现多线程的方式

有两种常用的方式:
一、继承Thread类并重写run方法,然后创建实例并启动
Thread

二、实现Runnable接口并重写run方法

Runnable
三、使用Callable<V>接口
{% post_link Java/Thread/Callable Callable %}

线程调度

一个CPU在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。
分时调度模型 : 所有线程轮流使用CPU使用权,平均分配线程占用CPU的时间片
抢占式调度模型 : 优先让优先级高的CPU使用,如果优先级相同,那么随机选择一个,优先级高获得的时间片也比较多
java采用抢占式调度模型

线程生命周期

创建(实例) : 创建线程对象
就绪 : 有执行资格,没有执行权
运行 : 有执行资格,有执行权
    阻塞 : 由于一些操作让线程处于该状态。没有执行资格,没有执行权
           而另一些操作却可以把它激活,激活后处于就绪状态
死亡 : 线程变成垃圾,等待被回收
面试的时候一般给出图解,容易让人理解,以下是最简易图解

线程安全问题

将共享数据一次只让一个线程访问,这样就可以保证数据的准确了

方式一 :

使用synchronized解决 : 
{% post_link Java/Thread/Synchronized 线程同步技术 %}

方式二 :

使用java提供的Lock接口 :
Lock锁

死锁问题

同步代码如果出现同步的嵌套,那么就容易出现死锁的问题
在两个或以上的线程在执行过程中,因为争夺资源产生的互相等待现象
在程序设计中,避免这种情况,让代码的嵌套顺序一致
简单案例 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static void main(String[] args) {
Play play1 = new Play(true);
Play play2 = new Play(false);
play1.start();
play2.start();
}
class Play extends Thread{
private boolean flag;
public Play(boolean flag) {
this.flag = flag;
}
public Play() {}
@Override
public void run() {
if (flag){
synchronized (MyLock.A){
System.out.println("if A");
synchronized (MyLock.B){
System.out.println("if B");
}
}
}else {
synchronized (MyLock.B){
System.out.println("else B");
synchronized (MyLock.A){
System.out.println("slse A");
}
}
}
}
}
class MyLock{
public static Object A = new Object();
public static Object B = new Object();
}
当play1获得锁A而同时play2获得锁B的时候,就可能死锁
以下是出现死锁的情况 :

线程间的通信问题

不同种类的线程针对同一个资源的操作,称为线程间的通信问题
以下是一个简单的生产者-消费者案例
等待唤醒机制

线程的状态

常见的线程转换状态有如下这些 :
    A: 创建--就绪--运行--死亡
    B: 创建--就绪--运行--就绪--死亡
    C: 创建--就绪--运行--其他阻塞--就绪--运行--死亡
    D: 创建--就绪--运行--同步阻塞--就绪--运行--死亡
    E: 创建--就绪--运行--等待阻塞--同步阻塞--就绪--运行--死亡
线程状态简易转换图如下 :

线程组

有些时候,我们需要对许多功能相同的线程设置值的时候需要一个个的设置
这个时候java为我们提供了线程组ThreadGroup类,利用它就可以统一的管理线程集合
线程组

线程池

程序启动一个新线程的成本是比较高的,因为涉及操作系统的交互.而使用线程池可以很好的提升性能
尤其在程序中要创建大量生存期很短的线程时,更应该考虑线程池的使用
那么该如何设置线程池的大小呢?
    这才是最难的地方,如果访问量大或者运算量大则应该设计大一点,反之设计小一些
    还涉及到并发访问测试以及压力测试等
线程池

定时器

JDK3为我们提供了一个Timer接口
它可以用于执行一些定时的任务

但是,现在的开发中一般使用quartz这个开源框架实现定时器任务

线程安全的类回顾

Vector
Hashtable
StringBuffer
但是 Vector与Hasht使用比较少了,在Collections工具类中同步的Collection,List,Map,Set接口的同步集合
public static <T> Collection<T> synchronizedCollection(Collection<T> c)
public static <T> Set<T> synchronizedSet(Set<T> s)
public static <T> List<T> synchronizedList(List<T> list)
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

相关习题

线程练习