volatile是java虚拟机提供的轻量级的同步机制。三大特性:(代码示例在下一节)
1、并不真实存在,是规则、规范,定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
2、三大特性:
3、JMM关于同步的规定:
4、线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后在将变量写回主内存
5、可见性:
public class P03 {
volatile int num;//加volatile程序才能正常停止
// int num;
void add1(){
num++;
}
public static void main(String[] args) {
P03 p = new P03();
//开启一个线程,改变num的数值
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.add1();
System.out.println("add1完成");
}).start();
//按理,当上面的线程执行完之后,应该立即结束,但未加volatile修饰变量时一直没结束
while (p.num == 0){
}
}
}
6、原子性:
public class P05 {
volatile int num;
void add1(){
num++;
}
public static void main(String[] args) {
P05 p = new P05();
//10个线程,每个加1000
for (int i = 0; i < 10; i++) {
new Thread(() ->{
for (int j = 0; j < 1000; j++) {
p.add1();
}
}).start();
}
// 2个线程时,是指:main线程、gc线程
while (Thread.activeCount() > 2){
Thread.yield();
}
// 理论上时10 * 1000,但测试结果小于该值; 说明volatile不保证原子性
// 想要结果是10000,使用synchronized、lock或者AtomicInteger
System.out.println(p.num);
}
}
7、指令重排
方式:
说明:
例如:

语句4永远不会是第一句,因为要考虑数据依赖性


总结:
工作内存与主内存同步延迟现象导致的可见性问题,可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见.
对于指令重排导致的可见性问题和有序性问题,可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。
单例模式:
public class P11 {
private static P11 instance;
private P11() {
System.out.println("构造方法...");
}
// DCL, double check lock,双端检验机制
public static P11 getInstance() {
if (instance != null) {
synchronized (P11.class){
if (instance != null) {
instance = new P11();
}
}
}
return instance;
}
}
Compare And Swap,比较并交换。
Unsafe:
缺点:
循环时间长,开销很大。如果CAS失败,会一直进行尝试;如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
只能保证一个共享变量的原子操作。如果是多个只能加锁了
ABA问题。当前线程获取到的是A,执行修改获取到的也是A,以为这个A没变化;实际有可能A被别的线程修改成B,但在当前线程准备修改之前又修改回A了;这样当前线程以为没有修改,产生ABA问题
自定义类型原子类:
public class P17 {
public static void main(String[] args) {
// 自定义两个日期:
LocalDate ld1 = LocalDate.now();
LocalDate ld2 = LocalDate.now();
// 日期原子类
AtomicReference<LocalDate> localDateAtomicReference = new AtomicReference<>(ld1);
//第一次修改:
boolean cas1 = localDateAtomicReference.compareAndSet(ld1, ld2);
System.out.println("cas1 = " + cas1);
System.out.println("localDateAtomicReference.get() = " + localDateAtomicReference.get());
// 第二次修改:
boolean cas2 = localDateAtomicReference.compareAndSet(ld1, ld2);
System.out.println("cas2 = " + cas2);
System.out.println("localDateAtomicReference.get() = " + localDateAtomicReference.get());
}
}


看一眼getAndIncrement方法:

更底层的:
例如
代码:(ABA的产生)
AtomicReference<String> stringAtomicReference = new AtomicReference<>("A");
//t1 改为B再改回A
new Thread(() -> {
stringAtomicReference.compareAndSet("A","B");
System.out.println("v1 = " + stringAtomicReference.get());
stringAtomicReference.compareAndSet("B","A");
System.out.println("v2 = " + stringAtomicReference.get());
},"t1").start();
//t2 将A改为B
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringAtomicReference.compareAndSet("A","B");
System.out.println("v3 = " + stringAtomicReference.get());
},"t2").start();
代码:(ABA的解决,使用包含时间戳的原子类:AtomicStampedReference)
AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("A", 1);
//t3 改为B再改回A
new Thread(() -> {
//保证t4先获取到版本号
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean compareAndSet1 = stampedReference.compareAndSet("A", "B", stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println("compareAndSet1 = " + compareAndSet1);
boolean compareAndSet2 = stampedReference.compareAndSet("B", "A", stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println("compareAndSet2 = " + compareAndSet2);
}, "t3").start();
//t4 将A改为B
new Thread(() -> {
int stamp = stampedReference.getStamp();
//保证t3执行一次ABA操作了
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean compareAndSet3 = stampedReference.compareAndSet("A", "B", stamp, stamp + 1);
System.out.println("compareAndSet3 = " + compareAndSet3);
}, "t4").start();
产生:
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
for (int j = 0; j < 100; j++) {
list.add(finalI * j);
}
System.out.println(list);
}).start();
}

