Java并发编程--基础进阶高级完整笔记。

这都不知道是第几次刷狂神的JUC并发编程了,从第一次的迷茫到现在比较清晰,算是个大进步了,之前JUC笔记不见了,重新做一套笔记。

参考链接:https://www.bilibili.com/video/BV1B7411L7tE






目录

1.多线程--基础内容

1.Thread状态

​ 6种:新建、运行、阻塞、等待、超时等待、结束(可点击Thread查看源代码)

public enum State {
NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;
}
2.Synchronized
  • 非公平锁
  • 可重入锁,

​ Synchronized:是非公平锁(不能保证线程获得锁的顺序,即线程不会依次排队去获取资源,而是争抢,但是结果一定是正确的),是可重入锁(已获得一个锁,可以再获得锁且不会造成死锁,比如synchronized内部可以再写个synchronized函数)

    /**
* Author: HuYuQiao
* Description: Synchronized实现方式(修饰函数即可)
*/
class TicketSync{
public int number = 50;
//synchronized本质是队列,锁
public synchronized void sale(){
if(number > 0) {
System.out.println(Thread.currentThread().getName() + "获得了第" + number-- +"票");
}
}
}
3.Lock锁
  • 可重入锁

  • 公平还是不公平锁可以设置(默认不公平锁)

        /**
    * Creates an instance of {@code ReentrantLock} with the
    * given fairness policy.
    *
    * @param fair {@code true} if this lock should use a fair ordering policy
    */
    public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    }

    Lock:加锁之后必须解锁,,否则其他线程就获取不到了,所以用try-catch-finally包起来。

    /**
* Author: HuYuQiao
* Description: Lock实现方式(加锁、解锁)
*/
class TicketLock{
Lock lock = new ReentrantLock();
public int number = 50;
public void sale(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获得了第" + number-- +"票"); } catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4.总结

​ 在不加锁情况下,多线程会争抢,导致输出顺序、计算结果都会不一致(上面例子结果如果一样是因为只有3个线程,for循环即出错,因为number--这个函数本身不是线程安全的),所以就引入了锁的概念,synchronized,lock保证了输出顺序、计算结果的一致性。

虚假唤醒:在synchronized.wait与lock.condition.await唤醒线程时,是从await代码之后开始运行,所以为了保证能唤醒线程,需要用while语句将代码包含起来。

​ 完整代码

package com.empirefree.springboot;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import sun.security.krb5.internal.Ticket; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /**
* @program: springboot
* @description: 多线程
* @author: huyuqiao
* @create: 2021/06/26 14:26
*/ @RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class ThreadTest { /**
* Author: HuYuQiao
* Description: Synchronized实现方式(修饰函数即可)
*/
class TicketSync{
public int number = 50;
//synchronized本质是队列,锁
public synchronized void sale(){
System.out.println(Thread.currentThread().getName() + "获得了第" + number-- +"票"); }
} /**
* Author: HuYuQiao
* Description: Lock实现方式(加锁、解锁)
*/
class TicketLock{
Lock lock = new ReentrantLock();
public int number = 50;
public void sale(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获得了第" + number-- +"票"); } catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
} @Test
public void testThread() {
// TicketSync ticket = new TicketSync();
TicketLock ticket = new TicketLock();
new Thread( () ->{
for (int i = 0; i < 50; i++) {
ticket.sale();
} },"ThreadA").start();
new Thread(()->{
for (int i = 0; i < 50; i++) {
ticket.sale();
}
},"ThreadB").start();
new Thread(()->{
for (int i = 0; i < 50; i++) {
ticket.sale();
}
},"ThreadC").start(); for (int i = 0; i < 500; i++) {
new Thread(() -> {
ticket.sale();
}).start();
}
}
}

2.八锁现象(synchronized、static)

即synchronized、static修饰的函数,执行顺序、输出结果。

结果表明:

​ 1.synchronized修饰的函数:会锁住对象(可以看成锁对象中某个方法),看起来代码会依次执行,而没有锁的方法即不受影响,一来就先执行

​ 2.static synchronized修饰的函数:会锁住类.class(可以不同对象访问的都是同一个函数),所以2个对象访问自己的函数依然还是顺序执行.

​ 3.一个有static,一个没有static:即一个锁类.class,另一个锁对象,不管是同一个对象还是不同对象,就都不需要等待了,不会顺序执行。

1.synchronized

​ 修饰函数会保证同一对象依次顺序执行()

class Phone{
//synchronized
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendSms");
}
public synchronized void call() {
System.out.println("call");
} public void playGame(){
System.out.println("playGame");
}
} public static void main(String[] args) {
//Thread--代码执行顺序问题
Phone phone = new Phone();
new Thread(phone::sendSms, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(phone::call, "B").start();
new Thread(phone::playGame, "C").start();
}
2.static synchronized
class PhoneStatic{
//static synchronized
public static synchronized void sendSmsStatic() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendSmsStatic");
}
public static synchronized void callStatic() {
System.out.println("callStatic");
}
} public static void main(String[] args) {
PhoneStatic phoneStatic = new PhoneStatic();
PhoneStatic phoneStatic2 = new PhoneStatic();
new Thread(() ->{
phoneStatic2.sendSmsStatic();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() ->{
phoneStatic2.callStatic();
}, "B").start();
}

3.Java集合--安全性

        //集合安全性--list,set,map都非线程安全
List<String> list = new Vector<>();
List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>(); Map<String, String> objectObjectHashMap = new ConcurrentHashMap<>();
Map<Object, Object> objectObjectHashMap1 = Collections.synchronizedMap(new HashMap<>());
//set底层就是map:无论hashset还是linkedhashset
Set<String> set = Collections.synchronizedSet(new LinkedHashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();

4.高并发--辅助类

​ 学习链接:https://www.cnblogs.com/meditation5201314/p/14395972.html

1.countdownLatch

Countdownlatch:减一操作,直到为0再继续向下执行

package Kuangshen.JUC.Thread;

import java.util.concurrent.CountDownLatch;

public class countDownLatch {
public static void main(String[] args) throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(5); for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "get out");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await(); //等待上述执行完毕再向下执行
System.out.println("close door");
}
}
2.cyclicbarrier

Cyclicbarrier:+1操作,对于每个线程都自动+1并等待,累计到规定值再向下执行,

package Kuangshen.JUC.Thread;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier; /**
* @author :Empirefree
* @description:TODO
* @date :2021/2/10 10:56
*/
public class cyclicbarrier {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
//CyclicBarrier里面是容量 + runnable
final CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () ->{
System.out.println("不断增加到7即向后执行,与countdownlatch相反");
}
); /*对于指派的局部变量,lambda只能捕获一次 ,故而需定义成final(int内部定义就是final),而且线程中,
不能对局部变量进行修改,如需要修改,需定义成原子类atomic
*/
for (int i = 0; i < 7; i++) {
int finalI = i;
new Thread(() ->{
System.out.println(finalI);
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} }).start();
}
} }
3.semaphore

