创建线程有四种方式:
public class ThreadTest {
public static void mAIn(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("关注公众号:一安未来");
}
}
public class ThreadTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("关注公众号:一安未来");
}
}
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThreadCallable mc = new MyThreadCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
}
class MyThreadCallable implements Callable {
@Override
public String call()throws Exception {
return "关注公众号:一安未来";
}
}
public class ThreadTest {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executorOne = new ThreadPoolExecutor(5, 5, 1,
TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(20), new CustomizableThreadFactory("Yian-Thread-pool"));
executorOne.execute(() -> {
System.out.println("关注公众号:一安未来");
});
//关闭线程池
executorOne.shutdown();
}
}
线程和进程是操作系统中重要的概念,都是操作系统资源分配的基本单位,但它们有一些关键的区别。
主要用于声明变量,以指示该变量可能会被多个线程同时访问,从而防止编译器进行一些优化,确保线程之间能够正确地读写共享变量。volatile 提供了一种轻量级的同步机制,但它并不能替代 synchronized,因为它无法解决复合操作的原子性问题。
作用:
原理:
volatile 的实现原理涉及到 CPU 的缓存一致性和内存屏障(Memory Barrier)的概念。
如果synchronized作用于代码块,反编译可以看到两个指令:monitorenter、monitorexit,JVM使用monitorenter和monitorexit两个指令实现同步;如果作用synchronized作用于方法,反编译可以看到ACCSYNCHRONIZED标记,JVM通过在方法访问标识符(flags)中加入ACCSYNCHRONIZED来实现同步功能。
操作系统的管程(monitors)是概念原理,ObjectMonitor是它的原理实现。
图片
在JAVA虚拟机(HotSpot)中,Monitor(管程)是由ObjectMonitor实现的,其主要数据结构如下:
ObjectMonitor() {
_header = NULL;
_count = 0; // 记录个数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
图片
图片
Mark Word 是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。
图片
重量级锁,指向互斥量的指针。其实synchronized是重量级锁,也就是说Synchronized的对象锁,Mark Word锁标识位为10,其中指针指向的是Monitor对象的起始地址。
在JDK1.6之前,synchronized的实现直接调用ObjectMonitor的enter和exit,这种锁被称之为重量级锁。从JDK6开始,HotSpot虚拟机开发团队对Java中的锁进行优化,如增加了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略,提升了synchronized的性能。
图片
CountDownLatch和CyclicBarrier都用于让线程等待,达到一定条件时再运行。主要区别是:
图片
CPU的缓存是以缓存行(cache line)为单位进行缓存的,当多个线程修改相互独立的变量,而这些变量又处于同一个缓存行时就会影响彼此的性能。这就是伪共享
现代计算机计算模型:
图片
也正是因为缓存行的存在,就导致了伪共享问题,如图所示:
图片
假设数据a、b被加载到同一个缓存行。
解决伪共享问题的一种方法是通过填充(Padding)来确保共享的变量独立存储于不同的缓存行中。填充的思想是在变量之间插入一些无关的数据,使它们分布到不同的缓存行,从而避免多个变量共享同一个缓存行。
在Java中,可以使用@Contended注解来避免伪共享。这个注解可以在字段上使用,它会在字段的前后插入填充,使得字段单独占据一个缓存行。
Fork/Join框架是Java7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
Fork/Join框架需要理解两个点,「分而治之」和「工作窃取」。
图片
图片
一般就是指做得快的线程(盗窃线程)抢慢的线程的任务来做,同时为了减少锁竞争,通常使用双端队列,即快线程和慢线程各在一端。
ThreadLocal的内存结构图:
图片
内存泄露问题:指程序中动态分配的堆内存由于某种原因没有被释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢或者系统奔溃等严重后果。内存泄露堆积将会导致内存溢出。
ThreadLocal的内存泄露问题一般考虑和Entry对象有关,ThreadLocal::Entry被弱引用所修饰。JVM会将弱引用修饰的对象在下次垃圾回收中清除掉。这样就可以实现ThreadLocal的生命周期和线程的生命周期解绑。但实际上并不是使用了弱引用就会发生内存泄露问题,考虑下面几个过程:
图片
当ThreadLocal Ref被回收了,由于在Entry使用的是强引用,在Current Thread还存在的情况下就存在着到达Entry的引用链,无法清除掉ThreadLocal的内容,同时Entry的value也同样会被保留;也就是说就算使用了强引用仍然会出现内存泄露问题。
图片
当ThreadLocal Ref被回收了,由于在Entry使用的是弱引用,因此在下次垃圾回收的时候就会将ThreadLocal对象清除,这个时候Entry中的KEY=null。但是由于ThreadLocalMap中任然存在Current Thread Ref这个强引用,因此Entry中value的值任然无法清除。还是存在内存泄露的问题。
AbstractQueuedSynchronizer(AQS)是Java中用于构建同步器的基础框架。它提供了一个灵活的、可重用的同步器实现,可以用来构建各种同步工具,如ReentrantLock、Semaphore、CountDownLatch等。AQS的核心思想是基于FIFO等待队列,通过状态(state)来管理线程的同步。
核心原理:
ReentrantLock 解析:
图片
图片
CPU上下文:CPU 寄存器,是CPU内置的容量小、但速度极快的内存。而程序计数器,则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。它们都是 CPU 在运行任何任务前,必须的依赖环境,因此叫做
CPU上下文切换:把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
图片