快速掌握系列之Java并发编程

简介: 避开晦涩难懂的翻译书籍,用简单且便于记忆的程序员白话,引导大家快速掌握Java编程知识。Java并发编程作为解决计算密集和IO密集的解决方案,有较多知识点需要掌握。但只要我们先掌握核心骨架知识,便能在工作中应用,深层原理和实操都可在必要时再查阅相关资料。开饭开饭!

一、知识骨架

1. 并发和并行

1.1 简介

并发是指一个线程同时跑多个任务。

并行是指多个线程同时跑多个任务。

1.2 原理

1.2.1 并发的原理

线程交替在cpu中执行任务,cpu执行和切换任务速度极快,肉眼无法察觉,呈现多个任务一起执行的假象,最终达到人们感官上的并发执行。

1.2.2 并行的原理

多个cpu或多个cpu核心各自认领一个任务,互不干涉的执行,达到并行执行的目的。

1.3 并发的指标

1)TPS

每秒处理的事务数(即写值数)

2)QPS

每秒处理的查询数

3)RT(Response-TIME)

响应时间

2. 进程和线程

2.1 简介

进程是操作系统资源分配的基本单元。

线程是操作系统任务调度的基本单元。

进程负责管理线程的内存、IO的资源分配,线程是被cpu调度执行具体的程序任务。

2.2 线程的状态

1)NEW,新建状态,表示已创建但未执行。

2)RUNNABLE,允许运行状态,表示可以被执行。

a. READY, 就绪状态,表示等待被执行。

b. RUNNING,运行状态,表示执行中。

3)BLOCKED,阻塞状态,未抢占到同步锁则进入阻塞状态。

4)WAITING, 等待状态,调用了wait()进入等待被唤醒状态。

5)TIMED_WAITING,等待超时恢复状态,主动调用sleep()进入超时后被唤醒的状态。

6)TEAMINATED,终止状态,执行完毕后的状态。

2.3 线程的上下文切换

2.3.1 概念

将当前线程所需上下文信息保存,切换为其他线程的上下文

2.3.2 频繁触发上下文切换的后果

上下文切换时并不会执行任务,过于频繁会导致任务执行效率变低。

2.3.3 触发上下文切换的场景

1)多个任务抢占synchronized保护的资源

未抢到同步锁的线程会保存自己的上下文,切换为抢到锁线程的上下文。

2)出现I/O阻塞

当前线程发生I/O阻塞,保存自己的上下文,等I/O结束时再进入就绪状态等待被唤醒。

3)主动等待

调用了wait()或sleep()方法,当前线程则让出自己的执行权。

4)线程执行完毕后

执行完毕后,让出执行权,等待垃圾回收。

2.3.4 如何避免频繁触发上下文切换

1)采用无锁或自旋锁的方式避免上下文切换。

a. 无锁则不会触发额外的上下文切换。

b. 命中自旋锁后,当前线程不会让出执行权,而是循环尝试直到获取到自旋锁。

2)减少I/O次数或让I/O不集中发生。

I/O次数变少,则线程额外上下文切换次数变少。

3)减少主动等待。

通过业务实现微调避开主动等待。

4)减少线程数量。

线程执行完毕后或线程执行时都不可避免的会发生上下文切换,减少线程数量可线性的减少上下文切换的次数。

2.4 并发编程特性

1)原子性

一个或多个操作要么都执行,要么都不执行。

2)可见性

一个线程修改了某变量值,其他线程能立即看到该值变化

3)有序性

程序执行顺序要按代码语义顺序执行

2.5 Java内存模型(JMM)

每个线程都有自己的本地内存,通过同步机制同步到主内存中

2.6 volatile修饰符

2.6.1 作用

1)保证可见性

写被volatile修饰的变量时,会把线程本地变量刷新到住内存中,导致其他线程的本地变量失效。

2)禁止指令重排

Java代码转化为字节码时,JVM会在不改变程序语义的基础上进行指令重排,优化代码执行效率,但该优化有时会导致意料之外的bug,加上该修饰符可防止该bug发生。

2.6.2 原理

通过在操作变量的代码区域加上内存屏蔽指令,保证内存屏蔽区域不发生指令重排,并将写操作立即写入主存保证可见性。

2.7 线程的类型

2.7.1 简介

