多线程并发问题的背景和定义
当一个程序涉及到多个线程同时运行时,就有可能出现多线程并发问题。多线程并发问题是指当多个线程同时访问共享资源(如数据、内存、文件等)时,由于相互之间的竞争和冲突,导致程序出现不稳定、不可预测的行为。例如,当两个线程同时访问同一个变量时,可能会出现读写冲突、数据错乱、死锁等问题。
多线程并发问题的起因
多线程并发问题的起因是由于多个线程同时访问共享资源而产生的竞争和冲突。例如,当多个线程同时读写同一个变量时,就有可能会出现读写冲突的情况。
多线程并发问题的影响
多线程并发问题的影响主要包括程序不稳定、不可预测、运行效率低下等方面。例如,当多个线程同时修改同一个数据结构时,就有可能会导致数据错乱、程序崩溃等问题。
多线程并发问题的定义
多线程并发问题的定义是指多个线程同时访问共享资源而产生的竞争和冲突,导致程序出现不稳定、不可预测的行为。在Java多线程编程中,常见的多线程并发问题包括:线程安全问题、死锁问题、活锁问题、饥饿问题等。
:::warning
a.线程安全问题: 当共享资源被多个线程同时操作时.会出现混乱,导致行为丢失,故障等问题.
b.死锁问题: 当不同的线程,互相在等待对方的释放锁条件时. 而彼此都不让步, 就会造成死锁.死锁会导致程序被拖垮掉, 或者直接无法工作了.
c.活锁问题: 当不同线程,获取到了值. 但出现故障,不像死锁不让步, 活锁会释放资源,之后重试.就像你要消费消息队列的第一条数据.错误就把取出来的数据继续放在消息队列头不断的重试,也是进行无意义的操作.
d.饥饿问题: 当线程的工作是为了消费消息队列的数据时,同时有多个线程,而每次的消息都被线程1截获并消费, 线程2迟迟取不到这条数据. 很生动就像人受饿. 解决方法 是实现公平. (负载均衡 公平锁)
:::
为了避免多线程并发问题,Java提供了一系列的线程同步机制,例如synchronized关键字、Lock接口、Semaphore类、CountDownLatch类等,开发人员可以根据具体的需求选择合适的线程同步机制来解决多线程并发问题。
Java并发安全问题的解决方法
- synchronized关键字
使用synchronized关键字修饰方法或代码块时, 多个线程同一时间只有一个线程能执行该修饰方法或代码块.确保同一时间只有一个线程操作数据.
public synchronized void add() {
count++;
}
- Lock接口
使用Lock可以灵活的控制锁的打开和关闭. 可以实现更加细粒度的线程同步
Lock lock = new ReentrantLock();
public void add() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
- volatile关键字
volatile关键字可以保证共享变量的可见性,即当一个线程修改了共享变量的值后,其他线程可以立即看到最新的值。英文翻译 容易变化的. 就像数据绑定. v-bind
- AtomicInteger类
AtomicInteger类是Java提供的一个原子性的整型变量,可以保证对该变量的所有操作都是原子性的。例如:
AtomicInteger count = new AtomicInteger();
public void add() {
count.incrementAndGet();
}
update table user set age = age+1
- CountDownLatch类
CountDownLatch类可以用来协调多个线程的执行,实现线程之间的同步。
CountDownLatch 定义了一个计数器,和一个阻塞队列, 当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,这里的计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器,CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。
CountDownLatch latch = new CountDownLatch(2);
new Thread(() -> {
// do something
latch.countDown();
}).start();
new Thread(() -> {
// do something
latch.countDown();
}).start();
latch.await(); 在两个线程执行完之后才恢复.
性能优化
在并发编程中,竞态条件和线程同步等问题可能会导致性能下降。为了解决这些问题,可以使用一些技术和算法来进行性能优化。其中,一种重要的技术是CAS算法。
- CAS(Compare and Swap)
算法是一种基于硬件的原子操作,用于实现多线程之间的同步和互斥。CAS算法的基本思想是:通过比较内存中的值和期望值是否相等,如果相等,则将内存中的值修改为新值,否则不进行操作,继续比较。 (乐观锁概念) - 线程池 Executor
Java四种常用的线程池使用方法:
- newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
2.newFixedThreadPool
创建固定大小的线程池。可控制线程最大并发数,超出的线程会在队列中等待。线程池的大小一旦达到最大值就会保持不变。 - newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,可灵活回收空闲线程,若无可回收,则新建线程。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时及周期性任务执行。
Spring boot 使用多线程的示例
- 配置自动注入 线程池
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
//或者注册成Bean 供全局自由使用
public Executor getAsyncExecutor() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,100,6000, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>());
executor.setCorePoolSize(10);
executor.setMaximumPoolSize(100);
return executor;
}
}
- 使用
//1. 注解
@Async
public void method(){
return;
}
/*此时该方法被调用时会自动开启线程*/
//2. 利用注册的Bean调用异步线程
@Autowired
Executor executor;
executor.submit(()->{
code......
})