semaphore:对于规定的值,多个线程只规定有指定的值能获取,每次获取都需要最终释放,保证一定能互斥执行

package Kuangshen.JUC.Thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; /**
* @author :Empirefree
* @description:TODO
* @date :2021/2/10 15:24
*/
public class semaphore {
public static void main(String[] args) {
final Semaphore semaphore = new Semaphore(3); for (int i = 0; i < 60; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位"); } catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}

5.读写锁(ReadWriteLock)

​ ReadWriteLock也是多线程下的一种加锁方式,下面列出ReadWriteLock和synchronized对多线程下,保证读完之后在写的实现方式

//读写锁 :写只有一个线程写,写完毕后 可以多个线程读
MyCache myCache = new MyCache();
int num = 6;
for (int i = 1; i < num; i++) {
int finalI = i;
new Thread(()->{
myCache.write(String.valueOf(finalI),String.valueOf(finalI)); },String.valueOf(i)).start();
}
for (int i = 1; i < num; i++) {
int finalI = i;
new Thread(()->{
myCache.read(String.valueOf(finalI));
},String.valueOf(i)).start();
} class MyCache{
private volatile Map<String,String> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
//存,写
public void write(String key,String value){
lock.writeLock().lock(); //写锁
try { System.out.println(Thread.currentThread().getName()+"线程开始写入");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"线程开始写入ok");
} catch (Exception e){
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//取,读
public void read(String key){
lock.readLock().lock(); //读锁
try { System.out.println(Thread.currentThread().getName()+"线程开始读取");
map.get(key);
System.out.println(Thread.currentThread().getName()+"线程读取ok");
} catch (Exception e){
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
} //存,写
public synchronized void writeSync(String key,String value){
try {
System.out.println(Thread.currentThread().getName()+"线程开始写入");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"线程开始写入ok");
} catch (Exception e){
e.printStackTrace();
}
}
//取,读
public void readSync(String key){
try {
System.out.println(Thread.currentThread().getName()+"线程开始读取");
map.get(key);
System.out.println(Thread.currentThread().getName()+"线程读取ok");
} catch (Exception e){
e.printStackTrace();
}
}
}

6.线程池

1.集合--队列(阻塞队列、同步队列)

  • 阻塞队列:blockingQueue(超时等待--抛弃,所以最后size=1)

            //阻塞队列
    ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
    arrayBlockingQueue.offer("a", 2, TimeUnit.SECONDS);
    arrayBlockingQueue.offer("a", 2, TimeUnit.SECONDS);
    System.out.println("超时等待==" + arrayBlockingQueue.size());
  • 同步队列:synchronizeQueue(类似于生产者消费者)

            //同步队列
    SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
    new Thread(()->{
    try {
    System.out.println(Thread.currentThread().getName()+"put 01");
    synchronousQueue.put("1");
    System.out.println(Thread.currentThread().getName()+"put 02");
    synchronousQueue.put("2");
    System.out.println(Thread.currentThread().getName()+"put 03");
    synchronousQueue.put("3");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }).start();
    new Thread(()->{
    try {
    System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
    System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
    System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }).start();
2.线程池基本概念(三大方法、七大参数、四种拒绝策略)
  • 三大方法:newSingleThreadExecutro(单个线程),newFixedThreadPool(固定大小线程池),newCachedThreadPool(可伸缩)

     ExecutorService threadPool = Executors.newSingleThreadExecutor();
    ExecutorService threadPool2 = Executors.newFixedThreadPool(5);
    ExecutorService threadPool3 = Executors.newCachedThreadPool();
  • 七大参数:

    ThreadPoolExecutor(int corePoolSize,  //核心线程池大小
    int maximumPoolSize, //最大的线程池大小(当阻塞队列满了就会打开)
    long keepAliveTime, //(空闲线程最大存活时间:即最大线程池中有空闲线程超过这个时间就会释放线程,避免资源浪费)
    TimeUnit unit, //超时单位
    BlockingQueue<Runnable> workQueue, //阻塞队列
    ThreadFactory threadFactory, //线程工厂 创建线程的 一般不用动
    RejectedExecutionHandler handler //拒绝策略
  • 四种拒绝策略:

    new ThreadPoolExecutor.AbortPolicy: // 该 拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常
    new ThreadPoolExecutor.CallerRunsPolicy(): // //该拒绝策略为:哪来的去哪里 main线程进行处理
    new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。
    new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

7.Stream(4个函数式接口、Lambda、异步回调)

1.函数式接口

​ 学习链接:https://www.cnblogs.com/meditation5201314/p/13693089.html

2.Lambda表达式

​ 学习链接:https://www.cnblogs.com/meditation5201314/p/13651755.html

3.异步回调
        //异步回调--无返回值
CompletableFuture<Void> future = CompletableFuture.runAsync(() ->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "....");
});
System.out.println("begin");
System.out.println(future.get());
System.out.println("end"); //异步回调--有返回值
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() ->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 3;
});
System.out.println("begin");
System.out.println(future2.get());
System.out.println(future2.whenComplete((result, error) ->{
System.out.println("返回结果:" + result);
System.out.println("错误结果" + error);
}).exceptionally(throwable -> {
System.out.println(throwable.getMessage());
return 502;
}).get());
System.out.println("end");

8.单例模式

1.饿汉模式(程序一启动就new,十分占内存)
/**
* @program: untitled
* @description: 单例模式
* @author: huyuqiao
* @create: 2021/06/27 14:44
*/ public class SingleModel { /*
* 可能会浪费空间
* */
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private static final SingleModel hugrySingle = new SingleModel();
private SingleModel(){ }
public static SingleModel getInstance(){
return hugrySingle;
}
}
2.懒汉模式(DCL模式:双重检测,需要的时候才new)

1.第一层if没有加锁,所以会有多个线程到达if

2.如果内部new对象指令重排,就会导致有些线程认为lazyManModel有对象,所以会直接返回lazyManModel(实际为null)

3.所以需要加上valitile防止指令重排

/**
* @program: untitled
* @description: 懒汉式
* @author: huyuqiao
* @create: 2021/06/27 15:06
*/ public class LazyManModel {
private volatile static LazyManModel lazyManModel;
private LazyManModel(){
System.out.println(Thread.currentThread().getName() + "...");
} //DCL懒汉式:双重检测锁--实现效果,只有为空的才null,否则不用null,所以需要2重if判断。
public static LazyManModel getInstance(){
if (lazyManModel == null){
synchronized (LazyManModel.class){
if (lazyManModel == null){
lazyManModel = new LazyManModel();
}
}
}
return lazyManModel;
} public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() ->{
LazyManModel.getInstance();
}).start();
}
}
}

