java进阶—JUC编程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java进阶—JUC编程相关的知识,希望对你有一定的参考价值。

1、线程和进程

获取CPU核数

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/15
* @description 测试
*/
public class Test
public static void main(String[] args)
// 获取CPU核数
// CPU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());

线程有几个状态

public enum State 
// 新生
NEW,

// 运行
RUNNABLE,

// 阻塞
BLOCKED,

// 等待
WAITING,

// 超时等待
TIMED_WAITING,

// 终止
TERMINATED;

wait/sleep区别

1、来自不同的类

wait => Object

sleep => Thread

2、关于锁的释放

wait会释放锁,sleep不会释放锁

3、使用范围是不同的

wait必须在同步代码块中

sleep可以在任何地方执行

4、是否需要捕获异常

wait 不需要捕获异常

sleep 必须要捕获异常

2、Lock锁

传统 Synchronized

线程就是一个单独的资源类,没有任何附属操作:

  • 属性和方法
/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/15
* @description 基本卖票
*/
public class SaleTicketDemo01
public static void main(String[] args)
// 真正的多线程开发,公司中的开发
// 线程就是一个单独的资源类,没有任何附属操作
Ticket ticket = new Ticket();

// lambda表达式 () ->
new Thread(() ->
for (int i = 0; i < 60; i++)
ticket.sale();

, "A").start();
new Thread(() ->
for (int i = 0; i < 60; i++)
ticket.sale();

, "B").start();
new Thread(() ->
for (int i = 0; i < 60; i++)
ticket.sale();

, "C").start();




/**资源类 OOP编程**/
class Ticket
/**属性、方法**/
private int number = 50;

/**
* 卖票方式
* synchronized 本质:队列,锁
*/
public synchronized void sale()
if (number > 0)
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余:"+ number);


Lock 接口

java进阶—JUC编程_java

java进阶—JUC编程_JUC_02

ReentrantLock的源码:

java进阶—JUC编程_java_03

公平锁:十分公平,可以先来后到

非公平锁:十分不公平,可以插队(默认)

Lock锁实现线程安全:

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/15
* @description 卖票2
*/
public class SaleTicketDemo02
public static void main(String[] args)
// 真正的多线程开发,公司中的开发
// 线程就是一个单独的资源类,没有任何附属操作
Ticket2 ticket = new Ticket2();

// lambda表达式 () ->
new Thread(() -> for (int i = 0; i < 60; i++) ticket.sale(); , "A").start();
new Thread(() -> for (int i = 0; i < 60; i++) ticket.sale(); , "B").start();
new Thread(() -> for (int i = 0; i < 60; i++) ticket.sale(); , "C").start();




/**
* Lock三部曲
* 1. new ReentrantLock();
* 2. lock.Lock(); // 加锁
* 3. finally => lock.unlock(); // 解锁
*/
class Ticket2
/**属性、方法**/
private int number = 50;

Lock lock = new ReentrantLock();
/**
* 卖票方式
* synchronized 本质:队列,锁
*/
public void sale()
// 加锁
lock.lock();
try
// 业务代码
if (number > 0)
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余:"+ number);

catch (Exception e)
throw new RuntimeException(e);
finally
// 解锁
lock.unlock();


Synchronized 和 Lock 区别

1、Synchronized 内置Java关键字,Lock一个Java类

2、Synchronized 无法判断锁的状态,Lock 可以判断是否获取到了锁

3、Synchronized 会自动释放锁,lock必须要手动释放锁!如果不释放锁,就会产生死锁

4、Synchronized 线程1(获得锁, 阻塞) 和 线程2(等待, 一直等待),Lock锁就不一定会等待(lock.tryLock();尝试获取锁)

5、Synchronized 可重入锁,不可以中断的,非公平的;Lock,可重入锁,可以判断锁,非公平(可以自己设置)

6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码问题

锁是什么, 如何判断锁的是谁?

3、生产者和消费者问题

面试必会: 单例模式、排序算法、生产者和消费者、死锁

