- 1,JUC的简介
- 2,volatile关键字与内存可见性
- 3,CAS算法
- 4,同步容器类ConcurrentHashMap
- 5,CountDownLatch闭锁
- 6,Callable接口
- 7,同步锁Lock
- 8,Condition 控制通信
- 9,生产者消费者案例
JUC的简介
在Java5.0提供了java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。
volatile关键字与内存可见性
-
内存可见性问题,当多个线程操作共享数据时,彼此不可见
-
volatile关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见(效率也低,但比synchronized的效率高,相较于synchronized是一种较为轻量级的同步策略)jvm底层有重排序,用volatile修饰后就不可重排序了,使用代码如下:
注意:
1,volatile不具备“互斥性”
2,volatile不能保证质量的“原子性”
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo).start();
//运行,发现flag=true并没有进来,解决方法一:加锁,效率低
while (true){
if(threadDemo.isFlag()){
System.out.println("----进来了-----");
break;
}
}
}
}
class ThreadDemo implements Runnable{
//解决方法二:加volatile关键词
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag =true;
System.out.println("flag = "+isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
CAS算法
-
CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问,保证数据的原子性。
-
CAS 是一种无锁的非阻塞算法的实现。
-
CAS 包含了 3 个操作数:
1,需要读写的内存值 V
2,进行比较的预估值 A
3, 拟写入的更新值 B -
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。代码实现如下:
public static void main(String[] args) {
AtomicDemo atomicDemo = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(atomicDemo).start();
}
}
}
class AtomicDemo implements Runnable {
//使用volatile不可解决
// private volatile int number = 0;
//利用原子变量解决(和包装类有点相似)
private AtomicInteger number = new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getNumber());
}
public int getNumber() {
return number.getAndIncrement();
}
- 模拟cas算法
public class JucTest {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int expectedValue = cas.get();
boolean a = cas.compareAndSet(expectedValue,(int)(Math.random()*101));
System.out.println(a);
}
}).start();
}
}
}
class CompareAndSwap{
private int value;
//获取内存值
public synchronized int get(){
return value;
}
//比较,expectedValue预估值
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;
if (oldValue == expectedValue){
this.value = newValue;
}
return oldValue;
}
//设置
public synchronized boolean compareAndSet(int expectedValue, int newValue){
return expectedValue == compareAndSwap(expectedValue,newValue);
}
}
同步容器类ConcurrentHashMap
-
ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对与多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用“锁分段”机制替代 Hashtable 的独占锁。进而提高性能
-
ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList
-
CopyOnWriteArrayList/CopyOnWriteArraySet:”写入并复制“
注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择,代码实现如下:
public static void main(String[] args) {
TestThread th = new TestThread();
for (int i = 0; i < 10; i++) {
new Thread(th).start();
}
}
class TestThread implements Runnable {
//java.util.ConcurrentModificationException
// private static List<String> list = Collections.synchronizedList(new ArrayList<>());
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static {
list.add("aa");
list.add("bb");
list.add("cc");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
list.add("-aa-");
}
}
}
CountDownLatch闭锁
- CountDownLatch:闭锁,在完成某些运算时,只有其他所有线程运算全部完成,当前运算才能执行,代码如下:
1, 确保某个计算在其需要的所有资源都被初始化之后才继续执行。
2, 确保某个服务在其依赖的所有其他服务都已经启动之后才启动。
3,等待直到某个操作所有参与者都准备就绪再继续执行。
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(5);
LatchDemo ld = new LatchDemo(latch);
Instant startTime = Instant.now();
for (int i = 0; i < 5; i++) {
new Thread(ld).start();
}
try {
latch.await();
} catch (InterruptedException e) {
}
Instant endTime = Instant.now();
System.out.println("耗费时间为:" + Duration.between(startTime, endTime).toMillis() + "毫秒");
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
synchronized (this) {
try {
for (int i = 0; i < 50000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();
}
}
}
}
Callable接口
- 实现多线程的第三种方式:Callable 接口,Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常。Callable 需要依赖FutureTask ,FutureTask 也可以用作闭锁。
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
//1,执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
Integer sum = null;
//2,接收线程运算后的结果
try {
sum = result.get(); //FutureTask可用于闭锁
System.out.println(sum);
System.out.println("======================");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += 1;
}
return sum;
}
}
同步锁Lock
用于解决多线程安全问题的方式:
1,同步代码块(synchronized:隐式锁)
2,同步方法(synchronized:隐式锁)
3,同步锁Lock(注意:是一个显示锁,需要通过lock()方法上锁,必须通过unlock()方法进行释放锁),代码如下:
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"12306").start();
new Thread(ticket,"飞猪").start();
new Thread(ticket,"携程").start();
}
}
class Ticket implements Runnable{
private int tick = 100;//总票数
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();//加锁
try {
if (tick > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"完成售票,余票为:"+ --tick);
}else {
System.out.println("票已全部售完");
break;
}
}finally {
lock.unlock();
}
}
}
}
Condition 控制通信
-
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
-
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll。
-
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,请使用其 newCondition() 方法。代码在下面生产者消费者案例中体现:
生产者消费者案例
- 为了避免虚假唤醒问题,应该总是使用在循环中,代码如下:
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生产者AA").start();
new Thread(productor, "生产者BB").start();
new Thread(consumer, "消费者C").start();
new Thread(consumer, "消费者D").start();
}
}
//店员
class Clerk {
private int product = 0;//数量
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//进货
public void get() {
lock.lock();
try {
while (product >= 1) {
System.out.println("商品已满!");
try {
condition.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + ++product);
condition.signalAll();
this.notifyAll();
} finally {
lock.unlock();
}
}
//卖货
public void sale() {
lock.lock();
try {
while (product <= 0) {
System.out.println("暂无货物!");
try {
condition.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + --product);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
//生产者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.get();
}
}
}
//消费者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}