9.Volatile和Atomic

​ 学习笔记:https://www.cnblogs.com/meditation5201314/p/13707590.html

10.Java中锁

1.公平锁(FIFO):Lock锁可以自定义,synchronized
2.非公平锁(允许插队):Lock锁可以自定义
3.可重入锁(获取一个锁再获取其他锁不会造成死锁):lock锁和synchronized
4.自旋锁:得不到就一直等待(Atomic.getAndIncrement底层就是自旋锁)
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock; /**
* @program: untitled
* @description: spinlock
* @author: huyuqiao
* @create: 2021/06/27 15:40
*/ public class SpinLockTest {
public static void main(String[] args) throws InterruptedException { //使用CAS实现自旋锁
SpinlockDemo spinlockDemo=new SpinlockDemo();
new Thread(()->{
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlockDemo.myUnlock();
}
},"t1").start(); TimeUnit.SECONDS.sleep(1); new Thread(()->{
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlockDemo.myUnlock();
}
},"t2").start();
}
} class SpinlockDemo { // 默认
// int 0
//thread null
AtomicReference<Thread> atomicReference=new AtomicReference<>(); //加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"===> mylock"); //自旋锁--为空则返回true,否则返回false
while (!atomicReference.compareAndSet(null,thread)){
System.out.println(Thread.currentThread().getName()+" ==> .自旋中~");
}
} //解锁
public void myUnlock(){
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"===> myUnlock");
atomicReference.compareAndSet(thread,null);
} }
5.死锁命令排查
jps -l
jstack 进程号