生产者和消费者问题 Synchronized 版

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/15
* @description 生产者和消费者测试1
*/
public class Test1
// 线程之间的通信问题:生产者和消费者 等待唤醒和通知唤醒
public static void main(String[] args)
Data data = new Data();

new Thread(() ->
for (int i = 0; i <10; i++)
try
data.increment();
catch (InterruptedException e)
throw new RuntimeException(e);


, "A").start();

new Thread(() ->
for (int i = 0; i <10; i++)
try
data.decrement();
catch (InterruptedException e)
throw new RuntimeException(e);


, "B").start();



/**
* 判断等待, 业务, 通知
*/
class Data

private int number = 0;

/**
* +1
*/
public synchronized void increment() throws InterruptedException
if (number != 0)
// 等待操作
this.wait();

number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我 +1 完毕了
this.notifyAll();


/**
* -1
*/
public synchronized void decrement() throws InterruptedException
if (number == 0)
this.wait();

number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我 -1 完毕了
this.notifyAll();


问题存在, A、B、C、D

存在虚假唤醒

java进阶—JUC编程_java_04

if 改为 while防止虚假唤醒

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/15
* @description 生产者和消费者测试1
*/
public class Test1
// 线程之间的通信问题:生产者和消费者 等待唤醒和通知唤醒
public static void main(String[] args)
Data data = new Data();

new Thread(() ->
for (int i = 0; i <10; i++)
try
data.increment();
catch (InterruptedException e)
throw new RuntimeException(e);


, "A").start();

new Thread(() ->
for (int i = 0; i <10; i++)
try
data.decrement();
catch (InterruptedException e)
throw new RuntimeException(e);


, "B").start();

new Thread(() ->
for (int i = 0; i <10; i++)
try
data.increment();
catch (InterruptedException e)
throw new RuntimeException(e);


, "C").start();

new Thread(() ->
for (int i = 0; i <10; i++)
try
data.decrement();
catch (InterruptedException e)
throw new RuntimeException(e);


, "D").start();



/**
* 判断等待, 业务, 通知
*/
class Data

private int number = 0;

/**
* +1
*/
public synchronized void increment() throws InterruptedException
while (number != 0)
// 等待操作
this.wait();

number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我 +1 完毕了
this.notifyAll();


/**
* -1
*/
public synchronized void decrement() throws InterruptedException
while (number == 0)
this.wait();

number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我 -1 完毕了
this.notifyAll();


JUC版的生产者和消费者问题

通过Lock了解到Condition

java进阶—JUC编程_JUC_05

代码实现:

package com.chen.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/16
* @description 生产者和消费者测试2
*/
public class Test2
// 线程之间的通信问题:生产者和消费者 等待唤醒和通知唤醒
public static void main(String[] args)
Data1 data = new Data1();


new Thread(() ->
for (int i = 0; i < 10; i++)
try
data.increment();
catch (InterruptedException e)
throw new RuntimeException(e);


, "A").start();

new Thread(() ->
for (int i = 0; i < 10; i++)
try
data.decrement();
catch (InterruptedException e)
throw new RuntimeException(e);


, "B").start();

new Thread(() ->
for (int i = 0; i < 10; i++)
try
data.increment();
catch (InterruptedException e)
throw new RuntimeException(e);


, "C").start();

new Thread(() ->
for (int i = 0; i < 10; i++)
try
data.decrement();
catch (InterruptedException e)
throw new RuntimeException(e);


, "D").start();



/**
* 判断等待, 业务, 通知
*/
class Data1

private int number = 0;

Lock lock = new ReentrantLock();

Condition condition = lock.newCondition();



/**
* +1
*/
public void increment() throws InterruptedException
lock.lock();
try

while (number != 0)
// 等待操作
condition.await();

number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我 +1 完毕了
condition.signalAll();
catch (InterruptedException e)
throw new RuntimeException(e);
finally
lock.unlock();



/**
* -1
*/
public void decrement() throws InterruptedException
lock.lock();
try
while (number == 0)
condition.await();

number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我 -1 完毕了
condition.signalAll();
catch (InterruptedException e)
throw new RuntimeException(e);
finally
lock.unlock();



Condition 精准的通知和唤醒线程

