volatile
volatile是java虚拟机提供的轻量级的同步机制
三大特性:保证可见性,不保证原子性,禁止指令重排。
JMM(内存模型):工作内存是每个线程的私有内存区域,而java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量。
public class ShareData {
volatile int i = 0; //可见性证明
public void add() {
i = 100;
}
}
ShareData s = new ShareData();
new Thread(() ->
{
s.add();
}, "t1").start();
while (s.i == 0) {
}
System.out.println("end");
不保证原子性:
public class ShareData {
volatile int i = 0;
public void add() {
i = 100;
}
public void addOne() {
i++;
}
}
main.ShareData s = new main.ShareData();
for (int i = 0; i < 1000; i++)
{
new Thread(() ->
{
s.addOne();
}, "Thread" + String.valueOf(i)).start();
}
//等待线程执行完成
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("end");
System.out.println(s.i);
执行结果:
end
996
AtomicInteger可解决原子性:
AtomicInteger atomicInteger = new AtomicInteger(0);
public void addOne2() {
atomicInteger.getAndIncrement();
}
禁止指令重排: 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
public class InstructionReOrder {
static Integer a = 0;
static Integer b = 0;
static Integer x = 0;
static Integer y = 0;
public void showReOrder() throws InterruptedException {
for(int i=0;i<Integer.MAX_VALUE;i++){
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 有可能发生重排,即 先执行 x = b,再执行 a = 1
a = 1;
x = b;
}
});
Thread t2= new Thread(new Runnable() {
@Override
public void run() {
// 有可能发生重排,即先执行 y = a,再执行 b = 1;
b = 1;
y = a;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
/**
* 如果没有指令重排,输出的可以结果为:(0,1)(1,1)(1,0)
* 但实际上有可能会输出(0,0)
*/
System.out.println("第 "+ i + "次,x="+x +", y="+y);
if(x == 0 && y == 0){
break;
}
a = b = 0;
x = y = 0;
}
}
}
结果:
第 591455次,x=0, y=1
第 591456次,x=0, y=1
第 591457次,x=0, y=0
volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象
内存屏障(Memory Barrirer)又称内存栅栏,是一个CPU指令,它的作用有两个:
1.保证特定操作的执行顺序。2.保证某些变量的内存可见性。
单例模式(双端检锁机制)
public class Single {
public static volatile Single instance = null;
public Single() {
System.out.println(Thread.currentThread().getName() + "构造函数");
}
//双端检锁机制
public static Single getInstance() {
if (instance == null) {
synchronized (Single.class) {
if (instance == null) {
instance = new Single();
}
}
}
return instance;
}
}
instance = new Single();可以分为以下三步完成
1. memory= allocate(); //1分配对象内存空间
2. instance(memory) //2.初始化对象
3. instance=memory;//3.设置instance指向刚分配的内存地址,此时instance!=null
步骤2和3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排序是允许的
1. memory= allocate(); //1分配对象内存空间
3. instance=memory;//3.设置instance指向刚分配的内存地址,此时instance!=null
2. instance(memory) //2.初始化对象
CAS
(compare and set)比较并交换
1.Unsafe
是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据。Unsafe类存在于sun.misc包中。其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
2.变量valueOffset, 表示该变量值在内存中的偏移地址, 因为UnSafe就是根据内存的偏移地址获取数据的。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
value用volatile 修饰,保证了多线程之间内存的可见性。
private volatile int value;
CAS的全称是Compare-And-Swap,它是一条CPU并发原语。
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
CAS并发原语体现在java语言中就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
CAS的缺点:
1. 循环时间长,开销大(如果CAS失败,会一起进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。)
2. 只能保证一个共享变量的原子操作。
3. 会出现ABA问题。
ABA问题解决: 加时间戳
AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<>(0, 0);
atomicReference.compareAndSet(0, 100, 0, 2);
System.out.println(atomicReference.getStamp());
集合不安全类
示例:
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++)
{
new Thread(() ->
{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, "Thread" + String.valueOf(i)).start();
}
Exception in thread "Thread1" java.util.ConcurrentModificationException
(并发修改异常)
导致原因:
并发争抢导致
解决方案:
List<String> list = new Vector<>();
List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); // 复制
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
CopyOnWrite容器是一种读写分离的思想,读和写不同的容器
同理:
HashSet不安全
线程安全的:
Set<String> list = new CopyOnWriteArraySet<String>();
Set<String> list = Collections.synchronizedSet(new HashSet<String>());
注意:
CopyOnWriteArraySet 底层就是CopyOnWriteArrayList
HashSet底层就是HashMap
HashSet的add方法底层为HasMap的put方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
ConcurrentHashMap 是线程安全的
锁
公平锁:是指多个线程按照申请锁的顺序来获取锁
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请锁的顺序比先申请锁的顺序先获取锁,在高并发情况下,有可能造成优先级反转或者饥饿现象。
非公平锁的优点在于吞吐量大比公平锁大,synchronized也是一种非公平锁。
ReentrantLock 默认为非公平锁
ReentrantLock lock = new ReentrantLock();
可重入锁(又名递归锁):线程可以进入任何一个它已经佣有的锁所同步着的代码块。ReentrantLock 和synchronized是典型的可重入锁,可重入锁的最大作用是避免死锁。
public synchronized void send1() {
System.out.println(Thread.currentThread().getName() + " send1");
send2();
}
public synchronized void send2() {
System.out.println(Thread.currentThread().getName() + " send2");
}
ReentrantLock lock = new ReentrantLock();
public void lock1() {
try
{
lock.lock();
System.out.println(Thread.currentThread().getName() + " lock1");
lock2();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public void lock2() {
try
{
lock.lock();
System.out.println(Thread.currentThread().getName() + " lock2");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程切换上下文的消耗,缺点是循环会消耗CPU
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public class Demo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
while (!atomicReference.compareAndSet(null, Thread.currentThread())) {
}
System.out.println(Thread.currentThread().getName() + " " + "获取锁");
}
public void unlock() {
atomicReference.compareAndSet(Thread.currentThread(), null);
System.out.println(Thread.currentThread().getName() + " " + "解锁了");
}
}
Demo demo = new Demo();
for (int i = 0; i < 4; i++)
{
new Thread(() ->
{
demo.lock();
try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}
demo.unlock();
}, "Thread" + String.valueOf(i)).start();
}
独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁。
共享锁:指该锁可被多个线程所持有。
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
public class MyCache {
public volatile Map<String, Object> map = new HashMap<>();
public ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put() {
try
{
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " 正在写入");
map.put(Thread.currentThread().getName(), "");
System.out.println(Thread.currentThread().getName() + " 写入完成");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.writeLock().unlock();
}
}
public void get() {
try
{
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " 正在读取");
map.get(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + " 读取完成");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.readLock().unlock();
}
}
}
CountDownLatch 倒计时,做减法
public class Main2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() ->
{
System.out.println(Thread.currentThread().getName());
latch.countDown();
try {
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}, "Thread" + String.valueOf(i)).start();
}
latch.await();
System.out.println("end");
}
}
CyclicBarrier 做加法
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("集齐龙珠");
});
for (int i = 0; i < 7; i++) {
new Thread(() ->
{
System.out.println(Thread.currentThread().getName());
try {
cyclicBarrier.await();
}
catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, "Thread" + String.valueOf(i)).start();
}
Semaphore 抢车位
public class Main2 {
public static void main(String[] args) throws Exception {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 7; i++) {
new Thread(() ->
{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位停1秒钟");
TimeUnit.SECONDS.sleep(1);
}
catch (Exception e) {
e.printStackTrace();
}
finally {
semaphore.release();
}
}, "Thread" + String.valueOf(i)).start();
}
}
}
阻塞队列
1.ArrayBlockingQueue: 由数组结构组成的有界阻塞队列
2.LinkedBlockingQueue 由链表结构组成的有界(Integer.MAX_VALUE)阻塞队列
3.PriorityBlockingQueue 支持优先级排序的无界阻塞队列
4.DelayQueue 使用优先级实现的延迟无界阻塞队列
5.SynchronousQueue 不存储元素的阻塞队列,也即单个元素的队列
6.LinkedTransferQueue 由链表结构组成的无界阻塞队列
7.LinkedBlockingDeque 由链表结构组成的双向阻塞队列
ArrayBlockingQueue (add 和 remove方法会抛出异常)
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));//true
System.out.println(blockingQueue.add("b"));//true
System.out.println(blockingQueue.add("c"));//true
System.out.println(blockingQueue.element()); //获取队列第一个元素
System.out.println(blockingQueue.remove()); //a
System.out.println(blockingQueue.remove()); //b
System.out.println(blockingQueue.remove()); //c
队列满再add():IllegalStateException: Queue full
队列空再remove(): java.util.NoSuchElementException
offer() peek() poll()
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a")); //true
System.out.println(blockingQueue.offer("b"));//true
System.out.println(blockingQueue.offer("c"));//true
System.out.println(blockingQueue.offer("d"));//false
System.out.println(blockingQueue.peek()); //探测第一个元素 a
System.out.println(blockingQueue.poll()); //a
System.out.println(blockingQueue.poll());//b
System.out.println(blockingQueue.poll());//c
System.out.println(blockingQueue.poll());//null
put(), take() 当队列已满,put()时,会阻塞线程,同样,当队列为空,take(),也会阻塞线程。
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("a");
blockingQueue.put("a");
blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
设置阻塞队列等待时间offer(), poll()
public class Main2 {
public static void main(String[] args) throws Exception {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a", 2L, TimeUnit.SECONDS);
blockingQueue.offer("a", 2L, TimeUnit.SECONDS);
blockingQueue.offer("a", 2L, TimeUnit.SECONDS);
blockingQueue.offer("a", 2L, TimeUnit.SECONDS); // 两秒后返回false
blockingQueue.poll(2L, TimeUnit.SECONDS);
blockingQueue.poll(2L, TimeUnit.SECONDS);
blockingQueue.poll(2L, TimeUnit.SECONDS);
blockingQueue.poll(2L, TimeUnit.SECONDS); //两秒后返回null
}
}
SynchronousQueue
public class Main2 {
public static void main(String[] args) throws Exception {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() ->
{
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + " 放入1" );
blockingQueue.put("1");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + " 放入2" );
blockingQueue.put("1");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + " 放入3" );
blockingQueue.put("1");
}
catch (Exception e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() ->
{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " 2秒后取出" );
blockingQueue.take();
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " 2秒后取出" );
blockingQueue.take();
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " 2秒后取出" );
blockingQueue.take();
}
catch (Exception e) {
e.printStackTrace();
}
}, "t2").start();
}
}
t1 放入1
t2 2秒后取出
t1 放入2
t2 2秒后取出
t1 放入3
t2 2秒后取出
Process finished with exit code 0
生产消费者传统版
public class Air {
private volatile int number = 0;
private Lock mylock = new ReentrantLock();
private Condition condition = mylock.newCondition();
public void increase() {
try {
mylock.lock();
while (number == 1) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "加1 " + number );
condition.signalAll();//唤醒其他线程
}
catch (Exception e) {
e.printStackTrace();
}
finally {
mylock.unlock();
}
}
public void decrease() {
try {
mylock.lock();
while (number == 0) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + " -1 " + number);
condition.signalAll();//唤醒其他线程
}
catch (Exception e) {
e.printStackTrace();
}
finally {
mylock.unlock();
}
}
}
Air air = new Air();
new Thread(() ->
{
try {
for (int i = 0; i < 5; i++) {
air.increase();
}
}
catch (Exception e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() ->
{
try {
for (int i = 0; i < 5; i++) {
air.decrease();
}
}
catch (Exception e) {
e.printStackTrace();
}
}, "t2").start();
new Thread(() ->
{
try {
for (int i = 0; i < 5; i++) {
air.increase();
}
}
catch (Exception e) {
e.printStackTrace();
}
}, "t3").start();
new Thread(() ->
{
try {
for (int i = 0; i < 5; i++) {
air.decrease();
}
}
catch (Exception e) {
e.printStackTrace();
}
}, "t4").start();
synchronized和lock之前的区别
1.synchronized是关键字,属于JVM层面,底层是通过monitor对象来完成,lock是具体的类,是api层面的锁。
2.使用方法,synchronized不需要用户去手动释放锁,ReentrantLock需要手动释放锁,否则可能导致死锁现象
3.synchronized不可中断,ReentrantLock可以中断1.设置超时方法tryLock(long timeout, TimeUnit unit),2.lockInterruptibly放代码块中,调用interrupt可中断
public class Main2 {
public static void main(String[] args) throws Exception {
ReentrantLock lock = new ReentrantLock();
new Thread(() ->
{
try {
lock.lockInterruptibly();
System.out.println("1");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
System.out.println("线程1获取锁");
try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}
Thread t2 = new Thread(() ->
{
try {
lock.lockInterruptibly();
System.out.println("2");
}
catch (InterruptedException e) {
System.out.println("中断线程2");
e.printStackTrace();
}
}, "t2");
t2.start();
System.out.println("线程2等待");
try{TimeUnit.SECONDS.sleep(5);}catch (InterruptedException e){e.printStackTrace();}
t2.interrupt();
}
}
4.加锁是否公平,synchronized是非公平锁,lock默认为非公平锁,可设置为公平锁
5.锁绑定多个条件Condition,synchronized没有,ReentrantLock 用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
public class CondtiionDemo {
private ReentrantLock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
private int num = 0;
public void p5() {
try
{
lock.lock();
while (num != 0) {
c1.await();
}
System.out.println(Thread.currentThread().getName() + ": 打印5次" );
num = 1;
c2.signal();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public void p10() {
try
{
lock.lock();
while (num != 1) {
c2.await();
}
System.out.println(Thread.currentThread().getName() + ": 打印10次" );
num = 2;
c3.signal();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public void p15() {
try
{
lock.lock();
while (num != 2) {
c3.await();
}
System.out.println(Thread.currentThread().getName() + ": 打印15次" );
num = 0;
c1.signal();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
}
CondtiionDemo demo = new CondtiionDemo();
for (int i = 0; i < 10; i++) {
new Thread(() ->
{
demo.p5();
}, "t1").start();
}
for (int i = 0; i < 10; i++) {
new Thread(() ->
{
demo.p10();
}, "t2").start();
}
for (int i = 0; i < 10; i++) {
new Thread(() ->
{
demo.p15();
}, "t3").start();
}
阻塞队列生产者,消费者模式
public class Prod {
private volatile boolean FLAG = true;
private BlockingQueue<String> blockingQueue = null;
private AtomicInteger atomicInteger = new AtomicInteger();
public Prod(BlockingQueue<String> blockingQueue) {
System.out.println(blockingQueue.getClass().getName());
this.blockingQueue = blockingQueue;
}
public void prod() throws Exception {
String res = null;
boolean offer = true;
while (FLAG) {
res = atomicInteger.incrementAndGet() + "";
offer = blockingQueue.offer(res, 2L, TimeUnit.SECONDS);
if (offer) {
System.out.println(Thread.currentThread().getName() + " 生产成功:" + res);
}
else {
System.out.println(Thread.currentThread().getName() + "生成失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "生产者停");
}
public void sonsumer() throws Exception {
String result = "";
while (FLAG) {
result = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (result == null || result.equalsIgnoreCase("")) {
FLAG = false;
System.out.println(Thread.currentThread().getName() + " 消费退出:");
return;
}
System.out.println(Thread.currentThread().getName() + " 消费成功:" + result);
System.out.println();
System.out.println();
}
}
public void stop() {
System.out.println("叫停");
FLAG = false;
}
}
public class Main2 {
public static void main(String[] args) throws Exception {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
Prod prod = new Prod(blockingQueue);
new Thread(() ->
{
try {
prod.prod();
}
catch (Exception e) {
}
}, "t1").start();
new Thread(() ->
{
try {
prod.sonsumer();
}
catch (Exception e) {
}
}, "t2").start();
try{TimeUnit.SECONDS.sleep(5);}catch (InterruptedException e){e.printStackTrace();}
prod.stop();
}
}
Callable接口
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1024;
}
}
public class Main2 {
public static void main(String[] args) throws Exception {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
Thread t = new Thread(futureTask, "a");
t.start();
while (!futureTask.isDone()) {
}
System.out.println(futureTask.get());
}
}
int i = Runtime.getRuntime().availableProcessors();//处理器的最大可用核数
System.out.println(i);
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
他的主要特点为:线程复用,控制最大并发数,管理线程。
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁的消耗。
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3.提高线程的可管理性。线程是稀缺资源,如果无限的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
newFixedThreadPool 固定线程数,执行长期的任务,性能好很多
public class Main2 {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}
}
newSingleThreadExecutor 单个线程数,一个任务一个任务执行的场景
newCachedThreadPool 不固定线程数,执行很多短期异步的小程序或者负载较轻的服务器。
ThreadPoolExecutor
public ThreadPoolExecutor(
int corePoolSize, //线程池中的常驻核心线程数
int maximumPoolSize, // 线程池能够容纳同时执行的最大线程数,此值必须大于等于1
long keepAliveTime, //多余的空闲线程的存活时间。当前线程数量超过corePoolSize时,当空闲时间 达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
TimeUnit unit, //keepAliveTime单位
BlockingQueue<Runnable> workQueue, //任务队列,被提交但尚未被执行的任务
ThreadFactory threadFactory, //表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认即可。
RejectedExecutionHandler handler//拒绝策略
//1.AbortPolicy(默认):直接抛出RejectedExecutionException阻止系统正常运行
//2.CallerRunsPolicy:调用者运行一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新的任务量。
//3.DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前队列加入队列中尝试再次提交当前任务
//4.DiscardPolicy:直接丢弃任务,不予任务处理也不抛出异常。如果允许丢弃任务,这是最好的方案。
)
自定义ThreadPoolExecutor
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(3);
ExecutorService executorService = new ThreadPoolExecutor(2,
3,
1,
TimeUnit.SECONDS,
workQueue,
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
合理配置线程池,你是如何考虑的:
1.CPU密集型:配置尽可能少的线程数量,一般公式:CPU核数+1个线程的线程池
2.I/O密集型:由于I/O密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。
1.CPU/(1-阻塞系数) 阻塞系数在0.8-0.9之间
比如:8/(1-0.9)=80个线程数
2.CPU核数*2
jps jstack的用法
死锁:
public class DeadLock {
public String locka = "a";
public String lockb = "b";
public void m1() {
synchronized (locka) {
System.out.println("获取锁a");
synchronized (lockb) {
System.out.println("尝试获取锁B");
}
}
}
public void m2() {
synchronized (lockb) {
System.out.println("获取锁b");
synchronized (locka) {
System.out.println("尝试获取锁a");
}
}
}
}
public class Main2 {
public static void main(String[] args) throws Exception {
DeadLock deadLock = new DeadLock();
new Thread(() ->
{
deadLock.m1();
}, "t1").start();
new Thread(() ->
{
deadLock.m2();
}, "t2").start();
}
}
jps:
F:\java\volatile>jps
263700 Launcher
270532 Main2
273792 Jps
4780
jstack 270532
===================================================
"t2":
at main.DeadLock.m2(DeadLock.java:24)
- waiting to lock <0x000000076b8a2120> (a java.lang.String)
- locked <0x000000076b8a2150> (a java.lang.String)
at main.Main2.lambda$main$1(Main2.java:26)
at main.Main2$$Lambda$2/693632176.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
"t1":
at main.DeadLock.m1(DeadLock.java:15)
- waiting to lock <0x000000076b8a2150> (a java.lang.String)
- locked <0x000000076b8a2120> (a java.lang.String)
at main.Main2.lambda$main$0(Main2.java:22)
at main.Main2$$Lambda$1/1642360923.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
什么是垃圾:简单的说就是内存中已经不再被使用到的空间就是垃圾。
可达性分析:如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
java中可以作为GC Roots的对象:
1.虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象
2.方法区中的类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI(Native方法)引用的对象
jps -l 命令
F:\java\volatile>jps -l
263700 org.jetbrains.jps.cmdline.Launcher
270532 main.Main2
318000 sun.tools.jps.Jps
4780
jinfo
F:\java\volatile>jinfo -flag PrintGCDetails 270532
-XX:-PrintGCDetails
开启:
-XX:+PrintGCDetails
设置元空间
-XX:MetaspaceSize=128m
设置新生代需要经历多少次GC晋升到老年代中的最大阈值
-XX:MaxTenuringThreshold=15
查询所有正在运行时的参数
jinfo -flags 327592
-Xms 等价于 -XX:IntialHeapSize
-Xmx 等价于 -XX:MaxHeapSize
-XX:+PrintFlagsInitial 查看初始默认值
-XX:+PrintFlagsFinal 查看修改后的值
冒号表示该值被人为修改
bool PrintFlagsFinal := true {product}
运行时修改jvm参数
java -XX:+PrintFlagsFinal -XX:MetaspaceSize=256m Main2
-XX:+PrintCommandLineFlags 查看程序使用的默认JVM参数, 偏重于看默认垃圾回收器
F:\java\volatile\target\classes\main>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266341248 -XX:MaxHeapSize=4261459968 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargeP
agesIndividualAllocation -XX:+UseParallelGC
初始最大堆内存大于物理内存的1/4,初始堆内存大小为1/64
long totalMemory = Runtime.getRuntime().totalMemory();
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("totalMemory(-xms)" + (totalMemory/(double)1024/1024) + "MB");
System.out.println("maxMemory(-xmx)" + (maxMemory/(double)1024/1024) + "MB");
totalMemory(-xms)245.5MB
maxMemory(-xmx)3614.5MB
-Xss,设置单个线程栈的大小,默认为512k - 1024k,等价于-XX:ThreadStackSize
-Xmn 设置年轻代大小
-XX:MetaspaceSize 设置元空间大小,元空间并不在虚拟机中,而是使用本地内存,因此,默认情况下, 元空间大小仅受本地内存限制。
-XX:+PrintGCDetails 查看GC详情
-XX:SurvivorRatio=8 表示 Eden:s0:s1=8:1:1(默认为8)
-XX:NewRatio=2 表示老年代:新生代为2:1(默认值为2)
GC回收时间过长会抛出OutOfmemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC都只回收了不到2%的极端情况下才会抛出。假如不抛出GC overhead limit exceeded 错误会发生什么情况呢,那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行。这样就行成恶性循环,CPU使用率一直是100%,而GC却没有任何成果。
-Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
public class Main2 {
public static void main(String[] args) throws Exception {
int i = 0;
List<String> s = new ArrayList<>();
while (true) {
s.add(String.valueOf(++i).intern());
}
}
}
Exception in thread "main" java.lang.OutOfMemoryError:
GC overhead limit exceeded
ByteBuffer.allocateDirect是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快,但如果不断分配本地内存,堆内存很少使用,那么JVM不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了。
public class Main2 {
public static void main(String[] args) throws Exception {
ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
}
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unable to create new thread准确的讲该native thread与对应的平台有关。
导致原因:1.你的应用创建了太多线程,一个应用进程创建太多线程,超过系统承载极限。2.你的服务器并不允许你的应用程序创建那么多线程,linux系统默认允许单个进程可以创建的线程数1024个,你的应用创建超过这个数量,就会报java.lang.OutOfMemoryError:unable to create new thread
解决办法:
1.想办法降低应用程序创建线程的数量,分析应用是否真的需要创建那么多线程,如果不是,改代码将线程数降到最低
2.对于有的应用,确实需要创建很多线程,远超过linux系统默认的1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制。
public class Main2 {
public static void main(String[] args) throws Exception {
for (int i = 0; ; i++)
{
new Thread(() ->
{
try{TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);}catch (InterruptedException e){e.printStackTrace();}
}, "Thread" + String.valueOf(i)).start();
}
}
}
Exception in thread "main" java.lang.OutOfMemoryError:
unable to create new native thread
ulimit -u 查看linux系统最大线程数
元空间存放了以下信息:
1.虚拟机加载的类信息
2.常量池
3.静态变量
4.即时编译后的代码
public class Main2 {
public static void main(String[] args) throws Exception {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Main2.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, args);
}
});
enhancer.create();
}
}
}
Exception in thread "main" java.lang.OutOfMemoryError:
Metaspace
垃圾回收的方式4种:serial(串行) parallel(并行) cms(并发) G1
serial:它为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。
parallel(并行):多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景
cms(并发)收集器:用户线程和垃圾线程同时执行(不一定并行,可能交替执行),不需要停顿用户线程。互联网公司多用它,适用对响应时间有要求的场景。
G1垃圾回收器,G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收。
年轻代使用收集器:Serial Copying, Parallel Scavenge, ParNew
老年代使用收集器:Serial MSC(Serial Old), Parallel Compacting(Parallel Old), CMS
年轻和老年代都使用的收集器:G1
-XX:+UseSerialGC
[DefNew: 6995K->1285K(78656K), 0.0024254 secs]
[Tenured: 0K->1284K(174784K), 0.0030842 secs]
-XX: +UseParNewGC(新生代换成并行收集器,老年代还是串行收集器)
[ParNew: 6995K->1314K(78656K), 0.0016239 secs]
[Tenured: 0K->1284K(174784K), 0.0031340 secs]
-XX:ParallelGCThreads 限制线程数量,默认开启和CPU数目相同的线程数。
-XX:+UseParallelGC(java8默认使用的垃圾收集器, 又叫㖔吐量优先收集器。 串行收集器在新生代和老年代的并行化)
[PSYoungGen: 496K->0K(2560K)]
[ParOldGen: 843K->1180K(5632K)]
它重点关注的是:
可控制的吞吐量(运行用户代码时间 /运行用户代码时间 +垃圾收集时间),高吞吐量意味着高效利用CPU时间,它多用于在后台运算而不需要太多交互的任务
它的自适应调节略:虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:MaxGCPauseMillis)或最大的吞吐量。
-XX:+UseParallelOldGC 与 -XX:+UseParallelGC可互相激活。
CMS收集器(Concurrent Mark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器。适合应用在互联网站或者B/S系统的服务器,这类应用尤其重视服务器的响应速度,希望系统停顿时间 短。
CMS非常适合堆内存大,CPU核数多的服务器端应用。
并发收集低停顿,并发指的是与用户线程一起执行。
开启参数: -XX:+UseConcMarkSweepGC 开启该参数后会自动将-XX:+UseParNewGC打开。
开启该参数后,使用(ParNew(Yong区使用) +CMS(old区使用)+ Serial Old 的收集组合), Serial Old 作为CMS出错的后备收集器。
4个步骤:
1.初始标记
2.并发标记(和用户线程一起)
3.重新标记
4.并发清除(和用户线程一起)
优点:并发收集低停顿
缺点:并发执行,对cpu资源压力大,采用的标记清除算法会导致大量碎片。
GC (CMS Initial Mark) 初始标记
[CMS-concurrent-mark-start]并发标记
[GC (CMS Final Remark) [YG occupancy: 833 K (1152 K)][Rescan (parallel) , 0.0003926 secs]重新标记
[CMS-concurrent-sweep: 0.000/0.000 secs]并发清除