1、什么是JUC?
java.util.concurrnet
java.util.concurrent.atomic
java.util.concurrent.locks
2、进程和线程
进程:一个程序的实例
线程:是一个进程的实体,CPU调度和分配的基本单位
java默认有两个线程,main方法和GC
开启线程的方式:Thread、Runnable、Callable
Java本质是无法开启线程的,是调用本地方法(c++)来开启线程的,java无法操作硬件。
3、并发、并行
并发编程:并发、并行
并发:多个线程操作同一个资源
并行:多个线程同时进行
public static void main(String[] args) {
int count = Runtime.getRuntime().availableProcessors();
System.out.println("CPU处理器数量:" + count); // 检测cpu逻辑处理器数量
}
并发编程的本质:充分利用CPU的资源
线程的六种状态
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
wait和sleep的区别
属于不同类
wait属于java.lang.Object类
sleep属于
java.util.concurrent.TimeUnit类
关于锁的释放
wait会释放锁
sleep不会释放锁
使用范围不同
wait必须在同步代码块使用
sleep可以在任意地方使用
是否需要捕获异常
wait不需要捕获异常
sleep必须需要捕获异常
synchronized 和 Lock 的区别
synchronized | Lock
java的一个关键字 | 一个接口,有很多实现类
无法判断锁的状态 | 可以判断是否获取了锁
可以自动释放锁 | 只能手动在finally中释放锁,否则会死锁
假设A线程获取锁的时候,B线程等待,如果A线程阻塞了,那么B线程智能永远等待 Lock可以尝试获取锁,有多种获取锁的方式 |
synchronized是可重入锁,非公平锁,不可以中断 Lock是可重入锁,默认是非公平锁(可以设置成公平锁),可以中断
功能单一,适合锁少量同步代码 | API丰富,灵活度高,适合锁大量同步代码
Callable
callable是创建线程的第三种方式
与之前两种的区别:
有返回值
可以抛出异常
方法与之前不同,之前是run(),现在是call()
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start(); // 有缓存,第二次不会再次输出
Integer result = (Integer) futureTask.get(); // 这个get方法,可能会产生阻塞,把他放在最后,或者异步调用
System.out.println(result);
}
}
class MyThread implements Callable
@Override
public Integer call() throws Exception {
return 1234;
}
}
常用辅助类
CountDownLatch(减法计数器)
// 减法计数器
public class CountDownLatchDemo {
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() + "执行成功~");
countDownLatch.countDown(); // 数量-1
}, String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零,再继续向下执行
System.out.println("六个线程全部执行成功!");
}
}
CyclicBarrier(加法计数器)
// 加法计数器
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 集齐7颗龙珠召唤神龙
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("7颗龙珠已集齐,召唤神龙~");
});
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();
}
}
}
Semaphore(计数信号量)
// 计数信号量
public class SemaphoreDemo {
public static void main(String[] args) {
// 假设有3个车位,6个车去停
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取,如果已经满了,等待,等待被释放为止
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2); // 睡眠两秒,模拟使用过程完成
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放
}
}, String.valueOf(i)).start();
}
}
}
读写锁ReadWriteLock
ReadWriteLock维护了一堆关联的locks,一个只用于读,一个用于写,读的时候可以多线程去读,写的时候只能一个线程去写
也被称作:独占锁和共享锁
public class ReadWriteLockDemo {
public static void main(String[] args) {
// 5个线程同时写入自定义缓存,要求一个一个写,防止写入错误,五个线程同时读缓存,读的顺序任意
MyCache cache = new MyCache();
// 5个线程同时写
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
cache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
// 5个线程同时读取
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
cache.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
class MyCache {
private volatile Map
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁
// 存,写入
public void put(String key, Object value) {
readWriteLock.writeLock().lock(); // 写锁加锁
try {
System.out.println(Thread.currentThread().getName() + "正在写入,Key:" + key);
map.put(key, value); // 写入
System.out.println(Thread.currentThread().getName() + "写入成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock(); // 写锁释放
}
}
// 取,读取
public void get(String key) {
readWriteLock.readLock().lock(); // 读锁加锁
try {
System.out.println(Thread.currentThread().getName() + "正在读取,Key" + key);
map.get(key); // 读取
System.out.println(Thread.currentThread().getName() + "读取成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock(); // 读锁释放
}
}
}
阻塞队列
什么时候阻塞?
写入时队列是满的会产生阻塞
读取时队列为空时会产生阻塞
BlockingQueue:阻塞队列
BlockingDueue:阻塞双端队列
抛出异常方式
/**
* 抛出异常方式
*/
public static void test1() {
ArrayBlockingQueue
System.out.println(queue.add("a"));
System.out.println(queue.add("b"));
System.out.println(queue.add("c"));
System.out.println(queue.element()); // 检测队列首部元素
// System.out.println(queue.add("d")); // 插入数据大于队列长度抛出异常:
java.lang.IllegalStateException: Queue full 队列已满
System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
// System.out.println(queue.remove()); // 队列中没有数据,再次移除会抛出异常:
java.util.NoSuchElementException 没有元素异常
}
当添加的数据大于队列大小时会抛出异常:
java.lang.IllegalStateException: Queue full 队列已满
当移除数据时队列为空时会抛出异常:
java.util.NoSuchElementException 没有元素异常
有返回值,不抛出异常
/**
* 有返回值,不抛出异常
*/
public static void test2() {
ArrayBlockingQueue
System.out.println(queue.offer("a")); // 返回值为true
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
System.out.println(queue.element()); // 检测队列首部元素
// System.out.println(queue.offer("d")); // 超出队列长度,返回值为false
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
// System.out.println(queue.poll()); // 取出时队列为空,返回值为null
}
当添加数据大于队列大小时,不抛出异常,返回值为false
当移除数据队列为空时,不抛出异常,返回值为null
等待,阻塞(一直等待)
/**
* 等待,阻塞(一直等待)
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue
queue.put("a"); // 无返回值
queue.put("b");
queue.put("c");
// queue.put("d"); // 超出队列长度,会一直等待队列有位置再添加到队列
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
// System.out.println(queue.take()); // 队列为空时一会一直等待队列中有数据加入再取出
}
等待,阻塞(超时退出)
/**
* 等待,阻塞(超时退出)
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue
System.out.println(queue.offer("a"));
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
// System.out.println(queue.offer("d", 2, TimeUnit.SECONDS)); // 如果队列满了,等待两秒,超过两秒后退出,返回false
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
// System.out.println(queue.poll(2, TimeUnit.SECONDS)); // 如果队列为空,等待两秒,超过两秒后退出,返回null
}
SynchronousQueue 同步队列
同步队列没有容量,put进去了一个元素,必须take出来,才能继续使用
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " put a");
queue.put("a");
System.out.println(Thread.currentThread().getName() + " put b");
queue.put("b");
System.out.println(Thread.currentThread().getName() + " put c");
queue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " take " + queue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " take " + queue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " take " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}
}