代码实现:

package com.chen.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/16
* @description Condition实现精准唤醒和通知
*/
public class Test3
public static void main(String[] args)
Data2 data = new Data2();

new Thread(() ->
for (int i = 0; i < 10; i++)
data.printA();

, "A").start();

new Thread(() ->
for (int i = 0; i < 10; i++)
data.printB();

, "B").start();

new Thread(() ->
for (int i = 0; i < 10; i++)
data.printC();

, "C").start();




/**
* A 执行完调用B, B执行完调用C, C执行完调用A
*/
class Data2


private Lock lock = new ReentrantLock();

private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;

public void printA()
lock.lock();
try
// 业务代码 判断 -> 执行 -> 通知
while (number != 1)
// 等待
condition1.await();

System.out.println(Thread.currentThread().getName() + "AAAAAAA");
// 唤醒B
number = 2;
condition2.signal();
catch (Exception e)
throw new RuntimeException(e);
finally
lock.unlock();




public void printB()
lock.lock();
try
// 业务代码 判断 -> 执行 -> 通知
while (number != 2)
// 等待
condition2.await();

System.out.println(Thread.currentThread().getName() + "BBBBBBB");
number = 3;
condition3.signal();
catch (Exception e)
throw new RuntimeException(e);
finally
lock.unlock();



public void printC()
lock.lock();
try
// 业务代码 判断 -> 执行 -> 通知
while (number != 3)
// 等待
condition3.await();

System.out.println(Thread.currentThread().getName() + "CCCCCCC");
number = 1;
condition1.signal();
catch (Exception e)
throw new RuntimeException(e);
finally
lock.unlock();


4、8锁现象

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/16
* @description 8锁现象的八个问题
* <p>
* 1、标准情况下,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话
* 2、sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话
* </p>
*/
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)
throw new RuntimeException(e);


new Thread(() ->
phone.call();
, "B").start();




class Phone
// synchronized 锁的对象是方法调用者
// 两个方法用的是同一个锁,谁想拿到谁执行
public synchronized void sendSms()
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
throw new RuntimeException(e);

System.out.println("发短信");

public synchronized void call()
System.out.println("打电话");

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/16
* @description 8锁现象解释
* <p>
* 3、增加了一个普通方法,先执行发短信还是Hello 普通方法
* 4、两个对象,两个同步方法 //打电话
* </p>
*/
public class Test2
public static void main(String[] args)
// 两个对象
Phone2 phone = new Phone2();
Phone2 phone2 = new Phone2();

// 锁的存在
new Thread(() ->
phone.sendSms();
, "A").start();

try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
throw new RuntimeException(e);


new Thread(() ->
phone2.call();
, "B").start();




class Phone2
/**
* synchronized 锁的对象是方法调用者
*/
public synchronized void sendSms()
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
throw new RuntimeException(e);

System.out.println("发短信");

public synchronized void call()
System.out.println("打电话");


/**
* 这里没有锁,不是同步方法,不受锁的影响
*/
public void hello()
System.out.println("hello");

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/16
* @description 8锁现象测试
* <p>
* 5、增加两个静态的同步方法, 只有一个对象先打印 发短信? 打电话? // 发短信
* 6、两个对象!增加两个静态的同步方法,先打印 发短信? 打电话? // 发短信
* </p>
*/
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)
throw new RuntimeException(e);


new Thread(() ->
phone2.call();
, "B").start();




class Phone3
/**
* synchronized 锁的对象是方法调用者
* static 静态方法
* 类一加载就有了! 锁的是Class
*/
public static synchronized void sendSms()
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
throw new RuntimeException(e);

System.out.println("发短信");

public static synchronized void call()
System.out.println("打电话");

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/16
* @description 8锁现象解释
* <p>
* 7、一个静态同步方法,一个普通同步方法, 一个对象 先打印 发短信? 打电话? // 打电话
* 8、一个静态同步方法,一个普通同步方法, 两个对象 先打印 发短信? 打电话? // 打电话
* </p>
*/
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)
throw new RuntimeException(e);


new Thread(() ->
phone2.call();
, "B").start();




