1. 创建线程,启动一个线程。
/**
* 创建和调用线程
*/
public class CusThread {
public static void main(String[] args) {
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
System.out.println(Thread.currentThread().getName());
}
}
start方法源码:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
2. 并行和并发
- 并行是指多个CPU在同一时刻执行多个任务。
- 并发是一个CPU在一个时间段内执行多个任务。
3. 进程、线程、管城
- 进程是正在运行的一个程序。
- 线程是一个进程内部的一条执行路径,
- 管程即是我们平常说的锁(监视器(Monitor))
4.用户线程和守护线程
- 用户线程即是系统的工作线程,用来完成业务操作。
- 守护线程即是系统的后台线程,是为其他线程服务的。
- 如果用户线程结束,那么后台线程也会结束。
5.判断是否为守护线程
public final boolean isDaemon() {
return daemon;
}
6. Future接口
Future异步接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask实现了Future 和 Runnable 接口,可以创建线程和操作线程,但是没有返回值,怎么解决?
FutureTask 将 Callable 构造注入。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
同时重写run方法去执行Callable 的call方法,
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
把值传给 outcome
protected void set(V v) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = v;
STATE.setRelease(this, NORMAL); // final state
finishCompletion();
}
}
重写Future 接口的get方法获取返回值,get方法是一个阻塞方法。
join 和 get 的区别?join 和 get几乎是一致的,都会阻塞当前线程,但是get有异常声明。
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
所以使用Callable创建线程如下,但是main会被get阻塞住,降低执行效率。
/**
* 创建和调用线程
*/
public class CusThread {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<>(() -> 1 << 4);
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println("结果是:" + futureTask.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
7. CompletableFuture
get()方法在Future计算完成之前会使得主线程一直处在阻塞状态下,阻塞的方式和异步编程的设计理念相违背。jdk8计出CompletableFuture,CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
CompletionStage代表异步计算的每一个阶段。
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {}
public interface CompletionStage<T> {}
四个核心静态方法:
/**
* 无返回值
*/
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(ASYNC_POOL, runnable);
}
/**
* 无返回值,有线程池
*/
public static CompletableFuture<Void> runAsync(Runnable runnable,
Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
/**
* 有返回值
*/
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(ASYNC_POOL, supplier);
}
/**
* 有返回值,有线程池
*/
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
runAsync方法测试
/**
* main
* ForkJoinPool.commonPool-worker-1
* pool-1-thread-1
*/
@Test
public void testRunAsync(){
CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
});
System.out.println(Thread.currentThread().getName());
CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
},executors);
}
supplyAsync方法测试 ,调用get方法依旧阻塞主线程
/**
* main
* ForkJoinPool.commonPool-worker-1
* pool-1-thread-1
*/
@Test
public void testSupplyAsync(){
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName());
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> Thread.currentThread().getName(), executors);
try {
System.out.println(completableFuture1.get());
System.out.println(completableFuture2.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
减少阻塞和轮询问
/**
* 结果是:16
*/
@Test
public void testWhenComplete(){
CompletableFuture.supplyAsync(() -> 1 << 4,executors).whenComplete((r,e) ->{
if(e == null){
System.out.println("结果是:" + r);
}
}).exceptionally(e -> {
System.out.println("出现异常");
return 500;
});
}
链式编程:@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)
public class Student {
private Integer id;
private String name;
private Integer age;
public static void main(String[] args) {
Student student = new Student().setId(1).setName("zhangsan").setAge(18);
}
}
8. 单线程和多线程效率的对比
可以看出效率提升了三倍
public class CompletableFutureDemo {
static List<NetMall> list = Arrays.asList(
new NetMall("jd"),
new NetMall("dangdang"),
new NetMall("taobao")
);
public static List<String> getPrice(List<NetMall> list, String productName){
return list.stream()
.map(netMall -> String.format(productName + "in %s price is %.2f", netMall.getNetMallName(), netMall.calcPrice(productName)))
.collect(Collectors.toList());
}
public static List<String> getPriceByCompletableFuture(List<NetMall> list, String productName){
return list.stream()
.map(netMall -> CompletableFuture.supplyAsync(() -> String.format(productName + "in %s price is %.2f", netMall.getNetMallName(), netMall.calcPrice(productName))))
.collect(Collectors.toList())
.stream().map(s -> s.join())
.collect(Collectors.toList());
}
/**
*1花费时间是:3025
*------------
*2花费时间是:1009
*/
public static void main(String[] args) {
long start = System.currentTimeMillis();
getPrice(list, "mysql");
long end = System.currentTimeMillis();
System.out.println("1花费时间是:" +(end - start));
System.out.println("------------");
long start2 = System.currentTimeMillis();
getPriceByCompletableFuture(list, "mysql");
long end2 = System.currentTimeMillis();
System.out.println("2花费时间是:" +(end2 - start2));
}
}
class NetMall{
@Getter
private String netMallName;
public NetMall(String netMallName) {
this.netMallName = netMallName;
}
public Double calcPrice(String productName){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
}
}
9.CompletableFuture常用方法
获取结果:
public T get();
public T get(long timeout, TimeUnit unit);
public T join();
public T getNow(T valueIfAbsent);//计算完成就返回正常值,否则返回传入的参数,立即获取结果不阻塞
主动触发计算:
public boolean complete(T value);//是否打断get方法立即返回括号值
对计算结果进行处理:
/**
*计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步
*骤有异常的话就叫停
*/
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn);
/**
*计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步
*/
public <U> CompletableFuture<U> handle(
BiFunction<? super T, Throwable, ? extends U> fn)
对计算结果进行消费:
public CompletableFuture<Void> thenAccept(Consumer<? super T> action);
对比补充:
public CompletableFuture<Void> thenRun(Runnable action);
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn);
public CompletableFuture<Void> thenAccept(Consumer<? super T> action);
- thenRun :任务A执行完执行B,并且不需要A的结果
- thenAccept,消费型接口, 任务A执行完执行B,B需要A的结果,但是任务B没有返回值
- thenApply,函数型接口,任务A执行完执行B,B需要A的结果,同时任务B有返回值
带 Async 和不带 Async 的区别
- 如果没有传入自定义线程池,都用默认线程池ForkJoinPool
- 传入一个线程池,如果你执行第一个任务时,传入了一个自定义线程池
- 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务时共用同一个线程池
- 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自定义的线程池,第二个任务使用的是ForkJoin线程池
- 备注:可能是线程处理太快,系统优化切换原则, 直接使用main线程处理
例如:
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
return uniAcceptStage(defaultExecutor(), action);
}
public Executor defaultExecutor() {
return ASYNC_POOL;
}
private static final Executor ASYNC_POOL = USE_COMMON_POOL ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
private static final boolean USE_COMMON_POOL =
(ForkJoinPool.getCommonPoolParallelism() > 1);
对计算速度进行选用:二选一
public <U> CompletableFuture<U> applyToEither(
CompletionStage<? extends T> other, Function<? super T, U> fn);
对计算结果合并:
public <U,V> CompletableFuture<V> thenCombine(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn);
10.乐观锁和悲观锁
悲观锁:认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改,synchronized和Lock的实现类都是悲观锁,适合写操作多的场景,先加锁可以保证写操作时数据正确,显示的锁定之后再操作同步资源。
乐观锁:认为自己在使用数据的时候不会有别的线程修改数据或资源,不会添加锁,Java中使用无锁编程来实现,只是在更新的时候去判断,之前有没有别的线程更新了这个数据,如果这个数据没有被更新,当前线程将自己修改的数据成功写入,如果已经被其他线程更新,则根据不同的实现方式执行不同的操作,比如:放弃修改、重试抢锁等等。判断规则有:版本号机制Version,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。适合读操作多的场景,不加锁的特性能够使其读操作的性能大幅提升,乐观锁则直接去操作同步资源,是一种无锁算法。
11.锁相关的8种案例演示code
class Phone {
public synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public synchronized void sendSMS() {
System.out.println("------sendSMS");
}
public void hello() {
System.out.println("------hello");
}
}
/**
* 现象描述:
* 1 标准访问ab两个线程,请问先打印邮件还是短信? --------先邮件,后短信 共用一个对象锁
* 2. sendEmail钟加入暂停3秒钟,请问先打印邮件还是短信?---------先邮件,后短信 共用一个对象锁
* 3. 添加一个普通的hello方法,请问先打印普通方法还是邮件? --------先hello,再邮件
* 4. 有两部手机,请问先打印邮件还是短信? ----先短信后邮件 资源没有争抢,不是同一个对象锁
* 5. 有两个静态同步方法,一步手机, 请问先打印邮件还是短信?---------先邮件后短信 共用一个类锁
* 6. 有两个静态同步方法,两部手机, 请问先打印邮件还是短信? ----------先邮件后短信 共用一个类锁
* 7. 有一个静态同步方法 一个普通同步方法,请问先打印邮件还是短信? ---------先短信后邮件 一个用类锁一个用对象锁
* 8. 有一个静态同步方法,一个普通同步方法,两部手机,请问先打印邮件还是短信? -------先短信后邮件 一个类锁一个对象锁
*/
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.sendSMS();
}, "b").start();
}
}
结论:
- 对于普通同步方法,锁的是当前实例对象,通常指this。
- 对于静态同步方法,锁的时当前类的Class对象。
- 对于同步方法块,锁的时synchronized括号内的对象。
最佳实战:
无锁 ——> 同步代码块 ——> 对象锁 ——> 类锁
12.从字节码角度分析 synchronized 的实现
命令:javap -v SynchronizedTest.class
public class SynchronizedTest {
private static final Object obj = new Object();
public void m1(){
synchronized (obj){
System.out.println("SynchronizedTest .... m1...");
}
}
public synchronized void m2(){
System.out.println("SynchronizedTest ..synchronized ..m2...");
}
public static synchronized void m3(){
System.out.println("SynchronizedTest ..static ..synchronized ..m2...");
}
public static void main(String[] args) {
}
}
m1:
代码块使用 monitorenter 和 monitorexit 来进出锁,第一个monitorexit(即:15:monitorexit)表示正常释放锁,第二个monitorexit(即:21:monitorexit)表示异常释放锁。
public void m1();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #7 // Field obj:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter
6: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #19 // String SynchronizedTest .... m1...
11: invokevirtual #21 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: aload_1
15: monitorexit
16: goto 24
19: astore_2
20: aload_1
21: monitorexit
22: aload_2
23: athrow
24: return
m2:
同步方法使用 ACC_SYNCHRONIZED 来标识同步方法,执行线程会将现持有monitor锁,然后再执行该方法,最后在方法完成(无论是否正常结束)时释放monitor。
public synchronized void m2();
descriptor: ()V
flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #27 // String SynchronizedTest ..synchronized ..m2...
5: invokevirtual #21 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
m3:
静态同步方法使用 ACC_STATIC 和 ACC_SYNCHRONIZED来标识静态同步方法。
public static synchronized void m3();
descriptor: ()V
flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #29 // String SynchronizedTest ..static ..synchronized ..m2...
5: invokevirtual #21 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
13.为什么任何一个对象都可以成为一个锁
C++源码:ObjectMonitor.java--->ObjectMonitor.cpp--->ObjectMonitor.hpp
每个对象天生都带着一个对象监视器,每一个被锁住的对象都会和Monitor关联起来。
总结:指针指向Monitor对象(也称为管程或监视器)的真实地址。每个对象都存在着一个monitor与之关联,当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由OnjectMonitor实现的,其主要的数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现)
14.公平锁和非公平锁
公平锁:是指多个线程按照申请锁的顺序来获取锁,这里类似于排队买票,先来的人先买,后来的人再队尾排着,这是公平的。
Lock lock = new ReentrantLock(true);//公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁:是指多个线程获取锁的顺序并不是按照申请的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级反转或者饥饿的状态(某个线程一直得不到锁)
Lock lock = new ReentrantLock();//默认非公平锁
Lock lock = new ReentrantLock(false);//非公平锁
为什么默认非公平锁?
- 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分地利用CPU的时间片,尽量减少CPU空间状态时间。
- 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当一个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得很大,所以就减少了线程的开销。
什么时候用公平?什么时候用非公平?
如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省了很多线程切换的时间,吞吐量自然就上去了;否则就用公平锁,大家公平使用。
15.可重入锁
是指同一线程在外层方法获取到锁的时侯,在进入该线程的内层方法会自动获取锁(前提,锁对象的是同一个对象),不会因为之前已经获取过还没释放而阻塞。
场景:避免死锁 。
15.1可重入锁种类
15.1.1 隐式锁(即synchronized关键字使用的锁),默认是可重入锁。
在一个synchronized修饰的方法或者代码块的内部调用本类的其他synchronized修饰的方法或者代码块时,是永远可以得到锁
15.1.2 显式锁(即Lock)也有ReentrantLock这样的可重入锁
需要注意的是显式锁加锁几次就需要解锁几次。
15.2可重入锁实现原理
每个锁对象拥有一个锁计数器和一个指向持有该锁线程的指针。
当执行 monitorenter 时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。在目标所对象的计数器不为零的时候,如果锁对象的持有线程是当前线程,那么java虚拟机会将其计数器加1,当执行 monitorexit 的时候,java虚拟机会将锁对象的计数器减1,计数器减至0则代表锁已被释放。
public class ReentryLockDemo {
private static final Object o = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (o) {
System.out.println("---------------外层调用");
synchronized (o) {
System.out.println("---------------中层调用");
synchronized (o) {
System.out.println("---------------内层调用");
}
}
}
}, "t1").start();
Lock lock = new ReentrantLock();
new Thread(() -> {
lock.lock();
try {
System.out.println("---------------外层调用");
lock.lock();
try {
System.out.println("---------------中层调用");
lock.lock();
try {
System.out.println("---------------内层调用");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}, "t2").start();
}
}
16. 死锁及排查
16.1死锁
死锁产生的原因:线程对资源没有合理的使用,线程推进顺序不正确。
死锁案例:
public class DeathLock {
private static final Object obj1 = new Object();
private static final Object obj2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行");
synchronized (obj1) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "持有资源obj1,希望得到资源obj2");
synchronized (obj2) {
System.out.println(Thread.currentThread().getName() + "得到资源obj2");
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行");
synchronized (obj2) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "持有资源obj2,希望得到资源obj1");
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + "得到资源obj1");
}
}
}
});
thread1.start();
thread2.start();
System.out.println("方法结束.....");
}
}
方法结束.....
Thread-1开始执行
Thread-0开始执行
Thread-1持有资源obj2,希望得到资源obj1
Thread-0持有资源obj1,希望得到资源obj2
结果:可以看出Thread-1得不到obj1,Thread-0得不到obj2,处在一个死锁的状态。
16.2 排查死锁:
方式1:使用命令
jps -l
11672 com.example.juc.deathlock.DeathLock
696 jdk.jcmd/sun.tools.jps.Jps
9032 org.jetbrains.jps.cmdline.Launcher
13468 org.jetbrains.idea.maven.server.RemoteMavenServer36
7468
jstack 11672
Java stack information for the threads listed above:
===================================================
"Thread-0":
at com.example.juc.deathlock.DeathLock$1.run(DeathLock.java:22)
- waiting to lock <0x000000071193e648> (a java.lang.Object)
- locked <0x000000071193e638> (a java.lang.Object)
at java.lang.Thread.run(java.base@17.0.8/Thread.java:833)
"Thread-1":
at com.example.juc.deathlock.DeathLock$2.run(DeathLock.java:39)
- waiting to lock <0x000000071193e638> (a java.lang.Object)
- locked <0x000000071193e648> (a java.lang.Object)
at java.lang.Thread.run(java.base@17.0.8/Thread.java:833)
Found 1 deadlock.
方式2: 使用jconsole
cmd打开jconsole连接进程,点击线程,点击检测死锁即可。
锁总结:
17.线程中断机制
基本方法:
/**
* 设置线程的中断状态为true,发起一个协商而不会立刻停止线程
*/
public void interrupt() {
if (this != Thread.currentThread()) {
checkAccess();
// thread may be blocked in an I/O operation
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupted = true;
interrupt0(); // inform VM of interrupt
b.interrupt(this);
return;
}
}
}
interrupted = true;
// inform VM of interrupt
interrupt0();
}
/**
* 判断线程是否被中断并清除当前中断状态(做了两件事情)
* 1.返回当前线程的中断状态
* 2.将当前线程的中断状态重新设置为false,清除线程的中断状态
* 3.这个方法有点不好理解在于如果连续两次调用此方法,则第二次返回false,因为连续调用两次的结果可能不一样
*/
public static boolean interrupted() {
Thread t = currentThread();
boolean interrupted = t.interrupted;
// We may have been interrupted the moment after we read the field,
// so only clear the field if we saw that it was set and will return
// true; otherwise we could lose an interrupt.
if (interrupted) {
t.interrupted = false;
clearInterruptEvent();
}
return interrupted;
}
/**
* 判断当前线程是否被中断(通过检查中断标志位)
*/
public boolean isInterrupted() {
return interrupted;
}
线程中断原则:一个线程不应该由其他线程来强制中断或者停止,而是应该由线程自己进行停止,所以stop()、resume()、suspend()已经被弃用。
线程中断案例:
使用volatile :
public class InterruptDemo {
//volatile修饰的的变量具有可见性
static volatile boolean isStop = false;
/**
*-----------hello volatile
* -----------hello volatile
* t1 isStop的值被改为true,t1程序停止
*/
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (isStop) {
System.out.println(Thread.currentThread().getName() + " isStop的值被改为true,t1程序停止");
break;
}
System.out.println("-----------hello volatile");
}
}, "t1").start();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
isStop = true;
}, "t2").start();
}
}
使用AtomicBoolean 原子类:
public class InterruptDemo1 {
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
/**
*-----------hello atomicBoolean
* -----------hello atomicBoolean
* -----------hello atomicBoolean
* t1 atomicBoolean的值被改为true,t1程序停止
*/
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (atomicBoolean.get()) {
System.out.println(Thread.currentThread().getName() + " atomicBoolean的值被改为true,t1程序停止");
break;
}
System.out.println("-----------hello atomicBoolean");
}
}, "t1").start();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
atomicBoolean.set(true);
}, "t2").start();
}
}
interrupt() 和isInterrupted()组合使用中断线程:
public class InterruptDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " isInterrupted()的值被改为true,t1程序停止");
break;
}
System.out.println("-----------hello isInterrupted()");
}
}, "t1");
t1.start();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//t2向t1放出协商,将t1中的中断标识位设为true,希望t1停下来
new Thread(() -> t1.interrupt(), "t2").start();
//当然,也可以t1自行设置
t1.interrupt();
}
}
当前线程的中断标识为true,是不是线程就立刻停止?
不回立即停止,具体来说,当对一个线程,调用interrupt时:
- 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而已,被设置中断标志的线程将继续正常运行,不受影响,所以interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行,对于不活动的线程没有任何影响。
- 如果线程处于阻塞状态(例如sleep,wait,join状态等),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(interrupt状态也将被清除),并抛出一个InterruptedException异常。
正常活动线程:
public class InterruptDemo3 {
/**
* t1线程调用interrupt()后的中断标志位01:true
* t1线程调用interrupt()后的中断标志位02:true
* t1线程调用interrupt()后的中断标志位03:true
*/
public static void main(String[] args) {
//实例方法interrupt()仅仅是设置线程的中断状态位为true,不会停止线程
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 300; i++) {
System.out.println("------: " + i);
}
System.out.println("t1线程调用interrupt()后的中断标志位02:" + Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
System.out.println("t1线程默认的中断标志位:" + t1.isInterrupted());//false
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();//true
System.out.println("t1线程调用interrupt()后的中断标志位01:" + t1.isInterrupted());
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2000毫秒后,t1线程已经不活动了,不会产生任何影响
System.out.println("t1线程调用interrupt()后的中断标志位03:" + t1.isInterrupted());
}
}
阻塞状态线程:
public class InterruptDemo4 {
/**
* 线程处于阻塞状态,在别的线程中调用当前线程对象的interrupt方法
* 那么线程将立即退出被阻塞状态(interrupt状态也将被清除(interrupted = false)),并抛出一个InterruptedException异常
*/
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " 中断标志位为:" + Thread.currentThread().isInterrupted() + " 程序停止");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
System.out.println("-------------hello InterruptDemo3");
}
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
t1.interrupt();
}, "t2").start();
}
}
18.LockSupport
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,其中park()和unpack()而作用分别是阻塞线程和解除阻塞线程。
package java.util.concurrent.locks;
public class LockSupport {
private LockSupport() {} // Cannot be instantiated.
public static void unpark(Thread thread) {
if (thread != null)
U.unpark(thread);
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
U.park(false, 0L);
setBlocker(t, null);
}
}
三种让线程等待和唤醒的方法:
- 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程,wait和notify方法必须要在同步代码块或者方法里面,且成对出现使用,并且先wait再notify才行。
- 使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程,Condition中的线程等待和唤醒方法,需要先获取锁,一定要先await后signal
- LockSupport类park()和unpack()可以阻塞当前线程以及唤醒指定被阻塞的线程
使用Object中的wait()方法和notify()方法:
public class LockSupportDemo {
/**
* t1 -----------come in
* t2 -----------发出通知
* t1 -------被唤醒
*/
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(() -> {
synchronized (objectLock) {
System.out.println(Thread.currentThread().getName() + "\t -----------come in");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t -------被唤醒");
}
}, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify();
System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
}
}, "t2").start();
}
}
Condition的await()和signal()方法:
public class LockSupportDemo1 {
/**
* t1 -----------come in
* t2 -----------发出通知
* t1 -----------被唤醒
*/
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t -----------come in");
condition.await();
System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
} finally {
lock.unlock();
}
}, "t2").start();
}
}
LockSupport类park()和unpack():类似ETC行车
public class LockSupportDemo2 {
/**
* t1 -----------come in
* t2 ----------发出通知
* t1 ----------被唤醒
*/
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t -----------come in");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");
}, "t2").start();
}
}
重点说明:
- LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,所有的方法都是静态方法,可以让线程再任意位置阻塞,阻塞后也有对应的唤醒方法。归根结底,LockSupport时调用Unsafe中的native代码
- LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程,LockSupport和每个使用它的线程都有一个许可(Peimit)关联,每个线程都有一个相关的permit,peimit最多只有一个,重复调用unpark也不会积累凭证。
- 形象理解:线程阻塞需要消耗凭证(Permit),这个凭证最多只有一个
- 当调用park时,如果有凭证,则会直接消耗掉这个凭证然后正常退出。如果没有凭证,则必须阻塞等待凭证可用;
- 当调用unpark时,它会增加一个凭证,但凭证最多只能有1各,累加无效。
为什么LockSupport可以突破wait/notify的原有调用顺序?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞,先发放了凭证后续可以畅通无阻。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证,而调用两次park却需要消费两个凭证,证不够,不能放行。
19. Java内存模型(JMM)
CPU的运行并不是直接操作内存而是先把内存里面的数据读到缓存,而内存的读和写操作的时候会造成不一致的问题。JVM规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致性的内存访问效果。
JMM(Java内存模型Java Memory Model)本身是一种抽象的概念并不真实存在,它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性和有序性展开的。
JMM来实现线程和主内存之间的抽象关系,屏蔽各个硬件平台和操作系统的内存访问差异以实现让Java程序再各种平台下都能达到一致性的内存访问效果。
系统中主内存共享变量数据修改被写入的时机是不确定的,多线程并发下很可能出现“脏读”,所以每个线程都有自己的工作内存,线程自己的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在线程自己的工作内存中进行,而不能够直接写入主内存中的变量,不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
JMM规范下三大特性:
- 原子性:指一个操作是不可被打断的,即多线程环境下,操作不能被其他线程干扰。
- 可见性:是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更,JMM规定了所有的变量都存储在主内存中。
- 有序性:对于一个线程的执行代码而言,我们总是习惯性地认为代码的执行总是从上到下,有序执行。但为了提升性能,编译器和处理器通常会对指令序列进行重新排序。Java规范规定JVM线程内部维持顺序化语义,即只要程序的最终结果与它顺序话执行的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。
- JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指令进行重排序,使机器指令更符合CPU的执行特性,最大限度的发挥机器性能。
- JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指令进行重排序,使机器指令更符合CPU的执行特性,最大限度的发挥机器性能。
- 单线程环境里确实能够保证程序最终执行结果和代码顺序执行的结果一致
- 处理器在进行重排序时必须考虑到指令之间的数据依赖性
- 多线程环境中线程交替执行,由于编译器优化重排的存在,可能出现乱序现象,两个线程使用的变量能否保证一致性是无法确定的,结果无法预测。
JMM规范下多线程对变量的读写过程:
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有的地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读写赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到线程自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存存储着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
JMM定义了线程和主内存之间的抽象关系:
- 线程之间的共享变量存储在主内存中(从硬件角度讲就是内存条)
- 每个线程都有一个自己的本地工作内存,本地工作内存中存储了该线程用来读写共享变量的副本(从硬件角度来说就是CPU的缓存)
总结:
- 我们定义的所有共享变量都储存在物理主内存中
- 每个线程都有自己独立的工作内存,里面保证该线程使用到的共享变量的副本(主内存中该变量的一份拷贝)
- 线程对共享变量所有的操作都必须先在线程自己的工作内存中进行后写回主内存,不能直接从主内存在读写(不能越级)
- 不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行(同级不能互相访问)。
JMM规范下多线程先行发生原则之happens-before:
在JVM中,如果一个操作执行的结果需要对另一个操作可见或者代码重排序,那么这两个操作之间必须存在happens-before(先行发生)原则,逻辑上的先后关系。
先行并发原则说明:
- 如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成,那么有很多操作都将变得非常罗嗦,但是我们在编写Java并发代码的时候并没有察觉到这一点。
- 我们没有时时、处处、次次,添加volatile和synchronized来完成程序,这是因为Java语言中JMM原则下,有一个“先行发生”(happens-before)的原则限制和规矩,给你理好了规矩!
- 这个原则非常重要:它是判断数据是否存在竞争,线程是否安全的非常有用的手段。依赖这个原则,我们可以通过几条简单规则一揽子解决并发环境下两个操作之间是否可能存在冲突的所有问题,而不需要陷入Java内存模型晦涩难懂的底层编译原理之中。
happens-before总原则:
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前
- 如果两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
happens-before之8条:
- 次序规则:一个线程内,按照代码的顺序,写在前面的操作先行发生于写在后面的操作,也就是说前一个操作的结果可以被后续的操作获取(保证语义串行性,按照代码顺序执行)。比如前一个操作把变量x赋值为1,那后面一个操作肯定能知道x已经变成了1
- 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作(后面指时间上的先后)。
- volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的后面同样指时间上的先后
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。
- 线程启动规则(Thread start Rule):Thread对象的start()方法先行发生于此线程的每一个动作
- 线程中断规则(Thread Interruption Rule):
- 对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 可以通过Thread.interrupted()检测到是否发生中断
- 也就是说你要先调用interrupt()方法设置过中断标志位,我才能检测到中断发生
- 线程终止规则(Thread Termination Rule):线程中的所有操作都优先发生于对此线程的终止检测,我们可以通过isAlive()等手段检测线程是否已经终止执行。
- 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始------->对象没有完成初始化之前,是不能调用finalized()方法的
happens-before小总结:
- 在Java语言里面,Happens-before的语义本质上是一种可见性
- A happens-before B ,意味着A发生过的事情对B而言是可见的,无论A事件和B事件是否发生在同一线程里
- JVM的设计分为两部分:
- 一部分是面向我们程序员提供的,也就是happens-before规则,它通俗易懂的向我们程序员阐述了一个强内存模型,我们只要理解happens-before规则,就可以编写并发安全的程序了
- 另一部分是针对JVM实现的,为了尽可能少的对编译器和处理器做约束从而提升性能,JMM在不影响程序执行结果的前提下对其不做要求,即允许优化重排序,我们只要关注前者就好了,也就是理解happens-before规则即可,其他繁杂的内容由JMM规范结合操作系统给我们搞定,我们只写好代码即可。
案例说明:
private int value =0;
public int getValue(){
return value;
}
public int setValue(){
return ++value;
}
问题描述:假设存在线程A和B,线程A先(时间上的先后)调用了setValue()方法,然后线程B调用了同一个对象的getValue()方法,那么线程B收到的返回值是什么?
答案:不一定
分析happens-before规则(规则5,6,7,8可以忽略,和代码无关)
1 由于两个方法由不同线程调用,不满足一个线程的条件,不满足程序次序规则
2 两个方法都没有用锁,不满足锁定规则
3 变量没有使用volatile修饰,所以不满足volatile变量规则
4 传递规则肯定不满足
综上:无法通过happens-before原则推导出线程A happens-before 线程B,虽然可以确定时间上线程A优于线程B,但就是无法确定线程B获得的结果是什么,所以这段代码不是线程安全的
注意:
如果两个操作的执行次序无法从happens-before原则推导出来,那么就不能保证他们的有序性,虚拟机可以随意对他们进行重排序。
如何修复?
把getter/setter方法都定义为synchronized方法------->不好,重量锁,并发性下降。
private int value =0;
public synchronized int getValue(){
return value;
}
public synchronized int setValue(){
return ++value;
}
把Value定义为volatile变量,由于setter方法对value的修改不依赖value的原值,满足volatile关键字使用场景:
/**
* 利用volatile保证读取操作的可见性,
* 利用synchronized保证符合操作的原子性结合使用锁和volatile变量来减少同步的开销
*/
private volatile int value =0;
public int getValue(){
return value;
}
public synchronized int setValue(){
return ++value;
}
20. volatile
volatile的特点是可以保证可见性和有序性(有时需要禁止指令重排),但无法保证原子性。
可见性:
- 当写一个volatile变量时,JMM会把该线程本地内存中的值立即刷新到主内存中。
- 当读一个volatile变量时,JMM会把该线程本地内存的值设置为无效,重新到主内存中读取最新的值。
public class VolatileSeeDemo {
static volatile boolean flag = true;
/**
* 不加 volatile 程序一直在执行
* 加了 volatile 程序直接停止
* 原因,线程 t1 拷贝了 flag 的内存副本,main改变了 flag 的值 但是对线程 t1 不可见
*/
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t-------come in");
while (flag) {
}
System.out.println(Thread.currentThread().getName() + "\t-------flag被设置为false,程序停止");
}, "t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新flag值
flag = false;
System.out.println(Thread.currentThread().getName() + "\t 修改完成");
}
}
有序性:
指令重排是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序。若不存在数据依赖关系,可以重排序;存在数据依赖关系,禁止重排序;在并发设计中需要重点考虑指令重排后绝对不能改变原有的串行语义。
无法保证原子性:
public class VolatileNoAtomicTest {
/** 原子性测试
* 每次运行的结果都不等于10000
*/
public static void main(String[] args) {
MyNumber myNumber = new MyNumber();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myNumber.addPlusPlus();
}
},String.valueOf(i)).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(myNumber.number);
}
}
class MyNumber{
volatile int number;
public void addPlusPlus(){
number++;
}
}
volatile如何实现可见性和有序性?
通过内存屏障实现,内存屏障(Memory Barrier)是一种同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作,避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,不允许把内存屏障之后的指令重排序到内存屏障之前。
内存屏障粗分两种:
- 写屏障(Store Memory Barrier):在写指令之后插入写屏障,强制把缓冲区的数据刷回到主内存中。
- 读屏障(Load Memory Barrier):在读指令之前插入读屏障,让工作内存或CPU高速缓存 当中的缓存数据失效,重新回到主内存中获取最新数据。
内存屏障细分四种:
屏障类型 | 指令示例 | 说明 |
LoadLoad | Load1;LoadLoad;Load2 | 禁止重排序:load2操作不会重排到load1之前 保证load2的操作读取的是主内存的最新数据 |
LoadStore | Load1;LoadStore;Store2 | 禁止重排序:load1操作读取完数据之后,才能让Store2及其之后的操作执行 |
StoreStore | Store1;StoreStore;Store2 | 禁止重排序:一定是Store1的数据写出到主内存后,才能让Store2及其之后的操作执行 保证Store1的操作写的数据,会被刷新到主内存 |
StoreLoad | Store1;StoreLoad;Load2 | 禁止重排序:一定是Store1的数据写出到主内存后,才能让Load2及其之后的操作执行 保证Store1的操作写的数据,会被刷新到主内存后,让Load2在主内存读取最新的数据 |
21. CAS(compare and swap)
CAS(compare and swap),为比较并交换,实现并发算法时常用到的一种技术,用于保证共享变量的原子性更新,它包含三个操作数---内存位置、预期原值与更新值。
CAS底层原理:
Unsafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,实现方式是基于硬件平台的汇编指令compxchg,去比较并更新变量值(原子性)。
AtomicInteger类主要利用CAS+volatile和native方法来保证原子操作,从而避免synchronized的高开销,执行效率大为提升:
new AtomicInteger(5).getAndIncrement();
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
}
public final class Unsafe {
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
public final boolean weakCompareAndSetInt(Object o, long offset,
int expected,
int x) {
return compareAndSetInt(o, offset, expected, x);
}
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
}
原子引用及案例:
public class AtomicReference<V> implements java.io.Serializable {}
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<>();
User zhangsan = new User("zhangsan", 18);
User lisi = new User("lisi", 28);
atomicReference.set(zhangsan);
System.out.println(atomicReference.compareAndSet(zhangsan, lisi) + "\t" + atomicReference.get());
System.out.println(atomicReference.compareAndSet(zhangsan, lisi) + "\t" + atomicReference.get());
}
}
@AllArgsConstructor
@NoArgsConstructor
@Data
class User{
private String name;
private Integer agee;
}
自旋锁:
自旋锁就是自已旋转的锁,是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。CAS是实现自旋锁的基础,CAS利用CPU指令保证了操作的原子性,以达到锁的效果。
自旋锁案例:
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t --------come in");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void unLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "\t --------task over,unLock.........");
}
/**
* A --------come in
* B --------come in
* A --------task over,unLock.........
* B --------task over,unLock.........
*/
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.lock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unLock();
}, "A").start();
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.lock();
spinLockDemo.unLock();
}, "B").start();
}
}
自旋锁的缺点:
CPU空转和ABA问题。长时间do while 不跳出,会带来很大的CPU开销;ABA问题的最终结果符合预期值,但是不代表中间过程没有问题,预期值已经被改过。
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
ABA问题解决:
添加版本号或者戳流水号。
21. 原子操作类
基本原子类型案例:
class MyNumber {
AtomicInteger atomicInteger = new AtomicInteger();
public void addPlusPlus() {
atomicInteger.getAndIncrement();
}
}
public class AtomicIntegerDemo {
public static final int SIZE = 50;
public static void main(String[] args) throws InterruptedException {
MyNumber myNumber = new MyNumber();
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
for (int i = 1; i <= SIZE; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 10; j++) {
myNumber.addPlusPlus();
}
} finally {
countDownLatch.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "\t" + "result: " + myNumber.atomicInteger.get());//main result: 500
}
}
引用原子类型案例:一次性筷子
public class AtomicMarkableReferenceDemo {
static AtomicMarkableReference markableReference = new AtomicMarkableReference(100, false);
/**
* 只能修改一次
* t2 默认标识: false
* t1 默认标识: false
* t2 t2线程CASResult:false
* t2 true
* t2 1000
*/
public static void main(String[] args) {
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t1 默认标识: false
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
markableReference.compareAndSet(100, 1000, marked, !marked);//t2 默认标识: false
}, "t1").start();
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t2 t2线程CASResult:false
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = markableReference.compareAndSet(100, 2000, marked, !marked);
System.out.println(Thread.currentThread().getName() + "\t" + "t2线程CASResult:" + b);
System.out.println(Thread.currentThread().getName() + "\t" + markableReference.isMarked());//t2 true
System.out.println(Thread.currentThread().getName() + "\t" + markableReference.getReference());//t2 1000
}, "t2").start();
}
}
对象的属性修改原子类:
目的:以一种线程安全的方式操作非线程安全对象内的某些字段。
案例1:加钱
class BankAccount {
public volatile int money = 0;
AtomicIntegerFieldUpdater<BankAccount> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");
public void transferMoney(BankAccount bankAccount) {
atomicIntegerFieldUpdater.getAndIncrement(bankAccount);
}
}
public class AtomicIntegerFieldUpdaterDemo {
public static void main(String[] args) throws InterruptedException {
BankAccount bankAccount = new BankAccount();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 1000; j++) {
bankAccount.transferMoney(bankAccount);
}
} finally {
countDownLatch.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + '\t' + "result: " + bankAccount.money); //main result: 10000
}
}
案例2:保证只有一个线程能执行任务
class MyVar {
public volatile Boolean isInit = Boolean.FALSE;
AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");
public void init(MyVar myVar) {
if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
System.out.println(Thread.currentThread().getName() + "\t" + "--------------start init ,need 2 secondes");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "--------------over init");
} else {
System.out.println(Thread.currentThread().getName() + "\t" + "--------------已经有线程进行初始化工作了。。。。。");
}
}
}
public class AtomicReferenceFieldUpdaterDemo {
/**
* 1 --------------start init ,need 2 secondes
* 4 --------------已经有线程进行初始化工作了。。。。。
* 2 --------------已经有线程进行初始化工作了。。。。。
* 5 --------------已经有线程进行初始化工作了。。。。。
* 3 --------------已经有线程进行初始化工作了。。。。。
* 1 --------------over init
*/
public static void main(String[] args) {
MyVar myVar = new MyVar();
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
myVar.init(myVar);
}, String.valueOf(i)).start();
}
}
}
原子操作增强类:
DoubleAccumulator
:一个或多个变量,它们一起保持运行double使用所提供的功能更新值DoubleAdder
:一个或多个变量一起保持初始为零double总和LongAccumulator
:一个或多个变量,一起保持使用提供的功能更新运行的值long ,提供了自定义的函数操作LongAdder
:一个或多个变量一起维持初始为零long总和(重点),只能用来计算加法,且从0开始计算
案例1:计数器
差距过于巨大!!!!增强类比原子类强了不是一点半点
class ClickNumber {
int number = 0;
public synchronized void clickBySynchronized() {
number++;
}
AtomicLong atomicLong = new AtomicLong(0);
public void clickByAtomicLong() {
atomicLong.getAndIncrement();
}
LongAdder longAdder = new LongAdder();
public void clickByLongAdder() {
longAdder.increment();
}
LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
public void clickByLongAccumulator() {
longAccumulator.accumulate(1);
}
}
public class AccumulatorCompareDemo {
public static final int _1W = 10000;
public static final int THREAD_NUMBER = 50;
/**
* ------costTime: 3645 毫秒 clickBySynchronized: 50000000
* ------costTime: 785 毫秒 clickByAtomicLong: 50000000
* ------costTime: 73 毫秒 clickByLongAdder: 50000000
* ------costTime: 55 毫秒 clickByLongAccumulator: 50000000
*/
public static void main(String[] args) throws InterruptedException {
ClickNumber clickNumber = new ClickNumber();
long StartTime;
long endTime;
CountDownLatch countDownLatch1 = new CountDownLatch(THREAD_NUMBER);
CountDownLatch countDownLatch2 = new CountDownLatch(THREAD_NUMBER);
CountDownLatch countDownLatch3 = new CountDownLatch(THREAD_NUMBER);
CountDownLatch countDownLatch4 = new CountDownLatch(THREAD_NUMBER);
StartTime = System.currentTimeMillis();
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickBySynchronized();
}
} finally {
countDownLatch1.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch1.await();
endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickBySynchronized: " + clickNumber.number);
StartTime = System.currentTimeMillis();
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByAtomicLong();
}
} finally {
countDownLatch2.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch2.await();
endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByAtomicLong: " + clickNumber.atomicLong.get());
StartTime = System.currentTimeMillis();
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByLongAdder();
}
} finally {
countDownLatch3.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch3.await();
endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAdder: " + clickNumber.longAdder.sum());
StartTime = System.currentTimeMillis();
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByLongAccumulator();
}
} finally {
countDownLatch4.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch4.await();
endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAccumulator: " + clickNumber.longAccumulator.get());
}
}
LongAdder为什么这么快:
- LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多,能够减少自旋次数,如果要获取真正的long值,只要将各个槽中的变量值累加返回
- sum()会将所有的Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。
LongAdder源码分析:what the fuck ???
public void increment() {
add(1L);
}
public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
if ((cs = cells) != null || !casBase(b = base, b + x)) {
int index = getProbe();
boolean uncontended = true;
if (cs == null || (m = cs.length - 1) < 0 ||
(c = cs[index & m]) == null ||
!(uncontended = c.cas(v = c.value, v + x)))
longAccumulate(x, null, uncontended, index);
}
}
final boolean casBase(long cmp, long val) {
return BASE.weakCompareAndSetRelease(this, cmp, val);
}
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended, int index) {
if (index == 0) {
ThreadLocalRandom.current(); // force initialization
index = getProbe();
wasUncontended = true;
}
for (boolean collide = false;;) { // True if last slot nonempty
Cell[] cs; Cell c; int n; long v;
if ((cs = cells) != null && (n = cs.length) > 0) {
if ((c = cs[(n - 1) & index]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
if (cellsBusy == 0 && casCellsBusy()) {
try { // Recheck under lock
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & index] == null) {
rs[j] = r;
break;
}
} finally {
cellsBusy = 0;
}
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (c.cas(v = c.value,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
break;
else if (n >= NCPU || cells != cs)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == cs) // Expand table unless stale
cells = Arrays.copyOf(cs, n << 1);
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
index = advanceProbe(index);
}
else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
try { // Initialize table
if (cells == cs) {
Cell[] rs = new Cell[2];
rs[index & 1] = new Cell(x);
cells = rs;
break;
}
} finally {
cellsBusy = 0;
}
}
// Fall back on using base
else if (casBase(v = base,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
break;
}
}
LongAdder运行逻辑:
运行结果获取:
public long sum() {
Cell[] cs = cells;
long sum = base;
if (cs != null) {
for (Cell c : cs)
if (c != null)
sum += c.value;
}
return sum;
}
使用总结:
AtomicLong线程安全,可允许一些性能损耗,要求高精度时可使用,保证精度,多个线程对单个热点值value进行了原子操作(保证精度,低性能代价),保证实时数据。
LongAdder当需要在高并发场景下有较好的性能表现,且对值得精确度要求不高时,可以使用,LongAdder时每个线程拥有自己得槽,各个线程一般只对自己槽中得那个值进行CAS操作(保证性能,低精度代价),保证最终一致性。
22. ThreadLocal
ThreadLocal为每个线程提供专属的实例副本,其他线程不可访问,可以避免了线程安全问题。
class House {
int saleCount = 0;
public synchronized void saleHouse() {
saleCount++;
}
ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);
public void saleVolumeByThreadLocal() {
saleVolume.set(1 + saleVolume.get());
}
}
public class ThreadLocalDemo {
/**
* 统计总销售额和每个线程自己的销售额
*/
public static void main(String[] args) {
House house = new House();
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
int size = new Random().nextInt(5) + 1;
try {
for (int j = 1; j <= size; j++) {
house.saleHouse();
house.saleVolumeByThreadLocal();
}
System.out.println(Thread.currentThread().getName() + "\t" + "号销售卖出:" + house.saleVolume.get());
} finally {
house.saleVolume.remove();
}
}, String.valueOf(i)).start();
}
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "共计卖出多少套: " + house.saleCount);
}
}
ThreadLocal源码分析:内保部维护了一个ThreadLocalMap, key为ThreadLocal对象
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
强软弱虚引用:
- 强引用:
- 对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收,当一个对象被强引用变量引用时,它处于可达状态,是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM也不会回收,因此强引用是造成Java内存泄露的主要原因之一。
- 软引用:
- 是一种相对强引用弱化了一些的引用,对于只有软引用的对象而言,当系统内存充足时,不会被回收,当系统内存不足时,他会被回收,软引用通常用在对内存敏感的程序中,比如高速缓存,内存够用就保留,不够用就回收。
- 弱引用:
- 比软引用的生命周期更短,对于只有弱引用的对象而言,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
- 软引用和弱引用的使用场景----->假如有一个应用需要读取大量的本地图片:
- 如果每次读取图片都从硬盘读取则会严重影响性能
- 如果一次性全部加载到内存中又可能会造成内存溢出
- 此时使用软应用来解决,设计思路时:用一个HashMap来保存图片的路径和与相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,有效避免了OOM的问题
- 虚引用:
- 虚引用必须和引用队列联合使用,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都有可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象。
- 虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize后,做某些事情的通知机制。换句话说就是在对象被GC的时候会收到一个系统通知或者后续添加进一步的处理,用来实现比finalize机制更灵活的回收操作。
为什么要用弱引用?不用如何?
为什么要用弱引用:
- 当方法执行完毕后,栈帧销毁,强引用t1也就没有了,但此时线程的ThreadLocalMap里某个entry的Key引用还指向这个对象,若这个Key是强引用,就会导致Key指向的ThreadLocal对象即V指向的对象不能被gc回收,造成内存泄露
- 若这个引用时弱引用就大概率会减少内存泄漏的问题(当然,还得考虑key为null这个坑),使用弱引用就可以使ThreadLocal对象在方法执行完毕后顺利被回收且entry的key引用指向为null.
这里有个需要注意的问题:
- ThreadLocalMap使用ThreadLocal的弱引用作为Key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc时,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现Key为null的Entry,就没有办法访问这些Key为null的Entry的value,如果当前线程迟迟不结束的话(好比正在使用线程池),这些key为null的Entry的value就会一直存在一条强引用链
- 虽然弱引用,保证了Key指向的ThreadLocal对象能够被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现key为null时才会去回收整个entry、value,因此弱引用不能100%保证内存不泄露,我们要在不使用某个ThreadLocal对象后,手动调用remove方法来删除它,尤其是在线程池中,不仅仅是内存泄漏的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。
清除脏Entry----key为null的entry:
set()方法:
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.refersTo(key)) {
e.value = value;
return;
}
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.refersTo(null))
slotToExpunge = i;
// Find either the key or trailing null slot of run, whichever
// occurs first
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
if (e.refersTo(key)) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// Start expunge at preceding stale entry if it exists
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (e.refersTo(null) && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// If there are any other stale entries in run, expunge them
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
get()方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.refersTo(key))
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
if (e.refersTo(key))
return e;
if (e.refersTo(null))
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
remove()方法:
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.refersTo(key)) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
最佳实战:
- ThreadLocal一定要初始化,避免空指针异常。
- 建议把ThreadLocal修饰为static
- 用完记得手动remove
23. Java对象内存布局和对象头
在HotSpot虚拟机里,对象在堆内存的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data) 和对齐填充(Padding)。
对象头(在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节)
对象标记(Mark Word):
- 默认存储对象的HashCode、分代年龄和锁标志等信息。
- 它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。
- 这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。类元信息(类型指针)
类元信息(类型指针):
对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象哪个类的实例.
实例数据:
存放类的属性(Field)数据信息,包括父类的属性信息
对齐填充(保证8个字节的倍数)
虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按8字节补充对齐。