Java并发编程--基础进阶高级(完结)的更多相关文章

  1. Java并发编程基础

    Java并发编程基础 1. 并发 1.1. 什么是并发? 并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互 ...

  2. 并发-Java并发编程基础

    Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...

  3. Java并发编程基础-线程安全问题及JMM(volatile)

    什么情况下应该使用多线程 : 线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止 CPU 占 ...

  4. java并发编程基础概念

    本次内容主要讲进程和线程.CPU核心数和线程数.CPU时间片轮转机制.上下文切换,并行和并发的基本概念以及并发编程的好处和注意事项,为java并发编程打下扎实基础. 1.什么是进程和线程 1.1 进程 ...

  5. 多线程(一)java并发编程基础知识

    线程的应用 如何应用多线程 在 Java 中,有多种方式来实现多线程.继承 Thread 类.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实现带 ...

  6. Java并发编程基础三板斧之Semaphore

    引言 最近可以进行个税申报了,还没有申报的同学可以赶紧去试试哦.不过我反正是从上午到下午一直都没有成功的进行申报,一进行申报 就返回"当前访问人数过多,请稍后再试".为什么有些人就 ...

  7. Java并发编程基础之volatile

    首先简单介绍一下volatile的应用,volatile作为Java多线程中轻量级的同步措施,保证了多线程环境中“共享变量”的可见性.这里的可见性简单而言可以理解为当一个线程修改了一个共享变量的时候, ...

  8. java并发编程基础——线程的创建

    一.基础概念 1.进程和线程 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据 ...

  9. Java并发编程基础-ReentrantLock的机制

    同步锁: 我们知道,锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在Lock接口出现之前,Java应用程序只能依靠synchronized关键字来实现同步锁 ...