class Phone4
/**
* 静态的同步方法
* 锁的Class类模板
*/
public static synchronized void sendSms()
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
throw new RuntimeException(e);

System.out.println("发短信");


/**
* 普通的同步方法
* 锁的调用者
*/
public synchronized void call()
System.out.println("打电话");

小结

new this 具体的一个手机

static Class 唯一的一个手机模板

5、集合类不安全

List 不安全

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/16
* @description List不安全
* <p>
* java.util.ConcurrentModificationException 并发修改异常!
* </p>
*/
public class ListTest
public static void main(String[] args)
// 并发下 ArrayList 不安全的
// List<String> list1 = new ArrayList<>();

/**
* 解决方法:
* 1.List<String> list = new Vector<>();
* 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3.List<String> list = new CopyOnWriteArrayList<>();
*/
// CopyOnWrite 写入时复制 COW 计算机程序设计领域的优化策略
// 多个线程调用的时候,list 读取的时候,固定的, 写入(覆盖)
// 写入的时候避免覆盖,造成数据问题
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 不安全

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/17
* @description Set不安全的
* <p>
* 1、java.util.ConcurrentModificationException 并发修改异常!
* 解决方法:
* 1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2、
* </p>
*/
public class SetTest
public static void main(String[] args)
// Set<String> set = new HashSet<>();
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
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();


HashSet 底层是什么?

private transient HashMap<E,Object> map;

public HashSet()
map = new HashMap<>();

// add set 本质就是 map key是无法重复的
public boolean add(E e)
return map.put(e, PRESENT)==null;

HashMap 不安全

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/17
* @description HashMap不安全
* <p>
* java.util.ConcurrentModificationException
* 解决方法:
* 1、Map<String, String> map = new ConcurrentHashMap<>();
* </p>
*/
public class MapTest
public static void main(String[] args)
// map是这样用的吗? 工作中不用HashMap
// 默认等价于什么? new HashMap<>(16, 0.75)
// Map<String, Object> 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();


ConcurrentHashMap的原理

/**
* @throws NullPointerException if the specified key or value is null
*/
public V put(@NotNull K key, @NotNull V value)
return putVal(key, value, false);

6、Callable

java进阶—JUC编程_多线程_06

1、可以有返回值

2、可以抛出异常

3、方法不同 run()/call()

代码测试

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/20
* @description Callable测试类
*/
public class CallableTest
public static void main(String[] args) throws ExecutionException, InterruptedException
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<V>()).start();
// new Thread(new FutureTask<V>( Callable )).start();
new Thread().start(); // 怎么启动Callable

MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask, "A").start();
// 结果会被缓存,效率高
new Thread(futureTask, "B").start();

// 该get方法可能会产生阻塞!把他放在最后、或者使用一步通信来处理
Integer o = (Integer) futureTask.get(); // 获取callable的返回结果
System.out.println(o);



class MyThread implements Callable<Integer>

/**
* Computes a result, or throws an exception if unable to do so
*/
@Override
public Integer call()
System.out.println("call()");
return 1024;

7、常用的辅助类

7.1、CountDownLatch

  • 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
  • A ​​CountDownLatch​​用给定的计数初始化。 ​​await​​方法阻塞,直到由于​​countDown()​​方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的​​await​​ 调用立即返回。 这是一个一次性的现象 - 计数无法重置。 如果您需要重置计数的版本,请考虑使用​​CyclicBarrier​​ 。
  • A ​​CountDownLatch​​是一种通用的同步工具,可用于多种用途。 一个​​CountDownLatch​​为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用​​await​​在门口等待,直到被调用​​countDown()​​的线程打开。 一个​​CountDownLatch​​初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。
  • ​CountDownLatch​​一个有用的属性是,它不要求调用​​countDown​​线程等待计数到达零之前继续,它只是阻止任何线程通过​​await​​,直到所有线程可以通过。
