一、锁
1、synchronized
同步方法
public synchronized static void test() {
}
同步代码块
public static void test() {
User user = new User(1, "a", 21);
synchronized (user) {
}
}
synchronized锁的是对象,静态同步方法与普通同步方法与普通方法三者互不影响,静态同步方法属于class对象(唯一)。普通同步方法属于new对象,每一个new对象互不影响。
2、lock
1)、ReentrantLock
public static void test() {
Lock lock = new ReentrantLock();
lock.lock();
try {
} finally {
lock.unlock();
}
}
2)、ReentrantReadWriteLock
可重入读写锁,针对读写的情况进行更细粒度的加锁,
- 可以有多个线程同时读,不允许写;
- 同时只能有一个线程写,不允许读。
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.readLock().lock();
readWriteLock.readLock().unlock();
readWriteLock.writeLock().lock();
readWriteLock.writeLock().unlock();
3、区别
- synchronized 是关键字,lock是类;
- synchronized 自动获取、释放锁,lock手动获取、释放锁;
- lock更灵活
二、等待与唤醒
1、wait()、notify()、notifyAll()
wait()、notify()、notifyAll()属于Object类的方法,与synchronized一起使用,只能让当前线程陷入等待或唤醒,或唤醒所有线程。
public static void test() {
User user = new User(1, "a", 21);
synchronized (user) {
this.wait();
this.notify();
}
}
2、await()、signal()、signalAll()
await()、signal()、signalAll()属于Condition的方法,Condition与Lock一起使用,每一把Lock锁都有一个对应的Condition对象,所以可以实现精准通知,让哪一把Lock锁的线程陷入等待,唤醒哪一把Lock锁里面的线程。
public static void test() {
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
Condition condition1 = lock1.newCondition();
Condition condition2 = lock2.newCondition();
lock1.lock();
try {
condition1.await();
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock1.unlock();
}
}
三、线程安全的集合
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- ConcurrentHashMap
四、常用辅助类
1、CountDownLatch
等待指定数量的线程执行完后,主线程才会继续向下执行
public static void main(String[] args) throws InterruptedException {
// 指定数量的线程执行完后,主线程才会继续向下执行
CountDownLatch countDownLatch = new CountDownLatch(3);
for (int i = 1; i < 4; i++) {
new Thread(() -> {
// 计数器减一
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
// 等待计数器归零
countDownLatch.await();
}
2、CyclicBarrier
当线程数量达到某个值,开启一个新的线程
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
// 当线程数量达到指定的个数,才会执行这段代码
System.out.println("最后执行");
});
for (int i = 1; i < 4; i++) {
new Thread(() -> {
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
3、Semaphore
指定许可证数量,即可以同时执行的线程数量,只有拿到许可证的线程才可以执行,没有拿到许可证的线程只能等待。
public static void main(String[] args) {
// 定义许可证数量
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i < 7; i++) {
new Thread(() -> {
try {
// 获取许可证
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放许可证
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
4、ForkJoin
将一个任务分为多个小任务并行处理,以提高效率。
public class ForkJoinDemo {
public static void main(String[] args) {
test1(0, 10_0000_0000);
test2(0, 10_0000_0000);
test3(0, 10_0000_0000);
}
public static void test1(long start, long end) {
long a = System.currentTimeMillis();
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
long b = System.currentTimeMillis();
System.out.println("test1 ===> sum = "+ sum +"; 耗时:" + (b - a) + "ms");
}
public static void test2(long start, long end) {
long a = System.currentTimeMillis();
Long sum = new MyForkJoin(start, end).compute();
long b = System.currentTimeMillis();
System.out.println("test2 ===> sum = "+ sum +"; 耗时:" + (b - a) + "ms");
}
public static void test3(long start, long end) {
long a = System.currentTimeMillis();
long sum = LongStream.rangeClosed(start, end).parallel().reduce(0, Long::sum);
long b = System.currentTimeMillis();
System.out.println("test3 ===> sum = "+ sum +"; 耗时:" + (b - a) + "ms");
}
}
class MyForkJoin extends RecursiveTask<Long> {
private final long start;
private final long end;
public MyForkJoin(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long temp = 1_0000L;
if ((end - start) < temp) {
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}
long middle = (start + end) / 2;
MyForkJoin myForkJoin1 = new MyForkJoin(start, middle);
myForkJoin1.fork();
MyForkJoin myForkJoin2 = new MyForkJoin(middle + 1, end);
myForkJoin2.fork();
return myForkJoin1.join() + myForkJoin2.join();
}
}
5、Future
异步回调,开启一个线程后,继续向下执行一段代码,然后才获取先前开启的线程的返回结果。
public class FutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("test1==================");
test1();
System.out.println("test2==================");
test2();
}
public static void test1() throws Exception {
CompletableFuture<Void> async = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步回调,没有返回值");
});
System.out.println("main");
async.get();
}
public static void test2() throws Exception {
CompletableFuture<Object> supplyAsync = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = 10/0;
System.out.println("异步回调,有返回值");
return 1;
});
System.out.println(supplyAsync.whenComplete((t, u) -> {
System.out.println("t = " + t);
System.out.println("u = " + u);
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 0;
}).get());
}
}
五、BlockingQueue
1、常用api
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(,) |
移除 | remove() | poll() | take() | poll(,) |
检测队首元素 | element() | peek() |
2、SynchronousQueue
同步队列,相当于容量为一的阻塞队列。
六、线程池
1、七大参数
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, // 核心线程数
Runtime.getRuntime().availableProcessors(), // 最大线程数
10, // 等待时间
TimeUnit.MINUTES, // 等待时间单位
new LinkedBlockingDeque<>(), // 任务队列
Executors.defaultThreadFactory(), //线程工厂
new ThreadPoolExecutor.AbortPolicy()); // 拒绝策略
2、四种拒绝策略
new ThreadPoolExecutor.AbortPolicy() // 抛出异常
new ThreadPoolExecutor.CallerRunsPolicy() // 由创建线程池的线程处理
new ThreadPoolExecutor.DiscardPolicy() // 不抛出异常,不处理任务
new ThreadPoolExecutor.DiscardOldestPolicy() // 尝试竞争,失败丢弃任务
3、原理
- 当任务进入线程池后,判断核心线程是否已满;
- 如果核心线程未满,创建一个新的线程;如果核心线程已满,判断任务队列是否已满;
- 如果任务队列未满,进入任务队列等待;如果任务队列已满,判断最大线程是否已满;
- 如果最大线程未满,创建一个新的线程;如果最大线程已满,执行拒绝策略。
七、轻量级同步机制
1、volatile
关键字,加在属性前面,可以保证可见性与禁止指令重排(volatile会在前后添加内存屏障),但不保证原子性。
- 可见性:即当一个线程对该属性进行修改后,其他线程可以立马知道并更新数据。
- 指令重排(目的:为了提高效率):在代码被执行时,并不一定是按照写的从上到下的顺序执行,当上下代码之间没有依赖关系时,有可能被打乱执行顺序。
- 原子性:当代码被编译为字节码文件时,一行代码会变为多个字节码执行,这多个字节码应该为一个整体,中间不允许插入其他字节码。
这三个东西也叫做JMM(java内存模型),一种概念、逻辑上的东西,实际并不存在。
2、atomic
java.util.concurrent下的一个包,存放的都是原子类,即可以保证原子性。
原理:通过CAS与自旋锁实现。
3、CAS
compare and set,比较并交换。
- 当线程想把自己改变后的值写入方法区时,先比较方法区中该属性目前的值是否与线程最初拿到的值一样,如果一样,就写入方法区,完成操作;如果不一样,就重新从方法区获取值,再重新执行一遍。
- 缺点:影响效率,ABA问题。
4、ABA问题
假如一个线程A最开始拿到的某属性值为1,在这期间其他线程对该属性进行了多次操作,改为2、3等,最后又改回1,当线程A想把操作结果写回方法区时,拿到该属性这时候值任为1,就认为没有被人改动过,就可以完成CAS,但实际在某些极端情况下是有问题的。
5、 AtomicStampedReference
原子引用,可以解决ABA问题,因为带了版本号,每次操作,版本号都会加一,在写入方法区前,不仅会比较操作的属性,还会比较版本号是否和原来一致。