1)用户线程,父线程执行完毕后会继续执行的线程

2)守护线程,父线程执行完毕后不会再继续执行的线程

2.7.2 程序演示

1)用户线程

@Slf4jpublicclassUserThreadExample {
publicstaticvoidmain(String[] args) throwsInterruptedException {
Threadthread=newThread(()->{
while(true) {
try {
log.info("用户线程运行中...");
Thread.sleep(1000);
                } catch (InterruptedExceptione) {
e.printStackTrace();
                }
            }
        });
thread.start();
Thread.sleep(100);
log.info("父线程执行完毕...");
    }
}

image.png

2)守护线程

@Slf4jpublicclassDaemonThreadExample {
publicstaticvoidmain(String[] args) throwsInterruptedException {
Runtime.getRuntime().addShutdownHook(newThread(()->log.info("JVM进程已结束")));
Threadthread=newThread(()->{
while(true) {
try {
log.info("用户线程运行中...");
Thread.sleep(1000);
                } catch (InterruptedExceptione) {
e.printStackTrace();
                }
            }
        });
thread.setDaemon(true);
thread.start();
Thread.sleep(100);
log.info("父线程执行完毕...");
    }
}

image.png

2.7.3 守护线程使用场景

1)心跳检测、时间监听等场景,当JVM进程退出时不再需要这些线程

2.8 如何正确终止线程

通过stop()是不安全的,会导致以下两个问题:

1)会抛出ThreadDeath异常,会导致业务处理不完整

2)会释放当前线程所有锁

通过interrupt方法给指定线程发送中断信号,再通过isinterrupted()方法判断是否完成中断

如果当前线程处于阻塞或等待状态,则会抛出异常,捕获该异常重新触发一次interrupt(),可实现将处于阻塞或等待状态的线程中断

// 未阻塞的线程中断的方法@Slf4jpublicclassInterruptExampleextendsThread{
@Overridepublicvoidrun() {
inti=0;
while(!Thread.currentThread().isInterrupted()){
i++;
        }
log.info("线程已被中断,i={}", i);
    }
publicstaticvoidmain(String[] args) throwsInterruptedException {
InterruptExampleinterruptExample=newInterruptExample();
interruptExample.start();
TimeUnit.SECONDS.sleep(1);
log.info("before:InterruptExample中断状态:{}", interruptExample.isInterrupted());
interruptExample.interrupt();
log.info("after:InterruptExample中断状态:{}", interruptExample.isInterrupted());
    }
}
// 阻塞的线程中断的方法@Slf4jpublicclassBlockedThreadInterruptExampleextendsThread{
@Overridepublicvoidrun() {
while(!Thread.currentThread().isInterrupted()) {
try {
TimeUnit.SECONDS.sleep(500);
            } catch (InterruptedExceptione) {
e.printStackTrace();
Thread.currentThread().interrupt();
            }
        }
log.info("线程被中断");
    }
publicstaticvoidmain(String[] args) throwsInterruptedException {
BlockedThreadInterruptExampleblocked=newBlockedThreadInterruptExample();
blocked.start();
TimeUnit.MILLISECONDS.sleep(100);
log.info("before:InterruptExample中断状态:{}", blocked.isInterrupted());
blocked.interrupt();
log.info("after:InterruptExample中断状态:{}", blocked.isInterrupted());
    }
}

2.9 线程池类型

1)FixedThreadPool(有限线程数的线程池)

核心线程数和最大线程数一样

2)CachedThreadPool (无限线程数的线程池)

3)ScheduledThreadPool (定时线程池)

定时或周期性执行任务

4)SingleThreadExecutor (单一线程池)

核心线程数和最大线程数都是1

5)SingleThreadScheduledExecutor(单一定时线程池)

6)自定义线程池

2.10 自定义线程池如何使用

