持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,点击查看活动详情
JUC简介
在 Java 5.0 提供了 java.util.concurrent (简称 JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步IO和轻量级任务框架。
JUC是用于解决多线程同步问题,给java开发者提供便利的工具。
进程和线程的区别
进程是指在系统中正在运行的一个应用程序。进程是执行程序的实例。
线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。 对于操作系统而言,其调度单元是线程。
一个进程至少包括一个线程,通常将该线程称为主线程。一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。
volatile关键字
volatile是JAVA虚拟机提供轻量级同步机制。
volatile特性
- 保证可见性。
- 不保证原子性。
- 禁止指令重排。
其中可见性是其重要特性。那么什么是可见性?
可见:某个线程对 被volatile修饰的对象,可被其他线程可见。
所谓原子性操作就是指这些操作是不可中断的,要么一定做完,要么就没有执行。
简单测试一下volatile的不保证原子性
用一个原子操作,一个用volatile来操作,作对比看看有什么区别
/**
* @author miracle
*/
public class Num {
/**
* volatile 可见性,但不保证原子性
*/
volatile int num = 0;
/**
* 保证原子性
*/
AtomicInteger atomicNum = new AtomicInteger();
void plus() {
this.num += 1;
}
/**
* 原子性操作
*/
public void atomicPlus() {
atomicNum.getAndIncrement();
}
}
测试用例
public static void main(String[] args) {
Num number = new Num();
for(int i = 0; i < 20; i++) {
new Thread(() -> {
for(int j = 0; j < 1000; j++) {
number.plus();
number.atomicPlus();;
}
}).start();
}
// 默认有 main 线程和 gc 线程,等待20个线程处理完,再获取值
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("num-->" + number.num);
System.out.println("atomicNu-->" + number.atomicNum);
}
输出结果:
num-->19932
atomicNu-->20000
总结:在多线程下使用volatile不能保证同步。因为其不保证原子性。
创建多线程的方式
- 继承Thread类。
- 实现Runnable接口。
- 实现Callable接口。
- 线程池。
线程池代码示例:
线程锁
1. Lock同步锁。
在Java 5.0之前,协调共享对象的访问时可以使用的机制只有synchronized和volatile。Java 5.0后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。
ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。但相较于synchronized提供了更高的处理锁的灵活性。
2. synchronized锁。
synchronized和lock锁的区别
- synchronized内置的java关键字,Lock是一个接口。
- synchronized无法判断获取锁的状态, Lock可以判断是否获取到了锁。
- synchronized会自动释放锁,Lock必须要手动释放锁!如果不是释放锁,会产生死锁。
- synchronized可以作用在方法和代码块上,而lock只能作用在代码块上。
- synchronized是非公平锁,而lock可以是公平锁也可以是非公平锁。