JUC:
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
并发编程根本原因:充分利用CPU的资源
java代码获取CPU核数:
public static void main(String[] args) {
System.out.println(Runtime.getRuntime().availableProcessors());
}
1,线程6种状态:
public enum State {
NEW, //新生
RUNNABLE, //运行
BLOCKED, //阻塞
WAITING, //等待
TIMED_WAITING, //超时等待
TERMINATED; //终止
}
2,wait sleep区别:
1,来自不同的类 wait -> Object sleep->Thread。
2,wait会释放锁,sleep不会释放锁。
3,使用范围不同,wait必须在同步代码块中使用,sleep可以在任何地方使用。
4,wait不需要捕获异常,sleep必须捕获异常。
3,Lock锁
Interface Lock:
所有已知实现类:
ReentrantLock , ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock
//可重入锁
public ReentrantLock() {
sync = new NonfairSync();//默认非公平锁
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
3.1 synchronized和lock区别:。
1,synchronized是Java内置关键字,Lock是一个Java接口。
2,synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁。
3,synchronized会自动释放锁,Lock必须要手动释放锁。如果不释放会造成死锁。
4,synchronized线程会等待锁,Lock锁不一定会等待下去。
5,synchronized适合锁少量的代码同步问题,Lock锁适合锁大量的同步代码块。
4 集合类不安全
4.1 ArrayList:并发下不安全,测试如下:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
结果:
可见出现了并发修改异常java.util.ConcurrentModificationException。
解决方案:
//解决方案:
//通过Vector
List<String> list = new Vector<>();//add时用的synchronized
//通过集合工具类转换
List<String> list = Collections.synchronizedList(new ArrayList<>());
//通过JUC java.util.concurrent.CopyOnWriteArrayList;
List<String> list = new CopyOnWriteArrayList<>();//add时用的lock锁
4.2 Set
public static void main(String[] args) {
Set<String> set = new HashSet<>();
//解决方案:
//1,通过集合工具类转换
Set<String> set1 = Collections.synchronizedSet(new HashSet<>());
//2,通过JUC java.util.concurrent.CopyOnWriteArraySet;
Set<String> set2 = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
顺便说一下HashSet底层是什么:
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
可见底层是一个HashMap。再来看add方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
因为map的key不能重复,所以set中的值不能重复。
4.3 Map
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
//解决方案:
//通过集合工具类转换
Map<String, Object> map1 = Collections.synchronizedMap(new HashMap<>());
//通过JUC java.util.concurrent.ConcurrentHashMap;
Map<String, Object> map2 = new ConcurrentHashMap<>();
}
5 Callable
Callable接口类似于Runnable。
1,可以有返回值
2,可以抛出异常
3,方法不同,callable->call() Runnable->run()
另外Callable有使用缓存,结果可能需要等待,有缓存。
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable使用方式:通过FutureTask类
public class Test {
public static void main(String[] args) {
//Callable调用方式 通过FutureTask
//因为FutureTask间接实现了Runnable接口,且能接受Callable接口类型参数;
FutureTask task = new FutureTask(new Callable_Thread());
new Thread(task).start();
}
}
class Callable_Thread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("call()");
return "call";
}
}
6 常用辅助类
6.1 CountDownLatch 减法计数器
使用方法:
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "已执行");
countDownLatch.countDown();//计数器减一
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零,再向下执行
System.out.println("end");
}
countDownLatch.countDown();//计数器减一
countDownLatch.await();//等待计数器归零,再向下执行
6.2 CyclicBarrier 加法计数器
public static void main(String[] args) throws InterruptedException {
//CyclicBarrier有两个构造方法
//CyclicBarrier(int parties, Runnable thread)构造方法,当计数器到达parties时,会执行thread.start()方法
CyclicBarrier cyclicBarrier = new CyclicBarrier(8,new Thread(()->{
System.out.println("计数器已到8");
}));
for (int i = 0; i < 8; i++) {
new Thread(()->{
try {
cyclicBarrier.await();
} catch (Exception e) {
}
}).start();
}
}
6.3 Semaphore 计数信号量
public static void main(String[] args) throws InterruptedException {
//抢车位 只有3个位置
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到了车位");
TimeUnit.SECONDS.sleep(2);//睡两秒
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + "释放了车位");
}
}).start();
}
}
semaphore.acquire();获得信号量,如果满了,则等待。
semaphore.release();释放信号量,将当前的信号量释放,然后唤醒等待的线程。
作用:多个资源互斥的使用,可以控制最大的线程数,进行并发限流。
7 ReadWriteLock 读写锁
读操作可以被多线程同时读。
写操作只能同时被一个线程写。
更加细粒度的控制
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing
*/
Lock writeLock();
}
其实现类为ReentrantReadWriteLock。
使用如下:
public class Test {
public static void main(String[] args) throws InterruptedException {
MyCache myCache = new MyCache();
//5个线程写操作
for (int i = 0; i < 5; i++) {
final int temp = i;//该值用于lambda表达式中
new Thread(()->{
myCache.write(Thread.currentThread().getName(),temp);
},String.valueOf(i)).start();
}
//5个线程读操作
for (int i = 0; i < 5; i++) {
final int temp = i;//该值用于lambda表达式中
new Thread(()->{
myCache.read(Thread.currentThread().getName());
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<Object,Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void write(Object key, Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入" + key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写入" + key + "完毕");
} finally {
readWriteLock.writeLock().unlock();
}
}
public void read(Object key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取" + key + "完毕,值为:" + o);
} finally {
readWriteLock.readLock().unlock();
}
}
}
"S:\Program Files\JDK\jdk1.8.0_221\bin\java.exe" ...
0开始写入0
0写入0完毕
1开始写入1
1写入1完毕
2开始写入2
2写入2完毕
3开始写入3
3写入3完毕
4开始写入4
4写入4完毕
3开始读取3
3读取3完毕,值为:3
4开始读取4
0开始读取0
2开始读取2
2读取2完毕,值为:2
4读取4完毕,值为:4
1开始读取1
0读取0完毕,值为:0
1读取1完毕,值为:1
分析结果可知,写操作是同步执行的,读操作不是。
独占锁(写锁)同时只能被一个线程占有
共享锁(读锁)同时可以被多个线程占有
8 阻塞队列 BlockingQueue
4种API(Application Programming Interface,应用程序接口)
方法 | 抛出异常 | 不抛出异常,有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add(E e) | offer(E e) | put(E e) | offer(E e, long timeout, TimeUnit unit) |
移除 | remove() | poll() | take() | poll(long timeout, TimeUnit unit) |
检测队首元素 | element() | peek() | - | - |
blockingQueue.add(E e)
队列已满时执行抛出异常"java.lang.IllegalStateException: Queue full"
blockingQueue.remove()
队列为空时执行会抛出和"java.util.NoSuchElementException"
blockingQueue.offer(E e)
队列已满时执行返回false。
blockingQueue.poll()
队列为空时执行返回null。
lockingQueue.put(E e)
队列已满时执行会一直等待下去,直到put进去。
blockingQueue.take()
队列为空时执行会一直等待下去,知道取到值。
blockingQueue.offer(E e, long timeout, TimeUnit unit)
队列已满时执行,会等待timeout unit,如果没有offer进去会自动结束,不再offer值。
blockingQueue.poll(long timeout, TimeUnit unit)
队列为空时执行,如果在timeout unit时间内取不到值会返回null
9 线程池
3大方法,7大参数,4种拒绝策略
池化技术:优化资源的使用。事先准备好一些资源。(线程池,连接池,内存池,对象池)
好处:
1,降低资源消耗
2,提高响应的熟速度
3,方便管理线程
4,线程复用
5,控制最大并发数
3大方法
ExecutorService singlePool = Executors.newSingleThreadExecutor();
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
ExecutorService cachePool = Executors.newCachedThreadPool();
7大参数
public ThreadPoolExecutor(
int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //线程保持活跃时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler //拒绝策略
){
***
}
4种拒绝策略
interface RejectedExecutionHandler 共有4种实现类。如下:
1,
AbortPolicy
:中止策略,拒绝任务的处理,并抛出一个RejectedExecutionException异常
2,CallerRunsPolicy
:继续运行策略 ,拒绝任务,返回给线程的调用者执行该任务。
3,DiscardPolicy
:放弃策略,拒绝任务并直接丢弃,不会抛出异常。
4,DiscardOldestPolicy
:舍弃最旧策略,丢弃最旧的未处理任务,然后重试,不行的话继续丢弃任务
最大线程数如何确定?
1,CPU密集型:线程数等于或略小于cpu核数,可以保持cpu效率最高。
2,IO密集型:线程数大于耗IO资源的线程。
10 Volatile关键字
使变量在多个线程间可见
强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
3个特性:
1,保证可见性
2,不保证原子性
3,禁止指令重排(内存屏障)
内存屏障:
1,保证特性操作的执行顺序
2,保证某些变量的内存可见性
【拓展】
- 如果不用lock锁,不用synchronized,怎么保证原子性?
- 可用JUC下的原子类解决。
volatile和synchronized的区别:
volatile | synchronized | |
---|---|---|
性能 | volatile是线程同步的轻量级实现,性能更优 | 性能低于volatile |
修饰范围 | 只能修饰变量 | 方法,代码块 |
阻塞 | 不会 | 会 |
特性 | 可以保证可见性,不能保证原子性 | 可以保证原子性,间接保证了可见性 |
目的 | 解决的是变量在多个线程间的可见性 | 解决的是多个线程之间访问资源的同步性 |
11 死锁
排查:使用JDK自带工具,执行jps命令得到线程ID,执行jstack+线程ID查看结果。