ThreadPoolExecutorthreadPool=newThreadPoolExecutor(
2,   //核心线程数量(常驻线程)5,   //最大线程数量2L,    //存活时间TimeUnit.SECONDS,
newArrayBlockingQueue<>(3),   //阻塞队列Executors.defaultThreadFactory(),     //线程工厂newThreadPoolExecutor.AbortPolicy()   //拒绝策略);

2.11 拒绝策略

1)AbortPolicy 策略会直接抛出RejectedExecutionException 的 RuntimeException,程序可以采用重试或放弃。
2) DiscardPolicy策略会直接默默丢失,不给你任何提示,不建议使用,容易莫名其妙丢任务。
3) DiscardOldestPolicy 策略丢弃任务队列中等待事件最长的,即最老的任务,和上一个区别是上一个丢弃的是新提交的任务,这个丢弃的是最老的任务。丢弃后就可以腾出一个队列的空位存放任务。
4) CallerRunsPolicy策略,谁提交任务谁来执行这个任务,即将任务执行放在提交的线程里面,减缓了线程的提交速度,相当于负反馈。在提交任务线程执行任务期间,线程池又可以执行完部分任务,从而腾出空间来。

2.12 核心线程数一般配多少

cpu密集型/计算密集型:核心线程数=CPU核数+1

IO密集型:

核心线程数 = CPU 核心数 * (1 + IO 耗时/ CPU 耗时)

核心线程数=CPU核数*2或CPU核心数/(1-阻塞系数)

阻塞系数在0.8~0.9之间

3. 锁

3.1 线程安全的概念

多个线程同时操作同一个资源,会导致的数据错乱,最终让线程的操作相对于数据不再安全

3.2 锁的类型

1)synchronized锁

Java提供的修饰符级别的可重入的互斥锁

2)ReentrantLock锁

Java提供的函数级别的可重入的互斥锁

3)ReentrantReadWriteLock/StampedLock

可重入的读写锁

3)自旋锁

依托CAS操作实现的各种锁

3.2 synchronized锁

3.2.1 类型

1)类锁

类的所有对象抢占到锁才能执行程序片段。

// 锁静态变量publicsynchronizedstaticinti;
// 锁静态方法publicstaticsynchronizedvoiddemo() {}
// 锁类synchronized(Demo.class) {}

2) 对象锁

如果是同一个对象,抢占到锁才能执行程序片段,如果是不同对象,可同时执行。

// 锁非静态变量publicsynchronizedinti;
// 锁非静态方法publicsynchronizedvoidm1() {}
// 锁某对象publicObjectDemoMethod(Objecta1) {
synchronized(a1) {}
}
// 锁当前对象publicvoidm2() {
synchronized(this) {}            
}

3.3 对象的存储结构

3.4 Mark Word的存储结构

Mark Word在64位系统中占有64个bit的存储空间,分为无锁、偏向锁、轻量级锁、重量级锁、GC标记等状态

比如下图当锁标记为轻量级锁00时,第0~62个bit存储的是指向栈中锁记录的指针

3.5 synchronized锁升级的过程

3.5.1 简介

锁会按无锁、偏向锁、轻量级锁、重量级锁流程升级

3.5.2 偏向锁

在没有线程竞争时,会通过偏向锁来抢占访问资格。这个抢占过程基于CAS。

3.5.3 轻量级锁

通过自适应自旋锁处理线程竞争。

自适应自旋锁会在自旋成功时,自旋时间会增加,自旋失败时,自旋时间会减少

3.5.4 重量级锁

通过系统层面的互斥量实现的互斥锁

3.5.5 锁升级的过程

1)当运行到被synchronized修饰的代码块时,对象的从无锁转化为偏向锁

2)当被synchronized保护代码块出现线程竞争时,偏向锁转化为轻量级锁

3)当线程自旋一定次数后获取锁失败,进入阻塞等待状态,最终转化为重量级锁

3.5.6 死锁

3.5.6.1 简介

当两个线程互相持有对方需要的锁资源时,导致的线程阻塞无法继续执行的现象

3.5.6.2 死锁产生的必要条件

1)互斥条件

资源具有互斥性

2)资源不释放性

线程没有结束之前资源不会释放

3)互相依赖性

两个线程互相依赖对方获得的资源

3.5.6.3 如何避免死锁

1)通过业务微调避开互斥

2)多个资源不要一起占有不释放

3)两个线程不要相互依赖

3.6 ReentrantLock锁

1)lock(),没有抢占到锁,则阻塞

2)trylock(),尝试抢占锁,抢占成功返回true,抢占失败返回false

3)unlock,释放锁

3.7 ReentrantReadWriteLock锁

3.7.1 常用函数

1)writeLock,写锁

2)readLock,读锁

3.7.2 互斥情况

1)读-读不互斥

2)读-写互斥

