进程是操作系统调度的最小单元,线程是cpu执行的最小单元,cpu将时间分片,随机算法选择线程执行,虽然操作系统和java都有线程优先级,但是这个优先级是不可靠的。

一、线程的使用方式(有人会把它称为线程有几种,我觉得会有歧义,严格来说线程在java api中有且只有一种。)

java线程的三种执行方式,其实本质上就一种,那就是thread线程通过start方法执行runnable。thread本身继承自runnable,所以无论是外部传入的任务还是它自身,本质都是thread来执行一个任务,第三种FutureTask也是继承自runnable

new Thread(){ public void run(){ //执行任务}}.start

new Thread(new Runnable(){ public void run(){执行任务}}.start

还有一种特殊的线程任务----FutureTask,这是一种有返回值的线程任务,需要构造callable回调接口。

二、线程的状态

线程在执行过程中是有状态的,当执行start之后,默认它是在可执行状态,一但cpu执行了该线程,该线程就转入运行状态,运行结束或者被停止就变成死亡状态,死亡后线程无法重新开启,生命只有一次,在运行状态中如果调用sleep(2)或者wait(2),线程就进入了等待超时状态,此时释放cpu执行权,sleep、wait时间一到,线程重新进入可执行状态,等待cpu执行,调用不带时间的wait,线程进入等待状态,需要notify或者notifyall来唤醒,唤醒后才能进入可执行状态,如果在运行状态调用yield,线程将释放cpu执行权限,进入可执行状态,如果在可执行状态下调用join,那么一但cpu执行了该线程,该线程就会优先于开启此线程的主线程执行,也就是主线程进入了等待状态,直到子线程执行完毕才执行主线程。线程的停止有暴力停止和非暴力停止,直接调用stop为暴力停止,可以通过interrupt向线程发送中断信号,线程中判断isInterrupt来决定是否需要结束线程,结束线程的正确方式就是让线程执行完成。当主线程开启了子线程,同时通过setDaemon(true)为守护线程的时候,主线程结束,子线程也会结束,进程就结束。否则子线程如果不结束,主线程也无法结束,只有在synchronized同步中才会进入阻塞状态,显示锁lock后底层只会进入等待状态而并非阻塞状态。

注意: sleep、wait会清除线程中的中断信号

三、多线程同步

多线程编程的时候,如果操作了同一个对象或者变量,很容易造成执行结果错误,破坏了数据的准确性。要保证数据的安全,就需要多线程同步,同步的本质就是加锁。

四、锁的使用方式

java中锁有两种api,一种是synchronized,另一种是ReentrantLock,两种方式都是可重入锁,也就是递归调用的时候不会造成死锁。 ReentrantLock 提供了一个构造可以使其变成非重入锁,死锁大多数都是由与锁的嵌套引起的。

synchronized可以作用在方法上,也能作用在代码块上,使用时需要传一个锁对象。

ReentrantLock 则可以插入代码的任意一行,通过lock和unlock 这两个api使用。

synchronized和ReentrantLock 都是非公平锁,都支持抢占式执行。

ReentrantReadWriteLock 俗称读写锁,读写锁可以分别获读锁和写锁,来提高数据读取效率。

五、synchronized的本质

synchronized的本质是在虚拟机曾引入了monitorenter monitorexit两句字节码指令,也就是拿到锁就是指持有了monitor对象,释放锁就是释放了monitor对象,在锁定的对象的对象头信息中包含了四种状态锁,也就是这个对象锁的状态不是一成不变的,如果状态是轻量级锁,那么它是通过cpu的cas指令来进行自旋的,当没有线程与之竞争的情况下会切换为偏向锁,也就是不做cas直接拿锁,因为就自己一条线程工作,如果出现了竞争线程,重新变为自旋的轻量级锁,当自旋达到一次线程上下文切换时间,锁就转换为重量级锁,重量级锁即阻塞态,当锁被回收就是gc锁。四种状态是对synchronized的一种优化手段,为了不让使用synchronized就进入阻塞态,造成上线文切换带来的得不偿失的结果。

六、CAS的本质

cas是现代cpu的指令,即比较和交换,它是原子操作,当多线程计算过程中,线程首先进入cas,比较当前值是否是预期值,如果不是就说明锁已经被占用了,那就再来一遍,保证线程不阻塞。但是cas会带来aba问题,线程中无法判断是否改过,一旦被改过又被还原了这个时候线程不知道,同时cas也会让cpu使用率变高。java中AtomicInteger等原子操作类的本质就是CAS。

七、ReentrantLock的本质

lock锁背后是AQS,即抽象队列同步,它是jdk提供的一套实现锁的模版方法,而AQS本质上使用了CHL方式,它是将线程放在队列中,如果没有得到锁就不断检查队列的前面成员有没有释放锁(自旋),如果释放就使得该线程获得锁,检测有没有锁这个是cas操作。

八、volatile 关键字

volatile 在java内存模型中,它保证了线程的可见性,也就是保证数据拿到的永远是最新的,虚拟机底层是通过cpu的lock指令-----在读数据的时候,将cpu缓存数据失效,强制从内存读取,在写数据时候强制从cpu缓存中写入内存,但是不能保证操作的原子性。

九、阻塞队列

阻塞队列是java中同步方式的一种实现,java api提供了多种数据结构的阻塞队列,阻塞队列应用场景是生产者消费者模式。

十、线程池

线程池是阻塞队列的一种应用,它的初衷是避免了创建和销毁线程带来的不必要的开销,同时线程在计算机中是稀缺资源,cpu密集型建议线程最大个数不超过cpu核心数,因为线程的上下文切换远比cpu计算时间来的长。而io密集型一般建议线程最大个数为cpu核心数*2,因为io的时间远比上下文切换来的慢,为了保证线程阻塞时有一个候补线程来工作,混合型的根据情况而定。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注