目录
1.什么是JUC并发编程
全称:java.util.conconcurrent
2.线程和进程
进程:一个程序,一个进程往往包含多个线程,至少包含一个
Java默认有几个线程? 2个,main,GC
线程:Thread,Runnable,Callable
Java真的能开启线程吗?开不了,start()方法底层直接调用的是C++(native方法)
并发和并行
并发:多个线程操作同一个资源。
一核CPU,多线程快速交替执行
并行(多个人一起行走)
多核CPU,多线程可以同时执行;线程池
并发编程的本质:充分利用CPU的资源
线程在java中的6中状态
NEW(创建),RUNNABLE(就绪),BLOCKED(阻塞),WAITING(死死等),TIMED_WAITING(超时等),TERMINATED(终止)
在操作系统中的5中状态:创建,就绪,运行,阻塞,终止
wait和sleep区别:
1.来着不同的类
wait => Object
sleep => Thread
2.关于锁的释放
wait会释放锁,sleep不会释放锁,抱着锁睡觉
3.使用的范围是不同的
wait:必须在同步代码块中使用
sleep:可以在任何地方使用(Thread.sleep()或者TimeUnit.SECONDS.sleep())
3.Lock(锁)
sychronized本质是队列+锁
@Test
@DisplayName(value = "JUC编程")
public void test2() throws InterruptedException {
TicketStation ticketStation = new TicketStation();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticketStation.scale();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticketStation.scale();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticketStation.scale();
}
}, "C").start();
}
}
class TicketStation {
private int num = 40;
public synchronized void scale() {
if(num > 0) {
System.out.println(Thread.currentThread().getName()+"买了第"+num--+"票,剩余:"+num+"张票");
}
}
}
lock锁
Lock lock = new ReentrantLock();
公平锁:十分公平:可以先来后到
class TicketStation {
private int num = 40;
Lock lock = new ReentrantLock();
public void scale() {
try {
lock.lock();
if(num > 0) {
System.out.println(Thread.currentThread().getName()+"买了第"+num--+"票,剩余:"+num+"张票");
}
} catch (Exception e) {
} finally {
lock.unlock();
}
}
}
非公平锁: 十分不公平:可以插队(默认)
synchronized和lock锁的区别
1.synchronized是内置的关键字,monitorenter和monitorexit这两个原语来实现同步的,Lock是一个java类
2.synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.synchronized会自动释放锁,Lock必须需要手动是否锁,如果不释放锁,死锁
4.synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock锁不一定会等待下去(lock.tryLock())
5.synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,默认非公平(可以自己设置)
6.synchronized适合少量代码同步问题,Lock适合锁大量的同步代码
4.生产者和消费者
传统线程通信同步问题(synchronized版):synchronized+wait/notify
进阶问题:存在虚假唤醒的问题(把if替换成while即可)
@Test @DisplayName(value = "生产者和消费者") public void test3() { Digit digit = new Digit(); new Thread(() ->{for (int i = 0; i < 10; i++) digit.add();},"A").start(); new Thread(() ->{for (int i = 0; i < 10; i++) digit.sub();},"B").start(); new Thread(() ->{for (int i = 0; i < 10; i++) digit.add();},"C").start(); new Thread(() ->{for (int i = 0; i < 10; i++) digit.sub();},"D").start(); } class Digit { int i = 0; public synchronized void add() { while(i > 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } i++; System.out.println(Thread.currentThread().getName()+" 生产了"+i); //通知消费 this.notifyAll(); } public synchronized void sub() { //等待生产 while(i == 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } i--; System.out.println(Thread.currentThread().getName()+" 消费了"+i); //通知生产 this.notifyAll(); } }
JUC版的通信同步问题:
通过接口condition(同步监视器)和lock接口,构成新版的解决多线程通信同步问题的办法
Lock lock = new ReentrantLock();
//同步监视器
Condition condition = lock.newCondition();
@Test
@DisplayName(value = "生产者和消费者")
public void test3() {
Digit digit = new Digit();
new Thread(() ->{for (int i = 0; i < 10; i++) digit.add();},"A").start();
new Thread(() ->{for (int i = 0; i < 10; i++) digit.sub();},"B").start();
new Thread(() ->{for (int i = 0; i < 10; i++) digit.add();},"C").start();
new Thread(() ->{for (int i = 0; i < 10; i++) digit.sub();},"D").start();
}
class Digit {
int i = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void add() {
try {
lock.lock();
while(i > 0) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i++;
System.out.println(Thread.currentThread().getName()+" 生产了"+i);
//通知消费
condition.signalAll();
} catch (Exception e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
}
public void sub() {
try {
lock.lock();
//等待生产
while(i == 0) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i--;
System.out.println(Thread.currentThread().getName()+" 消费了"+i);
//通知生产
condition.signalAll();
} catch (Exception e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
}
}
JUC具有精准唤醒的功能
@Test
@DisplayName(value = "Condition精准唤醒")
public void test3() {
PrintChar printChar = new PrintChar();
new Thread(() ->{for (int i = 0; i < 10; i++) printChar.printA();},"A").start();
new Thread(() ->{for (int i = 0; i < 10; i++) printChar.printB();},"B").start();
new Thread(() ->{for (int i = 0; i < 10; i++) printChar.printC();},"C").start();
}
class PrintChar{
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int i = 1; //1A 2B 3C
public void printA() {
try {
lock.lock();
while(i != 1) {
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":AAAAAA");
i = 2;
condition2.signal();
} finally {
lock.unlock();
}
}
public void printB() {
try {
lock.lock();
while(i != 2) {
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":BBBBBB");
i = 3;
condition3.signal();
} finally {
lock.unlock();
}
}
public void printC() {
try {
lock.lock();
while(i != 3) {
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":CCCCCC");
i = 1;
condition1.signal();
} finally {
lock.unlock();
}
}
}
八锁问题
synchronized修饰的方法是一个非静态的同步方法,那么锁的是对象,或者说是类实例的调用者,对象不唯一,可以多个new出来
synchronized修饰的方法是一个静态的同步方法,那么锁的是类模板,类模板全局唯一
5.集合线程不安全
不安全的集合在多线程的情况下,普遍会抛出一个异常叫“并发修改异常”,用ArrayList举例
java.util.ConcurrentModificationException
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
3种解决方法如下:
1.ArrayList线程不安全,可以替换成Vector(最基础的回答)
底层使用的是synchronized锁机制,性能很差
public static void main(String[] args) {
List<String> list = new Vector<String>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
2.Collections集合工具类转换成线程安全集合(稍微提高的回答)
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<String>());
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
3.JUC编程CopyOnWriteArrayList(满意的回答)
底层使用的是lock锁机制,在写入时复制,简称COW,是计算机程序设计的一种优化策略,避免造成数据问题
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<String>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
再举个例子:HashSet集合
1.Set<Object> synchronizedSet = Collections.synchronizedSet(new HashSet<>());
2.CopyOnWriteArraySet<Object> copyOnWriteArraySet = new CopyOnWriteArraySet<>();
HashSet的本质是HashMap
HashSet的add()方法:
private static final Object PRESENT = new Object();
PRESENT 没用
再举个例子:HashMap集合
1.Map<String, Object> synchronizedMap = Collections.synchronizedMap(new HashMap<String, Object>());
2.Map<Object, Object> concurrentHashMap = new ConcurrentHashMap<>();
6.Callable接口
- 有返回值
- 可以抛出异常
- 方法不同 call()
public class Test5 {
public static void main(String[] args) {
Math math = new Math();
FutureTask<Integer> futureTask = new FutureTask<>(math);
new Thread(futureTask,"线程1").start();
new Thread(futureTask,"线程2").start();//适配器
Integer r = futureTask.get();//会阻塞
System.out.println(r);
}
}
class Math implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 100;
}
}
FutureTask实现了Runable接口,同时还有包含参数Callable的构造函数
结果执行一次,因为FutureTask有缓存,增强效率
拿去结果,可能会被阻塞
7.常用辅助类(必须会)
7.1.CountDownLatch(减法计数器)
两个方法:
countDownLatch.countDown();//执行一次减1
countDownLatch.await();//等待减到0的时候,被唤醒继续往下执行
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+" go out");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("close door");
}
7.2.CyclicBarrier(加法计算器)
方法:
//期待的累计总数,累计总数达到后,会发生“召唤神龙”的事件(Runnable)
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("召唤神龙"));
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
public static void main(String[] args){
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("召唤神龙"));
for (int i = 1; i <=7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+" 得到第"+temp+"颗");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
7.3.Semaphore(信号量)
作用:
多个共享资源互斥的使用,并发限流,控制最大线程数
方法:
//限流,有限的资源
Semaphore semaphore = new Semaphore(3);
//资源获取
semaphore.acquire();
//资源释放
semaphore.release();
public static void main(String[] args){
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 10; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+ "获取到了车位");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName()+ "离开了车位");
}
},"业主"+i).start();
}
}
8.ReadWriteLock(读写锁)
读的时候可以被多个线程去读,写的时候只能有一个线程去写
读写锁有两个锁
一个叫读锁,也叫共享锁
一个叫写锁,也叫独占锁
public static void main(String[] args){
ReadWriteTest readWriteTest = new ReadWriteTest();
for (int i = 0; i < 10; i++) {
final String temp = String.valueOf(i);
new Thread(() -> {
readWriteTest.put(temp, temp);
}, "线程"+String.valueOf(i)).start();
}
for (int i = 0; i < 10; i++) {
final String temp = String.valueOf(i);
new Thread(() -> {
readWriteTest.get(temp);
}, "线程"+String.valueOf(i)).start();
}
}
class ReadWriteTest{
public Map<String, String> cache = new HashMap<String,String>();
public ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key,String value) {
try {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+"写入"+key);
cache.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入OK"+key);
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) {
try {
readWriteLock.readLock().lock();
cache.get(key);
System.out.println(Thread.currentThread().getName()+"读了"+key);
} finally {
readWriteLock.readLock().unlock();
}
}
}
执行的结果一定是单线程写,多行程读
9.阻塞队列(BlockingQueue)
queue接口下包含阻塞队列(BlockingQueue),非阻塞队列(AbstractQueue),双端队列(BlockingDeque,Deque)
Iterable->Collection->Queue->BlockingQueue->{ArrayBlockingQueue,LinkedBlockingQueue }
应用场景:多线程并发处理,线程池
BlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
四组API(必须得会)
(会抛出异常)插入,删除,检测队首元素: add,remove,element,一旦元素不存在或者空间满,则抛出异常
(不会抛出异常)插入,删除,检测队首元素:offer(),poll,peek,一旦元素不存在或者空间满,则不会抛出异常,返回null
(不会抛出异常,但等待(一直阻塞))插入,删除:put,take
(不会抛出异常,但等待(超时阻塞))插入,删除:offer(E e, long timeout, TimeUnit unit),poll(long timeout, TimeUnit unit)
10.同步队列SynchronousQueue
- 和其他的BlockingQueue 不一样,SynchronousQueue不存储元素
- put了一个元素,必须从里面先take取出来,否则不能再put进去值了
- 每个插入操作必须等待另一个线程相应的删除操作
public static void main(String[] args){
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
synchronousQueue.put("aabbcc");
System.out.println(Thread.currentThread().getName()+" put le aabbcc");
synchronousQueue.put("112233");
System.out.println(Thread.currentThread().getName()+" put le 112233");
synchronousQueue.put("aqaqaq");
System.out.println(Thread.currentThread().getName()+" put le aqaqaq");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"ThreadA").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"ThreadB").start();
}