3)写-写互斥

3.8 StampedLock锁

3.8.1 常用函数

1)writeLock,写锁

2)readLock,读锁

3)tryOptimisticRead

乐观读锁,适用于读多写少的场景,读取完后做一次校验,如果校验通过表示这段时间没有其他线程进行写操作,数据可直接使用,如果校验没通过,需要通过获取读锁,再获取数据

4)tryConvertToWriteLock(s1.readLoc())

将读锁转化为写锁

3.9 自旋锁

3.9.1 ABA问题

三个线程时,线程1准备将值从1000改成800,此时线程2将值从1000改成700,线程3将值从700改回1000,线程1开始执行把值改成800,最终导致线程2将值从1000改成700的操作被忽略了

3.9.2 如何解决ABA问题?

1)使用CAS技术

a、增加版本号,值被修改时,版本号发生变化,下一个值进行修改时先比对版本号

b、增加时间戳,先比较时间戳是否变化,变化了则修改失败

3.9.3 自旋锁的优缺点

优点:

自旋锁适用于短时间可以获取锁的场景,可以有效降低上下文切换的次数

缺点:

如果不能短时间获取锁,会一直占用cpu资源用于自旋

3.9.4 自旋锁实现

1)简单自旋锁

缺点:

a 无法重入

b 无法保证公平性(即FIFO)

@Slf4jpublicclassSpinLockimplementsLock {
/***  use thread itself as  synchronization state*  使用Owner Thread作为同步状态,比使用一个简单的boolean flag可以携带更多信息*/privateAtomicReference<Thread>owner=newAtomicReference<>();
@Overridepublicvoidlock() {
Threadt=Thread.currentThread();
//spinwhile (!owner.compareAndSet(null, t)) {
try {
TimeUnit.SECONDS.sleep(1L);
            } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
            }
log.info("线程"+t.getName()+"自旋中");
        }
    }
@OverridepublicvoidlockInterruptibly() throwsInterruptedException {
    }
@OverridepublicbooleantryLock() {
returnfalse;
    }
@OverridepublicbooleantryLock(longtime, TimeUnitunit) throwsInterruptedException {
returnfalse;
    }
@Overridepublicvoidunlock() {
owner.set(null);
    }
@OverridepublicConditionnewCondition() {
returnnull;
    }
}
@Slf4jpublicclassSpinLockDemo {
publicstaticvoidmain(String[] args) throwsInterruptedException {
SpinLocklock=newSpinLock();
// T1线程newThread(() -> {
try {
Threadt=Thread.currentThread();
log.info("线程{}尝试获取锁", t.getName());
lock.lock();
log.info("线程{}的操作中", t.getName());
TimeUnit.SECONDS.sleep(5);
            } catch (Exceptione) {
e.printStackTrace();
            } finally {
lock.unlock();
            }
log.info("线程T1运行结束");
        }, "T1").start();
// 睡眠保证 T1线程先执行TimeUnit.SECONDS.sleep(1);
// T2线程newThread(() -> {
try {
log.info("线程T2尝试获取锁");
lock.lock();
log.info("线程T2操作中");
TimeUnit.SECONDS.sleep(1);
            } catch (Exceptione) {
e.printStackTrace();
            } finally {
lock.unlock();
            }
log.info("线程T2运行结束");
        }, "T2").start();
    }
}


2)可重入自旋锁

缺点:

a 无法保证公平性(即FIFO)

@Slf4jpublicclassSpinLockimplementsLock {
/***  use thread itself as  synchronization state*  使用Owner Thread作为同步状态,比使用一个简单的boolean flag可以携带更多信息*/privateAtomicReference<Thread>owner=newAtomicReference<>();
@Overridepublicvoidlock() {
Threadt=Thread.currentThread();
//spinwhile (!owner.compareAndSet(null, t)) {
try {
TimeUnit.SECONDS.sleep(1L);
            } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
            }
log.info("线程"+t.getName()+"自旋中");
        }
    }
@OverridepublicvoidlockInterruptibly() throwsInterruptedException {
    }
@OverridepublicbooleantryLock() {
returnfalse;
    }
@OverridepublicbooleantryLock(longtime, TimeUnitunit) throwsInterruptedException {
returnfalse;
    }
@Overridepublicvoidunlock() {
owner.set(null);
    }