import java.util.concurrent.CountDownLatch;

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/20
* @description CountDownLatch测试
*/
public class CountDownLatchTest
public static void main(String[] args) throws InterruptedException
// 总数是6, 必须要执行任务的时候,再使用!
CountDownLatch downLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++)
new Thread(() ->
System.out.println(Thread.currentThread().getName() + "Go out");
downLatch.countDown(); // 数量 -1
, String.valueOf(i)).start();


downLatch.await(); // 等待计数器归0,然后再向下执行

System.out.println("Close Door");

​downLatch.countDown(); ​​// 数量-1

​downLatch.await();​​ // 等待计数器归0,然后再向下执行

每次有线程调用 downLatch()数量-1,假设计数器变为0, downLatch.await()就会被唤醒,继续执行

7.2、CyclicBarrier

  • 允许一组线程全部等待彼此达到共同屏障点的同步辅助。循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
  • A ​​CyclicBarrier​​支持一个可选的​​Runnable​​命令,每个屏障点运行一次,在派对中的最后一个线程到达之后,但在任何线程释放之前。 在任何一方继续进行之前,此屏障操作对更新共享状态很有用。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/20
* @description CyclicBarrier测试
*/
public class CyclicBarrierTest
public static void main(String[] args)
/**
* 集齐 7 颗龙珠召唤神龙
*/
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 | BrokenBarrierException e)
throw new RuntimeException(e);

).start();