随机推荐

  1. win10下卸载ubuntu的合理操作

    这里不推荐使用第三方软件,因为可能会被植入病毒,而且windows自带的命令行工具足以完成任务! win10系统自带的一个命令行工具--diskpart 在cmd中输入"diskpart&q ...

  2. 通过CRM系统改变传统工作模式

    在现在这个互联网时代,同行业的竞争越发激烈,因此许多企业都选择使用CRM来提高企业的销售业绩.CRM客户关系管理系统是能够优化企业的销售流程.维护良好的客户关系.对销售流程进行管理的强大工具.但是很多 ...

  3. Let's go!

    第一次开通博客 心情还是很激动的,而且做出了这么好看的页面虽然都是用的别人的组件,自己不是很知道原理但是也很开心,以后会将自己学习的东西写成笔记发在上面

  4. Linux工程师必备的88个监控工具

    Linux工程师必备的88个监控工具 https://learn-linux.readthedocs.io/zh_CN/latest/maintenance/monitor/tools/80-linu ...

  5. 列出 Ubuntu 和 Debian 上已安装的软件包

    列出 Ubuntu 和 Debian 上已安装的软件包 如果你经常用 apt 命令,你可能觉得会有个命令像 apt 一样可以列出已安装的软件包.不算全错. apt-get 命令 没有类似列出已安装软件 ...

  6. xpath定位中starts-with、contains、text()的用法

    starts-with 顾名思义,匹配一个属性开始位置的关键字 contains 匹配一个属性值中包含的字符串 text() 匹配的是显示文本信息,此处也可以用来做定位用 eg //input[sta ...

  7. Java 自定义常量

    Java 中的常量就是初始化或赋值后不能再修改,而变量则可以重新赋值. 我们可以使用Java 关键字 final 定义一个常量,如下 final double PI = 3.14; 注意:为了区别 J ...

  8. SystemVerilog 语言部分(二)

    接口interface: 既可以设计,也可以用来验证. 验证环境:interface使得连接变得简单不容易出错. interface可以定义端口,单双向信号,内控部使用initial always t ...

  9. 在浏览器上运行 VS Code——GitHub 热点速览 v.21.22

    作者:HelloGitHub-小鱼干 和小程序类似,如果平时开发所用到的软件也能运行在浏览器中,"用完即走"岂不妙哉?code-server 便是一个让人在浏览器运行 VS Cod ...

  10. IDEA debug ConcurrentLinkedQueue时抽风

    1. 介绍 如标题所见,我在使用IDEA debug ConcurrentLinkedQueue的Offer方法时,发生了下面的情况. 代码如下: ConcurrentLinkedQueue<s ...