线程和进程的概念:
一个进程可以看成是一个应用程序;
线程可以看成进程里更细的划分,一个进程可以有多个线程,至少有一个主线程。
(有多个 线路 可以执行,一个程序里面写了多个执行路径)
单核CPU还不能说同时执行,一个CPU的核只能干一件事情,有多个事情的话,就看他的线程是怎么进行调度的了。如果是多核CPU,就可以同时干多个事情了。
-
线程的调度方法:
-
分时调度:
每个线程分到相应的时间片,执行的时间到了时间片规定的时间,就按顺序切换另外一个线 程了。
-
抢占式调度:
线程有优先级的概念,优先级高的,抢到时间片的概率就越大。
Java 里使用的就是这种调度方式。
-
每个线程是一个独立的单位(有独立的栈空间和程序计数器,这是线程私有的数据空间),
多个线程之间可以共享一些数据(方法区 和 堆空间,这是线程共享的数据空间)
区分几个易混淆的概念:
-
同步、异步
同步:也就是我们通常说的 线程安全 的情况;排队执行,效率低的,但是安全性好。
异步:也就是我们通常说的 线程不安全 的情况,一起执行,效率是提高了,但安全性降低了
-
并发、并行
并发:一般都是并发量是多少多少,指的是在2个或多个事件,在 一段时间内 同时发生。
并行:指的是 2个或多个事件,在 同一时刻 发生。(在一个具体的时间点上)
Java 中创建线程的3种方式
-
实现 Runnable 接口 ,是 new Thread 里传入的参数。
-
实现 Callable 接口,得到一个 FurtureTask<> 对象, 是 new Thread 里传入的参数
可以调用 get()方***阻塞当前线程的执行,直到得到 返回值的结果为止。
-
继承 Thread 类(该类实现了 Runnable 接口)
Java 种线程的生命周期(线程的状态)
-
NEW 新建
创建线程,但还没有启动(start)
-
RUNNABLE 可运行
该线程正在 JVM 中 运行着。
(具体的在 操作系统 层面有没有运行(可能在等待资源),还要看底层操作系统的调度策略)
-
BLOCKED 阻塞
请求获取 monitor lock 时,其他线程正在占用着这把锁
该状态是被动进来的
-
WAITING 无限时等待
等待获取 monitor lock ;需要被其他线程显示的唤醒
该状态是 主动 进来的
进来该状态的途径:
Object.wait( ) , 没有 Timeout 参数 : 挂起一个线程
Thread.join( ) , 没有 Timeout 参数
LockSupport.park( )
-
TIMED_WAITING 限时等待
在一定时间后,会被系统自动唤醒
进来的途径:(都需要传入一个 Timeout 时间参数 )
Thread.sleep( ) : 睡眠一个线程
Object.wait( ) : 挂起一个线程
Thread.join( )
LockSupport.parkNanos( )
LockSupport.parkUntil( )
-
TERMINATED 死亡
任务结束了,自己了解。
或 产生异常而死亡。
线程里的一些机制或概念
-
Executor
线程池,里面可以有很多个线程,可以管理多个异步任务的执行(多个任务的执行互不干扰)。
因为频繁创建和销毁单个线程是需要耗费一定性能的,用线程池管理(线程可以反复使用)就可以解决这个问题。
常用的是 缓存线程池。
Executors.newCachedThreadPoll( )
有空闲线程就直接使用,否则就创建一个线程,再装入该线程池中。
定长线程池:
Executors.newFixedThreadPool( )
固定线程数量的线程池。
有空闲线程就直接使用。
没有空闲线程,线程池又装满了,就等待空闲线程的出现。
没有空闲线程,线程池没有装满,就创建线程放入该线程池中。
单线程线程池:
Executors.newSingleThreadExecutor( )
定长线程池 的 数量为 1 的情况
周期性任务定长线程池:
Executors.newScheduledThreadPool( )
Executors.
首先是个定长线程池,具有定长线程池的功能。
他还可以执行周期任务,首次执行还可以指定延迟多少秒执行。
-
Daemon
守护线程。 程序运行时,在后台提供服务的 线程,可以没有。
所有非守护线程结束了,守护线程也就跟着结束了。
main() 线程属于非守护线程。
-
sleep( )
使一个线程睡眠,可能产生 中断异常。
异常不可以 跨线程 处理,所以只能在本地进行处理。 (可以在捕获这个异常时结束这个异常)
-
yield()
调用该方法,说明该线程已经完成了他的任务的主要部分,可以让出处理器资源了(给其他线程处理),
只是给 线程调度器 的一个建议,具体给那个线程执行,还要看操作系统的具体策略。
-
中断异常
可以用 interrupt( ) 方法来做标记,提前结束线程。
对 Executor 线程池中的中断:
shutdown( ) 方法,等待所有线程执行完后,再关闭。
shutdownNow( ) 方法,相当于调用每个线程的 interrupt()方法。
中断单个线程:
用 submit()提交一个线程,得到一个 Future<?> 对象,调用该对象的 cancel(true)方法中断线程。
互斥同步(阻塞同步)
这是一种 悲观 的同步策略。只有做正确的同步措施,才会没问题,否则就肯定会出现问题。
无论共享的数据是否出现竞争,都有进行加锁、用户态核心转换、维护锁计数器 和 检查是否需要唤醒线程。
随着硬件指令集的发展,我们可以使用基于 冲突检测 的乐观并发策略。(非阻塞同步)
先操作,没有其他线程竞争共享数据就好 , 有的话就采取补偿措施(不断重试,直到成功)即可。
可以加锁。分为隐式锁和显式锁。
隐士锁: synchronized 关键字
-
可以用来 同步代码块
synchronized( ){ };
( ) 里放入锁对象,这个锁对象要是所有线程都共有的,不能是线程私有的,否则起不到同步的作用。
是一种 粒度很细的方法,可以同步具体的一行代码。
-
可以用来 同步方法
在方法声明的时候,加上 synchronized 关键字即可。
如果是 非静态方法,加锁的对象是 this,也就是调用该方法的那个对象,那个调用者。
如果是 静态方法,加锁的对象是 类名.class, 也就那整个类,那个字节码文件。
显示锁: ReentrantLock 。 是 java.util.concurrent (J.U.C) 包中的锁
- 声明一个 ReentrantLock 对象,调用他的 lock()方法加锁,调用他的 unlock()解锁。
比较
synchronized 是 JVM 实现的,ReentrantLock 是 JDK 实现的。
JVM 对 synchronized 做了很多很多优化。
synchronized 加锁的线程 不可以中断,ReentrantLock 加锁的线程 可以中断。
synchronized 是不公平锁,ReentrantLock 也是不公平锁,但可以设置为 公平锁。
公平锁 和 不公平锁
公平锁: 多个线程等待同一把锁时,先来的先得到锁。
不公平锁: 多个线程等待同一把锁时,得到锁的结果时 不确定的。
一个 ReentrantLock 可以同时绑定 多个Condition 对象。
一般都是使用 synchronized 来加锁,是 JVM 原生支持的。
线程之间的协调
-
join()方法
在线程中调用另一个线程的 join()方法,将当前线程挂起,直到调用该方法的线程结束。
可以保证线程的先后执行顺序。
-
wait() 、 notify() 、 notifyAll()
他们都是 Object 的方法。
wait() 挂起线程时, 会释放锁。 Thread 里的 sleep() 不会释放锁。
需要用 notify()或 norifyAll() 来唤醒。
-
await() 、 signal() 、 signalAll()
他们都是 java.util.concurrent (J.U.C)类库里的方法,通过 Condition 类来实现线程之间的协调。
可以用 Lock 来获取一个 Condition 对象。