7.3、Semaphore

  • 一个计数信号量。在概念上,信号量维持一组许可证。如果有必要,每个​​acquire()​​都会阻塞,直到许可证可用,然后才能使用它。每个​​release()​​添加许可证,潜在地释放阻塞获取方。但是,没有使用实际的许可证对象;​​Semaphore​​只保留可用数量的计数,并相应地执行。
  • 信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源。
  • 在获得项目之前,每个线程必须从信号量获取许可证,以确保某个项目可用。 当线程完成该项目后,它将返回到池中,并将许可证返回到信号量,允许另一个线程获取该项目。 请注意,当[调用​​acquire()​​时,不会保持同步锁定,因为这将阻止某个项目返回到池中。 信号量封装了限制对池的访问所需的同步,与保持池本身一致性所需的任何同步分开。
  • 此类的构造函数可选择接受公平参数。 当设置为false时,此类不会保证线程获取许可的顺序。 特别是, 闯入是允许的,也就是说,一个线程调用​​acquire()​​可以提前已经等待线程分配的许可证-在等待线程队列的头部逻辑新的线程将自己。 当公平设置为真时,信号量保证调用​​acquire​​方法的线程被选择以按照它们调用这些方法的顺序获得许可(先进先出; FIFO)。 请注意,FIFO排序必须适用于这些方法中的特定内部执行点。 因此,一个线程可以在另一个线程之前调用​​acquire​​ ,但是在另一个线程之后到达排序点,并且类似地从方法返回。 另请注意, 未定义的​​tryAcquire​​方法不符合公平性设置,但将采取任何可用的许可证。
  • 通常,用于控制资源访问的信号量应该被公平地初始化,以确保线程没有被访问资源。 当使用信号量进行其他类型的同步控制时,非正常排序的吞吐量优势往往超过公平性。
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/20
* @description Semaphore测试
*/
public class SemaphoreTest
public static void main(String[] args)
// 线程数量:停车位
Semaphore semaphore = new Semaphore(3);

for (int i = 1; i <= 6; i++)
new Thread(() ->
// acquire() 得到
try
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
catch (InterruptedException e)
throw new RuntimeException(e);
finally
// release() 释放
semaphore.release();

, String.valueOf(i)).start();


​semaphore.acquire();​​获得,假设如果已经满了,等待,等待被释放为止

​semaphore.release();​​释放,会将当前的信号量释放 +1 ,然后唤醒等待的线程

8、读写锁

  • A ​​ReadWriteLock​​维护一对关联的​​locks​​ ,一个用于只读操作,一个用于写入。​​read lock​​可以由多个阅读器线程同时进行,只要没有作者。 ​​write lock​​是独家的。
  • 所有​​ReadWriteLock​​实现必须保证的存储器同步效应​​writeLock​​操作(如在指定​​Lock​​接口)也保持相对于所述相关联的​​readLock​​ 。 也就是说,一个线程成功获取读锁定将会看到在之前发布的写锁定所做的所有更新。
  • 读写锁允许访问共享数据时的并发性高于互斥锁所允许的并发性。 它利用了这样一个事实:一次只有一个线程( 写入线程)可以修改共享数据,在许多情况下,任何数量的线程都可以同时读取数据(因此读取器线程)。 从理论上讲,通过使用读写锁允许的并发性增加将导致性能改进超过使用互斥锁。 实际上,并发性的增加只能在多处理器上完全实现,然后只有在共享数据的访问模式是合适的时才可以。
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/20
* @description ReadWriteLock测试
* <p>
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
* 读-读 可以共存
* 读-写 不能共存
* 写-写 不能共存
* </p>
*/
public class ReadWriteLockTest
public static void main(String[] args)

MyCacheLock myCache = new MyCacheLock();

for (int i = 1; i <= 5; i++)
final int temp = i;
new Thread(() ->
myCache.put(temp + "", temp + "");
, String.valueOf(i)).start();


for (int i = 1; i <= 5; i++)
final int temp = i;
new Thread(() ->
myCache.get(temp + "");
, String.valueOf(i)).start();





/**
* 加锁的
*/
class MyCacheLock
private volatile Map<String, Object> map = new HashMap<>();

/**读写锁,更细粒度的读写锁**/
private ReadWriteLock lock = new ReentrantReadWriteLock();

// 存,写,只希望同时只有一个线程写
public void put(String key, Object value)
lock.writeLock().lock();
try
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完毕");
catch (Exception e)
throw new RuntimeException(e);
finally
lock.writeLock().unlock();



// 取,读,所有人都可以读
public void get(String key)
lock.readLock().lock();
try
System.out.println(Thread.currentThread().getName() + "读取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
catch (Exception e)
throw new RuntimeException(e);
finally
lock.readLock().unlock();




class MyCache
private volatile Map<String, Object> map = new HashMap<>();
// 存,写
public void put(String key, Object value)
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完毕");


// 取,读
public void get(String key)
System.out.println(Thread.currentThread().getName() + "读取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");

9、阻塞队列

阻塞队列:

java进阶—JUC编程_多线程_07

BlockingQueue

java进阶—JUC编程_JUC_08

使用队列:添加、移除

  • 四组API

方式

抛出异常

不会抛出异常,有返回值

阻塞等待

超时等待

添加

add()

offer()

put()

offer(,,)

删除

remove()

poll()

take()

poll(,)

判断队列队头

element()

peek()

-

-

抛出异常:

import java.util.concurrent.ArrayBlockingQueue;

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/20
* @description 测试
*/
public class ArrayBlockingQueue1
public static void main(String[] args)
test1();


/**
* 抛出异常
*/
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"));

// java.lang.IllegalStateException: Queue full 抛出异常!
// System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());

// java.util.NoSuchElementException 抛出异常!
// System.out.println(blockingQueue.remove());

有返回值,不抛出异常:

/**
* 有返回值,不抛出异常
*/
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(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(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); // 等待超过两秒就退出

System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
blockingQueue.poll(2, TimeUnit.SECONDS);

SynchronousQueue 同步队列

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
* @author java小豪
* @version 1.0.0
* @date 2022/12/21
* @description
* <p>
* 同步队列 和其他的BlockingQueue 不一样,SynchronousQueue 不存储元素
* put 一个元素,就必须从里面先take取出来,否则不能在put进去值
* </p>
*/
public class SynchronousQueueTest
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)
throw new RuntimeException(e);

, "T1").start();

new Thread(() ->
try
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " = " +blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "

以上是关于java进阶—JUC编程的主要内容,如果未能解决你的问题,请参考以下文章

Java——聊聊JUC中的volatile与内存屏障

Java——聊聊JUC中的volatile与内存屏障

Java——聊聊JUC中的volatile与内存屏障

Java——聊聊JUC中的volatile与内存屏障

多线程进阶=>JUC并发编程

JUC并发编程 原理之 volatile -- 保证可见性 & 保证有序性 & 习题 balking模式