JUC学习入门
JDK 1.5开始出现处理线程的工具包 java.util .concurrent简称JUC。
1、Lock锁
多线程卖票问题
- 资源类
class Ticket{
//总票数
private Integer ticket=40;
//卖票
public void sale(){
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+":卖票"+"=>剩余票:"+ticket);
}
}
}
- 线程操作
public static void main(String[] args) {
//操作资源
Ticket ticket = new Ticket();
//线程
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"a").start();
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"b").start();
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"c").start();
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"d").start();
}
结果
:多个线程操作同一个资源,会有安全性问题
Synchronized锁
- 资源类
class Ticket{
//总票数
private Integer ticket=40;
//卖票
public synchronized void sale(){
if(ticket>0){//防止多卖
//业务
ticket--;
System.out.println(Thread.currentThread().getName()+":卖票"+"=>剩余票:"+ticket);
}
}
}
- 线程操作
public static void main(String[] args) {
//操作资源
Ticket ticket = new Ticket();
//线程
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"a").start();
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"b").start();
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"c").start();
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"d").start();
}
结果
:解决线程安全问题
Lock锁
- 资源类
class Ticket1{
//总票数
private Integer ticket=40;
private Lock lock = new ReentrantLock();
//卖票
public void sale(){
lock.lock();//加锁
try {
if (ticket>0) {//防止多卖
//业务
ticket--;
System.out.println(Thread.currentThread().getName()+":卖票"+"=>剩余票:"+ticket);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
- 线程操作
public static void main(String[] args) {
//操作资源
Ticket ticket = new Ticket();
//线程
public static void main(String[] args) {
//操作资源
Ticket ticket = new Ticket();
//线程
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"a").start();
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"b").start();
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"c").start();
new Thread(()->{
for (int i = 0; i < 20; i++) { ticket.sale(); }
},"d").start();
}
}
结果
:解决线程安全问题
Synchronized锁与Lock锁
- Synchronized 内置的Java关键字, Lock 是一个Java类
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock 必须要手动释放锁!
- Synchronized 线程 1(获得锁,阻塞)、线程2(等待);Lock锁就不一定会等待下
- Synchronized 可重入锁,不可以中断的,非公平;Lock:可重入锁,可以 判断锁,默认非公平(可调节)
- Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
生产者、消费者问题:线程A++;线程B–
- 线程间通信
Synchronized版
- 资源类
class Factory{
private Integer product=0;
public synchronized void supply() throws InterruptedException {
if(product>0){
//等待
this.wait();
}
//业务
product++;
System.out.println(Thread.currentThread().getName()+"生产;产品个数"+product);
//唤醒其他线程
this.notifyAll();
}
public synchronized void consum() throws InterruptedException {
if(product==0){
//等待
this.wait();
}
//业务
product--;
System.out.println(Thread.currentThread().getName()+"消费;产品个数"+product);
//唤醒其他线程
this.notifyAll();
}
}
- 线程通信操作
public static void main(String[] args) {
Factory factory=new Factory();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
factory.supply();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
factory.consum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者").start();
}
结果:生成一个,消费一个
生产者生产;产品个数1
消费者消费;产品个数0
生产者生产;产品个数1
消费者消费;产品个数0
生产者生产;产品个数1
消费者消费;产品个数0
生产者生产;产品个数1
四条线程操作:两条生产,两条消费
结果:生产者之间也会有安全问题
生产者1生产;产品个数1
消费者1消费;产品个数0
消费者2消费;`产品个数-1
生产者2生产;产品个数0
生产者1生产;产品个数1
消费者1消费;产品个数0
消费者2消费;`产品个数-1
-
虚假唤醒
:if
==>while
-
资源类
public synchronized void consum() throws InterruptedException {
while(product==0){
//等待
this.wait();
}
TimeUnit.SECONDS.sleep(1);
//业务
product--;
System.out.println(Thread.currentThread().getName()+"消费;产品个数"+product);
//唤醒其他线程
this.notifyAll();
}
lock版
- 资源类
class Factory1{
private Integer product=0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void supply() {
lock.lock();
try {
while(product>0){
//等待
condition.await();
}
//业务
TimeUnit.SECONDS.sleep(1);
product++;
System.out.println(Thread.currentThread().getName()+"生产;产品个数"+product);
//唤醒其他线程
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consum() {
lock.lock();
try {
while(product==0){
//等待
condition.await();
}
TimeUnit.SECONDS.sleep(1);
//业务
product--;
System.out.println(Thread.currentThread().getName()+"消费;产品个数"+product);
//唤醒其他线程
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
精准唤醒线程:A->B->C
- 资源类
class ThreadTest{
private Integer product=1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void t1() {
lock.lock();
try {
while(product!=1){
//线程一等待
condition1.await();
}
//业务
product=2;
System.out.println(Thread.currentThread().getName());
//唤醒其他线程
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void t2() {
lock.lock();
try {
while(product!=2){
//线程二等待
condition2.await();
}
//业务
product=3;
System.out.println(Thread.currentThread().getName());
//唤醒三线程
condition3.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void t3() {
lock.lock();
try {
while(product!=3){
//线程三等待
condition3.await();
}
//业务
product=1;
System.out.println(Thread.currentThread().getName());
//唤醒一线程
condition1.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
- 线程操作
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
new Thread(()->{
while(true) { threadTest.t1(); }
},"t1").start();
new Thread(()->{
while(true) { threadTest.t2(); }
},"t2").start();
new Thread(()->{
while(true) { threadTest.t3(); }
},"t3").start();
}
2、八锁现象
多线程中锁的是什么?
//1、标准情况下,两个线程先打印"发短信"还是"打电话"?
//2、sendSms延迟4秒,两个线程先打印"发短信"还是"打电话"?
#synchronized 锁的对象是方法的调用者!
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{phone.sendSms();},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone.call();},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
//3、增加了一个普通方法后!先执行"发短信"还是"Hello"?
#普通方法不是同步方法,不受锁的影响
//4、两个对象,两个同步方法,"发短信"还是"打电话"?
# 两个对象,两个调用者,两把锁!
public class Test2 {
public static void main(String[] args) {
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{ phone1.sendSms();},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone2.call();},"B").start();
}
}
class Phone2{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
//5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?
#静态方法的调用者为类模板,方法区中只存在一个
//6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?
#静态方法的调用者为类模板,方法区中只存在一个
public class Test3 {
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{phone1.sendSms();},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone2.call();},"B").start();
}
}
class Phone3{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
//7、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?
#静态方法类模板调用,同步方法对象调用
//8、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?
#静态方法类模板调用,同步方法对象调用
public class Test4 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone4{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
小结
锁的对象有两类:[Class模板]+[调用者对象]
3、集合不安全
List 不安全:java.util.ConcurrentModificationException 并发修改异常!
- List list = new Vector<>(); 底层自动实现synchronized,效率低但安全
- List list = Collections.synchronizedList(new ArrayList<>()); 集合工具类
- List list = new CopyOnWriteArrayList<>(); 写入时复制,
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
Set 不安全
- Set set = Collections.synchronizedSet(new HashSet<>());
- Set set = new CopyOnWriteArraySet<>();
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <=30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
Map 不安全
public static void main(String[] args) {
// Map<String, String> mapnew HashMap<>(16,0.75);
// Map<String, String> map = new HashMap<>();
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 1; i <=30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(
0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
4、Callable
与Runnable接口的区别
- 可以返回值
- 可以抛异常
FutureTask 实现了Runnable接口,作为一个适配器类可用于启动Callable接口线程
public static void main(String[] args) throws ExecutionException,InterruptedException {
FutureTask futureTask = new FutureTask(new MyThread()); // 适配类
new Thread(futureTask,"A").start();
Integer integier = (Integer) futureTask.get(); //接受返回值,等待结果返回,可能会导致主线程阻塞
System.out.println(integier);//1024
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("call()"); // 会打印几个call
// 耗时的操作
return 1024;
}
}
5、常用的辅助类
CountDownLatch:规定最大可运行的线程数。
countDownLatch.countDown()
:线程数减一countDownLatch.await()
:等待线程归零唤醒,往下执行
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数是6,必须要执行任务的时候,再使用!
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("Close Door");
}
}
CyclicBarrier:规定一组线程数,启动一条线程唤醒一个CyclicBarrier,达到规定时执行特带方法
cyclicBarrier.await()
: 等待CyclicBarrier清零
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();
}
}).start();
}
}
Semaphore:信号量。 多个共享资源互斥的使用
semaphore.acquire()
:占用信号量semaphore.release()
:释放信号量,其他线程可抢占信号量
public static void main(String[] args) {
// 线程数量
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();
}
}
6、读写锁
- 独占锁(写锁): 一次只能被一个线程占有
- 共享锁(读锁): 多个线程可以同时占有
- 读写锁
class MyCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
// 读写锁: 更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 存,写入的时候,只希望同时只有一个线程写
public void put(String key,Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
TimeUnit.SECONDS.sleep(1);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入OK");
} 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);
TimeUnit.SECONDS.sleep(1);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
- 线程操作
public static void main(String[] args) {
MyCacheLock cacheLock = new MyCacheLock();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
cacheLock.put(String.valueOf(temp),temp);
},String.valueOf(temp)).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
cacheLock.get(String.valueOf(temp));
},String.valueOf(temp)).start();
}
}
7、阻塞队列
BlockingQueue:
BlockingQueue
继承Queue
,Queue
继承Collection
- 添加:从队列一端存入数据,队列满时阻塞
- 取出:从队列另一端取数据,队列空时阻塞
- 阻塞时抛出异常
public static void test1(){
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// System.out.println(blockingQueue.add("d"));// IllegalStateException: Queue full 抛出异常!
System.out.println("=-===========");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());// java.util.NoSuchElementException 抛出异常!
}
- 阻塞时有返回值,没有异常
public static void test2(){
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("d")); // false 不抛出异常!
System.out.println("============================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); // null 不抛出异常!
}
-阻塞时等待阻塞(一直阻塞)
public static void test3() throws InterruptedException {
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d"); // 队列没有位置了,一直阻塞
System.out.println("============================");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take()); // 没有这个元素,一直阻塞
}
- 阻塞时等待(等待超时)
public static void test4() throws InterruptedException {
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// blockingQueue.offer("d",2,TimeUnit.SECONDS); // 等待超过2秒就退出
System.out.println("===============");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
blockingQueue.poll(2,TimeUnit.SECONDS); // 等待超过2秒就退出
}
SynchronousQueue:同步队列。队列中只有一个位置。
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+" put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+" put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
8、线程池
好处:线程复用、可以控制最大并发数、管理线程
- 降低资源的消耗
- 提高响应的速度
- 方便管理。
- 三大方法
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();//只有一个线程
//源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);//固定大小的线程池
//源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();//可伸缩的线程池
//源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,//20亿,自动生成易引发OOM
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
七大参数
//创造线程池方法
public ThreadPoolExecutor(int corePoolSize, //核心池大小
int maximumPoolSize, //线程池最大线程
long keepAliveTime, //超时时间,线程无调用时释放
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler)//拒绝策略线程池满时无法继续开辟线程
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5,10,TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 1; i <= 8; i++) {
//从线程池取出线程
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"created!");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
threadPoolExecutor.shutdown();
}
}
四种拒绝策略:最大线程数=线程最大数量(maximumPoolSize)+阻塞队列大小(workQueue)
ThreadPoolExecutor.AbortPolicy()
:超出最大线程数,不处理抛出异常
ThreadPoolExecutor.CallerRunsPolicy()
:超出最大线程数,哪来回拿去,主线程处理
ThreadPoolExecutor.DiscardPolicy()
:超出最大线程数,丢弃任务
ThreadPoolExecutor.DiscardOldestPolicy()
:超出最大线程数,尝试与最早的线程抢夺线程
最大线程数量设置
- cpu密集型:cpu的核数
//cpu核数
int processors = Runtime.getRuntime().availableProcessors();
- I/O密集型:程序有必须处理的大型任务的两倍