@OverridepublicConditionnewCondition() {
returnnull;
    }
}
@Slf4jpublicclassSpinLockDemo {
publicstaticvoidmain(String[] args) throwsInterruptedException {
SpinLocklock=newSpinLock();
// T1线程newThread(() -> {
try {
Threadt=Thread.currentThread();
log.info("线程{}尝试获取锁", t.getName());
lock.lock();
log.info("线程{}的操作中", t.getName());
TimeUnit.SECONDS.sleep(5);
            } catch (Exceptione) {
e.printStackTrace();
            } finally {
lock.unlock();
            }
log.info("线程T1运行结束");
        }, "T1").start();
// 睡眠保证 T1线程先执行TimeUnit.SECONDS.sleep(1);
// T2线程newThread(() -> {
try {
log.info("线程T2尝试获取锁");
lock.lock();
log.info("线程T2操作中");
TimeUnit.SECONDS.sleep(1);
            } catch (Exceptione) {
e.printStackTrace();
            } finally {
lock.unlock();
            }
log.info("线程T2运行结束");
        }, "T2").start();
    }
}


3、公平自旋锁

线程先拿一个排队号,然后不断询问当前服务号是否是自己的排队号,相同则获取到了锁

缺点:

a 无法重入

@Slf4jpublicclassSpinFairLockimplementsLock {
privateAtomicIntegerserviceNum=newAtomicInteger(0);
privateAtomicIntegerticketNum=newAtomicInteger(0);
privatefinalThreadLocal<Integer>myNum=newThreadLocal<>();
@Overridepublicvoidlock() {
Threadt=Thread.currentThread();
myNum.set(ticketNum.getAndIncrement());
log.info("线程{}拿到排队号{}", t.getName(), myNum.get());
while (serviceNum.get() !=myNum.get()) {
try {
TimeUnit.SECONDS.sleep(1L);
            } catch (InterruptedExceptione) {
thrownewRuntimeException(e);
            }
log.info("线程"+t.getName()+"自旋中");
        }
log.info("线程{}发现当前服务号为{}", t.getName(), serviceNum.get());
    }
@OverridepublicvoidlockInterruptibly() throwsInterruptedException {
    }
@OverridepublicbooleantryLock() {
returnfalse;
    }
@OverridepublicbooleantryLock(longtime, TimeUnitunit) throwsInterruptedException {
returnfalse;
    }
@Overridepublicvoidunlock() {
serviceNum.compareAndSet(myNum.get(), myNum.get() +1);
myNum.remove();
    }
@OverridepublicConditionnewCondition() {
returnnull;
    }
}
@Slf4jpublicclassSpinFairLockDemo {
publicstaticvoidmain(String[] args) throwsInterruptedException {
SpinFairLocklock=newSpinFairLock();
// T1线程newThread(() -> {
try {
Threadt=Thread.currentThread();
log.info("线程{}尝试获取锁", t.getName());
lock.lock();
log.info("线程{}的操作中", t.getName());
TimeUnit.SECONDS.sleep(5);
            } catch (Exceptione) {
e.printStackTrace();
            } finally {
lock.unlock();
            }
log.info("线程T1运行结束");
        }, "T1").start();
// 睡眠保证 T1线程先执行TimeUnit.SECONDS.sleep(1);
// T2线程newThread(() -> {
try {
log.info("线程T2尝试获取锁");
lock.lock();
log.info("线程T2操作中");
TimeUnit.SECONDS.sleep(1);
            } catch (Exceptione) {
e.printStackTrace();
            } finally {
lock.unlock();
            }
log.info("线程T2运行结束");
        }, "T2").start();
    }
}

4. 线程间通讯

4.1 线程间通信的方法

1)基于volatile修饰的共享变量

2)通过wait/notify机制

wait()方法,使当前线程进入阻塞状态,并释放持有的锁

notify()方法,唤醒处于阻塞状态下的一个线程

notifyAll()方法,黄兴处于阻塞状态下的所有线程

3)基于synchronized实现

4)Thread.join()方法

等待前面线程执行结束后再执行,通过wait()/notify()方法实现

5)await()方法

让线程等待,并释放锁

6)signal()、signalAll()方法

唤醒被await()方法阻塞的线程

二、常用函数

1. CountDownLatch介绍

