进程和线程的区别
进程是操作系统分配资源(进程id、虚拟内存,文件、socket资源等等)的最小单元;
线程是操作系统进行任务调度的最小单元,线程隶属于进程。
1,Java如何开启线程?怎么保证线程安全?
开启线程的方法:
1)继承Thread类,实现run方法,调用start方法;
2)实现Runnable接口,实现run方法,通过Thread类或者线程池类启动;
3)实现Callable接口,实现call方法,通过FeatureTask创建线程,可以获取执行结果;
4)通过线程池启动线程。
保证线程安全的方法:
加锁,保证线程的可见性,串行修改共享资源
1)使用JVM提供的锁,即使用synchronized关键字;
2)使用JDK提供的锁,实现了Lock接口的锁,可重入锁,读写锁等。
2,synchronized
Java中每一个对象都是锁,synchronized使用的锁保存在对象头的Mark Word中
synchronized使用有三种形式:
1)修饰普通成员方法,锁是当前实例对象;
2)修饰静态成员方法,锁是当前类的class对象;
3)synchronized代码块,锁是括号中指定的对象。
使用sychronized,加锁和解锁是自动完成的;退出同步方法块,或者同步方法块内抛出异常,会自动释放锁。
在早期(JSE1.6之前的版本)synchronized采用操作系统内核API实现加锁和解锁,性能开销比较高,被称为重量级锁;JSE1.6之后对synchronized进行极大的优化,引入偏向锁、轻量级锁,加锁和解锁性能损耗得到很大的改善。随着锁竞争程度的增加,synchronized使用的锁会从偏向锁升级到轻量级锁,进而升级到重量级锁,锁升级之后不能降级。
Java对象头
32位JVM,1个字是32位;64位虚拟机,1个字是64位。
数组对象,占用3个字宽;
非数组对象,占用2个字宽。
对象头的结构
长度 | 内容 | 说明 |
1个字宽 | Mark Word | 存hashCode或锁信息等 |
1个字宽 | Class Metadata Address | 存对象类型数据的指针 |
1个字宽 | Array Length | 如果是数组对象,存数组的长度 |
对象头Mark Word的结构
锁状态 | 4bit | 1bit 是否是偏向锁 | 2bit 锁标志位 | |
无锁状态 | 分代年龄 | 0 | 01 | |
偏向锁 | 线程ID+epoch | 分代年龄 | 1 | 01 |
轻量级锁 | 指向栈中锁记录的指针 | 00 | ||
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 | ||
GC标记 | 空 | 11 |
对象头里锁的状态不同,对象头Mark Word里各个位存的信息也不同。
偏向锁
大多数情况下,锁不仅不存在竞争,而且总是被同一个线程获得。
在没有竞争的情况下,synchronized使用偏向锁。
轻量级锁
在竞争比较小的时候,synchronized的锁会升级到轻量级锁。
轻量级锁在获取锁不成功的时候,线程不会阻塞,而是自旋,通过CAS操作尝试获取锁。自旋会消耗CPU,当一定次数的自旋还是获取不到锁时,synchronized会将锁升级为重量级锁,线程获取锁不成功时会阻塞等待,同时让出CPU。
synchronized锁的优缺点
锁 | 优点 | 缺点 | 使用场景 |
偏向锁 | 加锁和解锁不需要额外的开销,纳秒级可以忽略不计 | 存在竞争时会发生锁撤销带来开销 | 只有一个线程访问同步块(单例模式初始化实例) |
轻量级锁 | 竞争的线程不会阻塞,提高了响应速度 | 如果始终得不到锁,自旋会消耗CPU | 追求响应时间 同步块执行非常快 |
重量级锁 | 竞争的线程阻塞不自旋,不消耗CPU | 线程阻塞,响应速度慢 | 追求吞吐量 同步块执行比较长 |
Java对象内存布局
使用openjdk的jol-core工具包
<!--maven依赖,注意其他版本输出信息有差异-->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
场景一
第一次输出是无锁状态;第二次输出是轻量级锁状态。
场景二
第一次输出是无锁状态;第二次o2并没有加锁,输出偏向锁状态,但是Mark Word没有记录持有锁的线程id;第三次o2加锁后,输出偏向锁状态,Mark Word有记录持有锁的线程id。第四次同步代码块执行完之后o2上的锁应该已经释放,但是仍然输出的是偏向锁状态,Mark Word也记录着线程id,并没有撤销锁,注意当存在其他线程竞争时才会撤销偏向锁,如果竞争激烈会升级到轻量级锁或者重量级锁。
关于第二次输出结果的说明,JVM的一个特性
synchronized不响应中断异常
synchronized获取锁时,如果没有竞争到锁线程阻塞后,不会响应中断异常。
JDK中实现Lock接口的锁,使用lockInterruptibly方法能够响应中断异常。