原因:
解决方法:
使用vector,底层给方法加了synchronized,效率低

使用Collections工具类,将不安全的集合包装成安全的集合类,

实际是在方法体内加synchronized:

使用juc中的类


写时复制
不安全的类及其对应的juc类:
题目:

结果:

主要看有线程没有排队
公平锁/非公平锁
关于两者区别:
ReentrantLock默认采用的是非公平锁,可以增大吞吐量;synchronized也是一种非公平锁.

A调用B方法,A获得了锁,则B不用再获取锁
代码:
@Slf4j
public class P27 {
public static void main(String[] args) {
P27 p = new P27();
p.sendSms();
}
synchronized void sendSms() {
log.info("发短信...");
sendEmail();
}
synchronized void sendEmail() {
log.info("发邮箱...");
}
}

解释:
- synchronized修饰普通方法,锁的是对象,即锁是调用的对象
- sendSms获得了锁,内部调用sendEmail又要获取锁,按理已经被获取了,是获取不到的
- 但是,由于是可重入锁,sendEmail在sendSms内部,sendSms已经获取到锁了,内部方法不用再去获取锁了,已经自动获取了
@Slf4j
public class P27 {
Lock lock = new ReentrantLock();
public static void main(String[] args) {
P27 p = new P27();
p.sendSms();
}
void sendSms() {
lock.lock();
try {
log.info("发短信...");
sendEmail();
} finally {
lock.unlock();
}
}
void sendEmail() {
lock.lock();
try {
log.info("发邮箱...");
} finally {
lock.unlock();
}
}
}

手写一个自旋锁:
@Slf4j
public class P29 {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
void myLock() {
log.info("准备获取锁...");
while (!atomicReference.compareAndSet(null, Thread.currentThread())) {
}
log.info("获取到锁");
}
//解锁
void myUnlock() {
log.info("准备释放锁...");
atomicReference.compareAndSet(Thread.currentThread(), null);
log.info("释放锁成功");
}
public static void main(String[] args) {
P29 p = new P29();
//线程1先获取到锁
new Thread(() -> {
p.myLock();
try {
log.info("do something");
//保证线程2准备获取锁,但又没获取到
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
p.myUnlock();
}
}).start();
//保证线程1获取到锁
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程1释放锁之后,线程2才能获取到锁
new Thread(() -> {
p.myLock();
try {
log.info("do something");
} finally {
p.myUnlock();
}
}).start();
}
}

代码:
@Slf4j
public class P31 {
volatile Map<Integer, Integer> cache = new HashMap<>();
ReadWriteLock rwLock = new ReentrantReadWriteLock();
/**
* 放入数据
*/
void put(int key, int val) {
Lock writeLock = rwLock.writeLock();
writeLock.lock();
try {
log.info("准备写入:{}", key);
cache.put(key, val);
log.info("{}写入完成!", key);
} finally {
writeLock.unlock();
}
}
/**
* 读取数据
*/
void get(int key) {
Lock readLock = rwLock.readLock();
readLock.lock();
try {
log.info("准备读取:{}", key);
Integer val = cache.get(key);
log.info("读取到的结果:{}", val);
} finally {
readLock.unlock();
}
}
public static void main(String[] args) {
P31 p = new P31();
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
p.put(finalI, finalI);
}, String.valueOf(i)).start();
}
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
p.get(finalI);
}, String.valueOf(3 + i)).start();
}
}
}