CountDownLatch是一个现场同步工具类,它允许一个或多个线程一直处于等待状态,知道其他线程执行结束

countdown(), 对计数器进行递减

await(),使调用该方法的现场进入等待状态

2 Semaphore介绍

它的主要功能是用来限制对某个资源同时访问的线程数量

acquire() 获取一个令牌

release() 释放一个令牌

令牌的数量是固定的,令牌不够时阻塞当前线程

3 CyclicBarrier介绍

让一组线程到达一个屏障(也叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续往下执行

线程通过CyclicBarrier的await()方法进入屏障

reset()把原本阻塞的现场全部失效,并唤醒

4 ThreadLocal介绍

当多个线程同时访问某个共享变量时,让每个线程对共享变量的相关操作仅对当前操作可见

set(T value)设置一个value,保存到当前线程的副本中

get() 得到当前线程内保存的value

remove() 移除当前线程内保存的value

withInitial()

通过ThreadLocal解决SimpleDateFormat线程安全问题

5 任务的拆分与聚合Fork/Join

5.5.1 简介

可以把一个大任务拆分成多个子任务进行并行计算,再把计算结果合并

Fork用来分解任务

Join用来实现数据聚合

fork() 创建一个异步执行的子任务

join() 等待任务完成后返回计算结果

invoke() 开始执行任务,必要时等待其执行结果

ForkJoinTask线程池

5.5.2 实际应用场景

远程调用多个业务服务查询,聚合为一个完整的json信息反馈给客户端

5.5.3 举例

publicclassForkJoinExample {
privatestaticfinalIntegerMAX=400;
staticclassCalculationTaskextendsRecursiveTask<Integer> {
privateIntegerstartValue;
privateIntegerendValue;
publicCalculationTask(IntegerstartValue, IntegerendValue) {
this.startValue=startValue;
this.endValue=endValue;
        }
// 运算过程@OverrideprotectedIntegercompute() {
if(endValue-startValue<MAX) {
System.out.println("开始计算的部分:startValue = "+startValue+";endValue = "+endValue);
IntegertotalValue=0;
for(intindex=this.startValue; index<=this.endValue; index++) {
totalValue+=index;
                }
returntotalValue;
            }
returncreateSubtasks();
        }
privateIntegercreateSubtasks() {
CalculationTasksubTask1=newCalculationTask(startValue, (startValue+endValue)/2);
subTask1.fork();
CalculationTasksubTask2=newCalculationTask((startValue+endValue)/2+1, endValue);
subTask2.fork();
returnsubTask1.join()+subTask2.join();
        }
    }
publicstaticvoidmain(String[] args) {
ForkJoinPoolpool=newForkJoinPool();
ForkJoinTask<Integer>taskFuture=pool.submit(newCalculationTask(1, 2002));
try {
Integerresult=taskFuture.get();
System.out.println("result:"+result);
        }catch (InterruptedExceptione) {
e.printStackTrace();
        }catch (ExecutionExceptione) {
e.printStackTrace();
        }
    }
}


三、 常见问题排查方法

1. 多个线程发生死锁如何排查

  1. 通过jps命令,查看Java进程的pid
  2. 通过jstack 命令查看线程dump日志

可以看到两个线程都处于Blocked状态,且互相持有对方需要的锁

2. cpu占用率很高,响应很慢如何排查

  1. top -c查看占用cpu最高的进程pid
  2. 使用top -H -p  查找该进程中消耗CPU最多的线程id
  3. 通过printf "0x%x\n" <线程id> 把线程pid转化为16进制
  4. 通过 jstack打印进程的dump日志,通过grep知道找到对应线程id的日志(-A表示显示匹配行及其后的n行)

3. 如何查看上下文切换次数?

相关文章
|
1月前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
17天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
21 0
|
19天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
3天前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
25 12
|
22天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
22天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
16天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
16天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
40 3
|
22天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
83 6
|
26天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界里,异常是程序运行中不可忽视的“惊喜”。它们可能突如其来,也可能悄无声息地潜伏。掌握异常处理的艺术,意味着你能够优雅地面对程序的不完美,并确保它即使在风雨飘摇中也能继续航行。本文将引导你理解Java异常的本质,探索捕获和处理这些异常的方法,并最终学会如何利用自定义异常为你的代码增添力量。
下一篇
DataWorks