JAVA线程实现的三种方式:
1. 使用内核线程(Kernel-Level Thread,KLT)
- 由内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。
- 轻量级进程(Light Weight Process,LWP):内核线程的一种高级接口
-
- 优点:LWP与KLT1对1,因此每个都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞,也不会影响整个进程继续工作。
- 缺点:由于基于内核线程实现,所以各种线程操作(创建、析构及同步)都需要进行系统调用,代价相对较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换;另外,一个系统支持轻量级进程的数量是有限的。
- 优点:由于用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助,甚至可以不需要切换到内核态,所以操作非常快速且低消耗的,且可以支持规模更大的线程数量。
- 缺点:由于没有系统内核的支援,所有的线程操作都需要用户程序自己处理,线程的创建、切换和调度都是需要考虑的问题,实现较复杂。
- 进程与用户线程1对n的关系
- 优点:以支持大规模的用户线程并发,且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。
- 用户线程与轻量级进程之间多对多的关系
Java线程调度的两种方式:
1. 协同式线程调度(Cooperative Threads-Scheduling)
- 由线程本身来控制线程的执行时间。线程把自己的工作执行完后,要主动通知系统切换到另外一个线程上。
- 好处:实现简单;切换操作自己可知,不存在线程同步的问题。
- 坏处:线程执行时间不可控,假如一个线程编写有问题一直不告知系统进行线程切换,那么程序就会一直被阻塞。
2. 抢占式线程调度(Preemptive Threads-Scheduling)
- 由系统来分配每个线程的执行时间。
- 好处:线程执行时间是系统可控的,不存在一个线程导致整个进程阻塞的问题。可以通过设置线程优先级,优先级越高的线程越容易被系统选择执行。
线程安全的实现
1. 互斥同步(Mutual Exclusion&Synchronization)
- 含义
- 同步:在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用。
- 互斥:是实现同步的一种手段,临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是主要的互斥实现方式。
- 问题:互斥同步在实现阻塞和唤醒时需要挂起线程和恢复线程的操作,都需要转入内核态中完成,很影响系统的并发性能,也称为阻塞同步(Blocking Synchronization) 解决办法:锁优化
- 自旋锁:在许多应用上共享数据的锁定状态只是暂时,没必要去挂起和恢复线程。当物理机器有多个处理器使得多个线程同时并行执行时,先让后请求锁的线程等待,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁,这时只需让线程执行一个忙循环,即自旋。如果对于某个锁,自旋很少成功获得过,那么很可能以后将省略自旋等待这个锁,避免浪费处理器资源。
- 锁消除(Lock Elimination):指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到代码中堆上的所有数据都不会逃逸出去被其他线程访问到,可把它们当做栈上数据对待,即线程私有的,无须同步加锁。
- 锁粗化(Lock Coarsening):一般情况下,会将同步块的作用范围限制到只在共享数据的实际作用域中才进行同步,使得需要同步的操作数量尽可能变小,但如果反复操作对同一个对象进行加锁和解锁,即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗,此时,虚拟机将会把加锁同步的范围粗化到整个操作序列的外部,这样只需加一次锁。
- 轻量级锁(Lightweight Locking):目的是消除同步周期内不存在竞争时的互斥操作。将对象赋值到线程的栈中,使用CAS操作把线程ID记录到对象中(如果这步不成功,说明有其他线程想要使用该变量,此时变成重量级锁)。
- 偏向锁(Biased Locking):目的同样是消除数据在无竞争情况下的同步。偏向锁会偏向于第一个获得它的线程,如果在后面的执行中该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。
- 手段
-
使用
synchronized
关键字:编译后会在同步块的前后分别形成monitorenter
和monitorexit
这两个字节码指令,并通过一个reference
类型的参数来指明要锁定和解锁的对象。执行monitorenter
指令时先要尝试获取对象的锁。若该对象没被锁定或者已被当前线程获取,那么锁计数器+1;而在执行monitorexit
指令时,锁计数器-1;当锁计数器=0时,锁就被释放;若获取对象锁失败,那当前线程会一直被阻塞等待,直到对象锁被另外一个线程释放为止。 - 使用重入锁
ReentrantLock:与
synchronized
的不同:- 等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
- 公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。而
synchronized
是非公平的,即在锁被释放时,任何一个等待锁的线程都有机会获得锁。
-
2. 非阻塞同步(Non-Blocking Synchronization)
- 先进行操作,若无其他线程争用共享数据,操作成功;反之产生了冲突再去采取其他的补偿措施。
3. 不用同步的方式保证线程安全,因为有些代码天生就是线程安全的。如:每个线程的Thread
对象中都有一个ThreadLocalMap对象,它存储了一组以ThreadLocal.threadLocalHashCode为key、以本地线程变量为value的键值对,所以通过ThreadLocal对象访问的都是本地线程变量