例如,一年12个月,12个月都过了,一年才算过完了;
@Slf4j
public class P32 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(12);
for (int i = 1; i <= 12; i++) {
int finalI= i;
new Thread(() -> {
log.info("{}月过了",finalI);
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("一年过完了");
}
}

被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。例如,模拟拼团:
@Slf4j
public class P33 {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("---拼团成功!!!---");
});
for (int i = 0; i < 7; i++) {
final int finalI = i;
new Thread(() -> {
log.info("第{}个人参加拼团", finalI);
try {
cyclicBarrier.await();
log.info("已经够7个人,第{}个人已付款,拼团成功", finalI);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}

例如,饭店来了6个人,却只有三个餐桌可供就餐的例子:
@Slf4j
public class P34 {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
Random random = new Random();
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(() -> {
try {
log.info("第{}个人准备就餐", finalI);
semaphore.acquire();
log.info("第{}个人成功就餐...", finalI);
int time = random.nextInt(3);
TimeUnit.SECONDS.sleep(time);
log.info("第{}个人就餐结束,用时{}秒!", finalI, time);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}

常用方法:
例如,有一张超级用户的表,有id、名称、密码等信息,便可以使用枚举来实现:
import lombok.Getter;
public enum AdminEnum {
SUPER_ADMIN(1L, "超级管理员", "1234"),
COMMON_ADMIN(2L, "普通管理员", "5678");
@Getter
private Long id;
@Getter
private String username;
@Getter
private String password;
AdminEnum(Long id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
//自定义方法,通过id来找用户
public static AdminEnum getById(Long id){
for (AdminEnum value : values()) {
if (id.equals(value.id)){
return value;
}
}
return null;
}
public static void main(String[] args) {
//获取所有枚举元素
AdminEnum[] values = AdminEnum.values();
System.out.println("====================");
for (AdminEnum value : values) {
//ordinal -- 枚举的索引
System.out.println("ordinal = " + value.ordinal() + ",value = " + value);
}
//输出每一个枚举元素的信息
System.out.println("====================");
AdminEnum superAdmin = AdminEnum.SUPER_ADMIN;
System.out.println("superAdmin = " + superAdmin);
System.out.println("superAdmin.id = " + superAdmin.id);
System.out.println("superAdmin.username = " + superAdmin.username);
System.out.println("superAdmin.password = " + superAdmin.password);
//获取指定字符串值的枚举常量
System.out.println("====================");
AdminEnum valueOf = AdminEnum.valueOf("SUPER_ADMIN");
System.out.println("valueOf super_admin = " + valueOf);
//通过id获取用户的方法:
System.out.println("====================");
AdminEnum byId = AdminEnum.getById(1L);
System.out.println("byId = " + byId);
}
}

空时,从队列中获取元素的操作将会被阻塞。满时,往队列里添加元素的操作将会被阻塞。阻塞队列:(常用已标红)
ArrayBlockingQueue :由数组结构组成的有界阻塞队列。LinkedBlockingQueue:由链衣结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列常用方法:

public class P37 {
public static void main(String[] args) {
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(2);
System.out.println("add 1:" + blockingQueue.add(1));
System.out.println("add 2:" + blockingQueue.add(2));
try {
System.out.println("add 3:" + blockingQueue.add(3));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("element:" + blockingQueue.element());
System.out.println("remove 1:" + blockingQueue.remove());
System.out.println("remove 2:" + blockingQueue.remove());
try {
System.out.println("element:" + blockingQueue.element());
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println("remove 3:" + blockingQueue.remove());
} catch (Exception e) {
e.printStackTrace();
}
}
}

public class P38 {
public static void main(String[] args) {
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(2);
System.out.println("offer 1:" + blockingQueue.offer(1));
System.out.println("offer 2:" + blockingQueue.offer(2));
try {
System.out.println("offer 3:" + blockingQueue.offer(3));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("peek:" + blockingQueue.peek());
System.out.println("poll 1:" + blockingQueue.poll());
System.out.println("poll 2:" + blockingQueue.poll());
try {
System.out.println("peek:" + blockingQueue.peek());
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println("poll 3:" + blockingQueue.poll());
} catch (Exception e) {
e.printStackTrace();
}
}
}

@Slf4j
public class P39 {
public static void main(String[] args) {
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(1);
new Thread(() -> {
try {
blockingQueue.put(1);
log.info("放入1成功");
blockingQueue.put(2);
log.info("放入2成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
blockingQueue.take();
log.info("取出1成功");
blockingQueue.take();
log.info("取出2成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}

BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(1);
System.out.println("blockingQueue.offer(1) = " + blockingQueue.offer(1));
try {
System.out.println("blockingQueue.offer(2,1,TimeUnit.SECONDS) = " + blockingQueue.offer(2,1,TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}

@Slf4j
public class P40 {
public static void main(String[] args) {
BlockingQueue<Integer> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
blockingQueue.put(1);
log.info("放入1成功");
blockingQueue.put(2);
log.info("放入2成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
blockingQueue.take();
log.info("取出1成功");
TimeUnit.SECONDS.sleep(2);
blockingQueue.take();
log.info("取出2成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}

步骤
资源类,类似于一个商品(但储量只能为1)操作即方法,假设有两个方法:生产、消费;判断:判断该不该自己干活?如果不该,则等待(类似于已经有商品了,生产者等待)干活,则干活(类似于,已经有商品了,消费者该干活了)通知其他线程干活(类似于生产一个商品,就要通知消费者消费;消费一个产品就要通知生产者生产)synchronized版和lock版:

public class P41 {
public static void main(String[] args) throws InterruptedException {
//=============使用synchronized实现=========
ResourceSync resourceSync = new ResourceSync();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
resourceSync.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
resourceSync.del();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
TimeUnit.SECONDS.sleep(1);
System.out.println("=====================");
//===============使用lock实现===================
ResourceLock resourceLock = new ResourceLock();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
resourceLock.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
resourceLock.del();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
// 使用synchronized实现
@Slf4j
class ResourceSync {
int count;
synchronized void add() throws InterruptedException {
while (count != 0) {
this.wait();
}
log.info("add 1");
count++;
this.notifyAll();
}
synchronized void del() throws InterruptedException {
while (count == 0) {
this.wait();
}
log.info("del 1");
count--;
this.notifyAll();
}
}
// 使用lock实现
@Slf4j
class ResourceLock {
int count;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
void add() throws InterruptedException {
lock.lock();
try {
while (count != 0) {
condition.await();
}
log.info("add 1");
count++;
condition.signalAll();
} finally {
lock.unlock();
}
}
void del() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
condition.await();
}
log.info("del 1");
count--;
condition.signalAll();
} finally {
lock.unlock();
}
}
}

生产者消费者 – 阻塞队列
public class P44 {
public static void main(String[] args) throws InterruptedException {
Resource44 resource = new Resource44(new ArrayBlockingQueue<>(3));
//生产线程启动
new Thread(() -> {
try {
resource.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//消费线程启动
new Thread(() -> {
try {
resource.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//五秒后强制叫停:
TimeUnit.SECONDS.sleep(5);
System.out.println("main方法叫停生产消费!!!");
resource.stop();
}
}
@Slf4j
class Resource44 {
private volatile boolean flag = true;
private BlockingQueue<Integer> blockingQueue;
private AtomicInteger atomicInteger = new AtomicInteger();
public Resource44(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
log.info("BlockingQueue:{}", blockingQueue.getClass().getName());
}
/**
* 生产者生产
*
* @throws InterruptedException
*/
public void product() throws InterruptedException {
while (flag) {
int val = atomicInteger.incrementAndGet();
boolean offer = blockingQueue.offer(val, 2, TimeUnit.SECONDS);
if (offer) {
log.info("生产 资源-{} 成功", val);
} else {
log.info("生产 资源-{} 失败", val);
}
TimeUnit.SECONDS.sleep(1);
}
log.info("生产者停止生产!!!");
}
/**
* 消费者消费
*
* @throws InterruptedException
*/
public void consumer() throws InterruptedException {
while (flag) {
Integer poll = blockingQueue.poll(1, TimeUnit.SECONDS);
if (StringUtils.isEmpty(poll)) {
log.info("消费者1秒钟未收到资源,停止消费!!!");
} else {
log.info("消费 资源-{}", poll);
}
TimeUnit.SECONDS.sleep(1);
}
log.info("消费者停止消费!!!");
}
/**
* 强制停止生产消费
*/
public void stop() {
this.flag = false;
}
}
1、原始构成
2、使用方法
3、等待是否可中断
4、加锁是否公平
5、锁绑定多个条件condition
即:
要求:
实现:(主要就是lock的精准唤醒)
public class P43 {
public static void main(String[] args) {
Print print = new Print();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
print.printA();
}).start();
new Thread(() -> {
print.printB();
}).start();
new Thread(() -> {
print.printC();
}).start();
}
}
}
class Print {
Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
int note;
void printA() {
lock.lock();
try {
while (note != 0) {
c1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println("AA");
}
note = 1;
c2.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void printB() {
lock.lock();
try {
while (note != 1) {
c2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println("BB");
}
note = 2;
c3.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void printC() {
lock.lock();
try {
while (note != 2) {
c3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println("CC");
}
note = 0;
c1.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class P45 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new CallableImpl());
new Thread(futureTask).start();
new Thread(futureTask).start();
String res = futureTask.get();
System.out.println(res);
}
}
class CallableImpl implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("CallableImpl.call...");
return "ok";
}
}

FutureTash实现了Runnable:又可以传Callable



使用new Thread开启线程,多次传同一个同一个FutureTask对象,只会计算一次,但是传的是不同FutureTask对象,则会计算多次。例如上例中明明开启两个线程,但只计算了一次:


总结在另一篇文章:https://blog.csdn.net/m0_55155505/article/details/125191350
代码
public class P55 {
public static void main(String[] args) {
Resource55 resource = new Resource55();
new Thread(() -> {
resource.toHigh();
}).start();
new Thread(() -> {
resource.toThin();
}).start();
}
}
@Slf4j
class Resource55{
Lock l1 = new ReentrantLock();
Lock l2 = new ReentrantLock();
void toHigh(){
l1.lock();
try{
log.info("变高了...");
TimeUnit.SECONDS.sleep(1);
//变高的人想变瘦
toThin();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l1.unlock();
}
}
void toThin(){
l2.lock();
try{
log.info("变瘦了...");
TimeUnit.SECONDS.sleep(1);
//变瘦的人想变高
toHigh();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l2.unlock();
}
}
}
如何知道这是死锁,而不是死循环等错误?
- 命令行输入
jps -l查看对应的pid
- 输入
jstack 对应pid
- 最后面写着:Found 1 deadlock
@Slf4j
class RunnableImpl55 implements Runnable{
private String lockA;
private String lockB;
public RunnableImpl55(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
private void lockA2lockB() throws InterruptedException {
synchronized (lockA){
log.info("已经持有锁:{},想要获取锁:{}",lockA,lockB);
TimeUnit.SECONDS.sleep(1);
lockB2lockA();
}
}
private void lockB2lockA() throws InterruptedException {
synchronized (lockB){
log.info("已经持有锁:{},想要获取锁:{}",lockB,lockA);
TimeUnit.SECONDS.sleep(1);
lockA2lockB();
}
}
@Override
public void run() {
try {
lockA2lockB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new RunnableImpl55(lockA,lockB)).start();
new Thread(new RunnableImpl55(lockB,lockA)).start();
}
}

另一篇文章:https://blog.csdn.net/m0_55155505/article/details/125976760
类加载器分类(按照java虚拟机规范)
虚拟机自带的加载器:
1、引导类加载器:Bootstrap ClassLoader
//通过Launcher.getBootstrapClassPath().getURLs()获取:
file:/C:/Java/jdk1.8.0_271/jre/lib/resources.jar
file:/C:/Java/jdk1.8.0_271/jre/lib/rt.jar
file:/C:/Java/jdk1.8.0_271/jre/lib/sunrsasign.jar
file:/C:/Java/jdk1.8.0_271/jre/lib/jsse.jar
file:/C:/Java/jdk1.8.0_271/jre/lib/jce.jar
file:/C:/Java/jdk1.8.0_271/jre/lib/charsets.jar
file:/C:/Java/jdk1.8.0_271/jre/lib/jfr.jar
file:/C:/Java/jdk1.8.0_271/jre/classes
2、扩展类加载器
//通过System.getProperty("java.ext.dirs")获取:
C:\Java\jdk1.8.0_271\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
3、应用程序类加载器(系统类加载器,appClassLoader)
java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类的时候才会将该类的class文件加载到内存生成class对象,而且加载某个类的class文件时,java虚拟机采用的时双亲委派模式,即把请求交由父类处理,他是一种任务委派模式
原理:(向上委托到最顶层之后,依次尝试加载:引导类加载器 -> 扩展类加载器 -> 系统类加载器)
例子:
- 自定义一个java.lang.String类
- 尝试创建对象
- 发现用不是自定义的String类(未输出"自定义String…")
优点:
原文更详细:https://blog.csdn.net/qq_30336433/article/details/83268945
是什么?
严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。核心组件:
虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。
类装载器采用的机制是双亲委派模式.
- 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
- 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
包括:安全提供者、消息摘要、数字签名、加密、鉴别
如图:自定义java.lang.String类,自其中写main方法尝试运行

运行结果:

说明使用的不是我们自定义的String
结论:
1、自定义String类,在加载的时候使用引导类加载器进行加载
↓
2、引导类加载器在加载的时候会加载jdk自带的文件中的String类
↓
3、自带jdk中的String没有main方法,导致报错
这样可以保证对java核心源代码的保护,这就是沙箱安全机制
什么是垃圾:内存中不在使用的空间
如何判断:
引用计数法(了解)

枚举根节点做可达性分析(根搜索路径)
比如:各个线程被调用的方法中使用到的参数、局部变量等。
比如:Java类的引用类型静态变量
比如:字符串常量池(stringTable)里的引用、static final修饰的常量
基本数据类型对应的class对象,一些常驻的异常对象(如:NullPointerException、outofMemoryError),系统类加载器。
如何清除:清除阶段算法
分为以下三种参数:
标配参数

X参数(了解)
XX参数(包括Xms、Xmx,类似于起了别名)

XX参数:
Boolean类型,-XX:+(或-)参数,例如:-XX:+PrintGCDetails
查看是否开启:

添加运行参数:

再次查看:

KV设置值类型:-XX:属性key=属性value,例如-XX:MetaspaceSize=11111111
查看:

设置:

再查看:(和设置的不一致应该是动态调整)

jinfo

注意:
-Xms :初始堆空间内存(默认为物理内存的1/64)-Xmx:最大堆空间内存(默认为物理内存的1/4)
查看初始默认参数:
java -XX:+PrintFlagsInitial初始化参数

java -XX:+PrintFlagsFinal用于查看修改后的参数( = 表示默认, := 表示修改过,修改后的值;但jdk17的话,没有使用=和:=,而是在最后面加了一个参数(default、command line)用来表示是默认值还是修改了的值)


java -XX:+PrintCommandLineFlags打印命令行参数

使用代码查看:
public class P65 {
public static void main(String[] args) {
long totalMemory = Runtime.getRuntime().totalMemory();
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("totalMemory = " + totalMemory / 1024 / 1024 + " MB");
System.out.println("64 * totalMemory = " + 64 * totalMemory / 1024 / 1024 + " MB");
System.out.println("maxMemory = " + maxMemory / 1024 / 1024 + " MB");
System.out.println("4 * maxMemory = " + 4 * maxMemory / 1024 / 1024 + " MB");
}
}


你配过哪些参数?
-XX:+PrintFlagsInitial :查看所有的参数的默认初始值(示例在本节最后面)
-XX:+PrintFlagsFinal:查看所有的参数的最终值(可能会存在修改,不再是初始值;=表示初始值,:=表示修改过,修改后的值)
-Xms :初始堆空间内存(默认为物理内存的1/64),等价于-XX:InitialHeapSize
-Xmx:最大堆空间内存(默认为物理内存的1/4),等价于-XX:MaxHeapSize
-Xss:设置单个线程栈的大小,一般为512k~1024k;等价于-XX:ThreadStackSize
-Xmn:设置新生代的大小。(初始值及最大值)
-XX:MetaspaceSize:设置元空间大小
-XX:NewRatio:配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio:设置新生代中Eden和so/s1空间的比例
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
-XX:+PrintGCDetails:输出详细的Gc处理日志
打印gc简要信息:1、-XX:+PrintGC 2、-verbose:gc
-XX:HandlePromotionFailure:是否设置空间分配担保
关于GC参数可参考:PrintGCDetails 输出参数及解释

详细的,可参考:https://blog.csdn.net/m0_55155505/article/details/125976760#_1355
强引用:默认就是强引用,宁愿OOM也不回收
public class P72 {
public static void main(String[] args) {
Object obj = new Object();
try {
toOOM();
} catch (OutOfMemoryError e) {
e.printStackTrace();
System.out.println(obj);
}
}
public static void toOOM() throws OutOfMemoryError {
// 设置参数 -Xms10m -Xmx10m
byte[] bytes = new byte[10 * 1024 * 1024];
}
}

软引用:内存够不回收,内存不足会被回收
public class P73 {
public static void main(String[] args) {
Object obj = new Object();
SoftReference softReference = new SoftReference(obj);
obj = null;
System.gc();
System.out.println("第一次gc后:");
System.out.println("obj = " + obj);
System.out.println("softReference.get() = " + softReference.get());
try {
toOOM();
} catch (OutOfMemoryError e) {
e.printStackTrace();
System.out.println("OOM之后:");
System.out.println("obj = " + obj);
System.out.println("softReference.get() = " + softReference.get());
}
}
public static void toOOM() throws OutOfMemoryError {
// -Xms10m -Xmx10m
byte[] bytes = new byte[10 * 1024 * 1024];
}
}

弱引用:只要发生gc,就会被回收
public class P74 {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
WeakReference weakReference = new WeakReference(obj);
obj = null;
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println("obj = " + obj);
System.out.println("weakReference.get() = " + weakReference.get());
}
}

public class P78 {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
ReferenceQueue referenceQueue = new ReferenceQueue();
WeakReference weakReference = new WeakReference(obj, referenceQueue);
System.out.println("=====gc前:=====");
System.out.println("obj = " + obj);
System.out.println("weakReference.get() = " + weakReference.get());
System.out.println("referenceQueue.poll() = " + referenceQueue.poll());
obj = null;
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println("=====gc后:=====");
System.out.println("obj = " + obj);
System.out.println("weakReference.get() = " + weakReference.get());
System.out.println("referenceQueue.poll() = " + referenceQueue.poll());
}
}

虚引用:

public class P79 {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
ReferenceQueue referenceQueue = new ReferenceQueue();
PhantomReference phantomReference = new PhantomReference(obj, referenceQueue);
System.out.println("=====gc前:=====");
System.out.println("obj = " + obj);
System.out.println("phantomReference.get() = " + phantomReference.get());
System.out.println("referenceQueue.poll() = " + referenceQueue.poll());
obj = null;
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println("=====gc后:=====");
System.out.println("obj = " + obj);
System.out.println("phantomReference.get() = " + phantomReference.get());
System.out.println("referenceQueue.poll() = " + referenceQueue.poll());
}
}

软引用、弱引用的应用:

WeakHashMap:

public class P76 {
public static void main(String[] args) throws InterruptedException {
WeakHashMap<String, Object> weakHashMap = new WeakHashMap<>();
String key = new String("a");
Object val = new Object();
weakHashMap.put(key, val);
key = null;
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println(weakHashMap); //{}
}
}
java.lang.StackOverflowError
public class P81 {
public static void main(String[] args) {
main(args);
}
}

对象创建的堆装不下了!!!
public class P82 {
public static void main(String[] args) {
// -Xms10m -Xmx10m
byte[] bytes = new byte[10 * 1024 * 1024];
}
}


回收时间过长时会抛出OutOfMemroyError。public class P83 {
public static void main(String[] args) {
// -Xms10m -Xmx10m -XX:+PrintGCDetails
List<String> list = new ArrayList<>();
int i = 0;
try {
while (true) {
list.add(String.valueOf(i++).intern());
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}

看垃圾回收情况:基本没会受到垃圾!!

导致原因:
写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,
它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在ava堆和 Native堆中来回复制数据。
ByteBuffer.allocate(capability)第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢
ByteBuffer.allocteDirect(capability)第二种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。
但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC, DirectByteBuffer对象们就不会被回收.
这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了。
例子:
public class P84 {
public static void main(String[] args) {
// -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
// 设置本地内存5M,但申请6M的空间
long maxDirectMemory = VM.maxDirectMemory();
System.out.println("maxDirectMemory = " + maxDirectMemory / (double) 1024 / 1024 + " MB");
ByteBuffer allocate = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
}

线程创建的太多了



Linux如何调最大线程数?
查看:ulimit -u

修改:vim /etc/security/limits.d/20-nproc.conf;*表示除了root之外的用户,这里可以看到,root用户没有限制,其他用户是4096(不同的服务器配置不同,也可能是1024)

public class P87 {
public static void main(String[] args) {
// -XX:MaxMetaspaceSize=10m
int i = 0;
try {
for (; ; i++) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(P87.class);
// 默认是true,表示是同一个class;设为false,每次在方法区产生新的class
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(o, objects);
}
});
enhancer.create();
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println("次数:" + i);
}
}
}



详细内容可前往:https://blog.csdn.net/m0_55155505/article/details/125976760#_1511
java -XX:+PrintCommandLineFlags -version

jdk 8 情况下,查看各个垃圾收集器的使用情况:
C:\Users\AikeTech>jps
7092 P91
8648
6828 Launcher
8380 Jps
C:\Users\AikeTech>jinfo -flag UseSerialGC 7092
-XX:-UseSerialGC
C:\Users\AikeTech>jinfo -flag UseConcMarkSweepGC 7092
-XX:-UseConcMarkSweepGC
C:\Users\AikeTech>jinfo -flag UseParNewGC 7092
-XX:-UseParNewGC
C:\Users\AikeTech>jinfo -flag UseParallelGC 7092
-XX:+UseParallelGC
C:\Users\AikeTech>jinfo -flag UseParallelOldGC 7092
-XX:+UseParallelOldGC
C:\Users\AikeTech>jinfo -flag UseG1GC 7092
-XX:-UseG1GC





XX:+UseSerialGC,默认和Serial Old配合使用public class P94 {
public static void main(String[] args) {
// -Xms10m -Xmx10m -XX:+UseSerialGC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
byte[] bytes = new byte[10 * 1024 * 1024];
}
}

-XX:+UseParNewGC,默认和Serial Old搭配,但Serial Old将被废弃public class P95 {
public static void main(String[] args) {
// -Xms10m -Xmx10m -XX:+UseParNewGC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
byte[] bytes = new byte[10 * 1024 * 1024];
}
}

-XX:+UseParallelGC,老年代默认ParallelOldGC,且可以互相激活public class P96 {
public static void main(String[] args) {
// -Xms10m -Xmx10m -XX:+UseParallelGC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
byte[] bytes = new byte[10 * 1024 * 1024];
}
}

-XX:+UseConcMarkSweepGC,年轻代ParNew,还会有Serial Old进行兜底
过程:
优点:比较耗时的标记和清理和用户线程一起执行,总体停顿时间少。
缺点:


代码:(效果不是很好,本节一开始那有张不错的图)
public class P98 {
public static void main(String[] args) {
// -Xms10m -Xmx10m -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
byte[] bytes = new byte[10 * 1024 * 1024];
}
}

-XX:+UseSerialOldGC: 

-XX:+UseG1GCpublic class P101 {
public static void main(String[] args) {
// -Xms10m -Xmx10m -XX:+UseG1GC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
byte[] bytes = new byte[10 * 1024 * 1024];
}
}


特点:
区域化内存划片Region
- 这些Region的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。
- 这些Region的一部分包含老年代,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了。
- 在G1中,还有一种特殊的区域,叫Humongous(巨大的)区域
如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区工它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么Gi会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
回收步骤:小区域收集 + 形成连续的内存块
参数配置:可以配一下最大堆内存、最大停顿时间


和CMS相比:



top命令:

可以查看内存(MEM)、cpu等占用情况
load average后面有三个值,表示系统1分钟、5分钟、15分钟的负载值;三个值的平均值如果大于0.6,说明系统负载重。图中的负载:(1.51+0.91+0.42)/3=0.95,说明负载重
一直按1,可以看到有每个cpu的情况:

uptime命令是top的精简版:可以用来看负载

vmstat:

主要用于查看cpu
vmstat -n 2 3表示每隔两秒采样一次,总共采样三次
procs
cpu(前三个重要)
查看所有cpu核信息:mpstat -P ALL 2,其中2表示每2秒采样一次

每个进程使用cpu用量的分解信息:pidstat -u 2 -p 进程id,其中2表示每两秒采样一次
[root@yy ljy]# ps -ef | grep 'java -jar'
root 23534 1 0 Jun17 ? 01:12:01 java -jar wechat-0.0.1-SNAPSHOT.jar
root 30815 30045 0 20:46 pts/1 00:00:00 grep --color=auto java -jar
[root@yy ljy]# pidstat -u 2 -p 23534
Linux 3.10.0-1160.49.1.el7.x86_64 (yy) 08/17/2022 _x86_64_ (1 CPU)
08:47:01 PM UID PID %usr %system %guest %CPU CPU Command
08:47:03 PM 0 23534 0.51 0.00 0.00 0.51 0 java
08:47:05 PM 0 23534 0.00 0.00 0.00 0.00 0 java
08:47:07 PM 0 23534 0.00 0.00 0.00 0.00 0 java
08:47:09 PM 0 23534 0.00 0.00 0.00 0.00 0 java
08:47:11 PM 0 23534 0.00 0.00 0.00 0.00 0 java

经验值:
应用程序可用内存/系统物理内存 > 70%内存充足
应用程序可用内存/系统物理内存 < 20%内存不足,需要增加内存
20% < 应用程序可用内存/系统物理内存 < 70%内存基本够用
pidstat -p 进程号 -r 采样间隔秒数:



磁盘块设备分布
util:一秒中有百分几的时间用于I/O操作。接近100%时,表示磁盘带宽跑满,需要优化程序或者增加磁盘;其他命令:
pidstat -d 采样间隔秒数 -p 进程号
如果没有该命令,安装命令:
wget http://gael.roualland.free.fr/ifstat/ifstat-1.1.tar.gz
tar xzvf ifstat-1.1.tar.gz
cd ifstat-1.1
./configure
make
make install
使用


top

ps -ef或者jps

grep -v grep:查找不含有 grep 的行
查具体的线程:ps -mp 进程id -o THREAD,tid,time

参数解释:
-m:显示所有的线程
-p pid:进程使用cpu的时间
-o:该参数后是用户自定义格式
线程id转为16进制:printf "%x\n” 有问题的线程ID

或者使用计算器:但注意最后要把字母转为小写字母:
jstack 进程ID | grep tid(16进制线程ID小写英文) -A60,其中A60表示打印前60行

去看第十行:
