允许一个或者多个线程去等待其他线程完成操作。
CountDownLatch接收一个int型参数,表示要等待的工作线程的个数。
等于0主线程才往下执行
CountDownLatch countDownLatch = new CountDownLatch(6);
//-1
countDownLatch.countDown();
//等待计数器归零才会向下执行
countDownLatch.await();
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"go out");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待计数器归零才会向下执行
countDownLatch.await();
countDownLatch.countDown();//-1
}
}
有若干个线程,比如说有五个线程,需要它们都到达了某一个点之后才能开始一起执行,也就是说假如其中只有四个线程到达了这个点,还差一个线程没到达,此时这四个线程都会进入等待状态,直到第五个线程也到达了这个点之后,这五个线程才开始一起进行执行状态
等待的线程达到7个,才一起被唤醒最后一个执行的线程执行召唤神龙
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功");
});
cyclicBarrier.await();
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
//集齐七颗龙珠,召唤神龙
//召唤龙珠
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功");
});
for (int i = 0; i < 7; i++) {
final int temp = i;
//lambda不能直接拿到for循环中的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内部并没有真正保存P,而是只保存了P的个数。其次,Semaphore直接复用了AbstractQueueSyncrhonizer框架的共享模式锁,其acquire和release操作直接调用共享模式的AQS加锁和AQS解锁,没有增加其他逻辑,只不过在加锁和解锁的过程中,是把P的个数存入AQS原子整数。
线程中限制次数等待抢占,满了就等待其他线程执行完毕
Semaphore semaphore = new Semaphore(3);
semaphore.acquire();//获得,如果满了,会等待被释放为止
semaphore.release();//释放
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
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) {
e.printStackTrace();
} finally {
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
}
}
再重写的run中进行锁操作
ReentrantLock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
// 睡眠 不建议使用 Thread.sleep(10);
TimeUnit.SECONDS.sleep(10);
// 等待
condition.await();
// 唤醒
condition.signal();
// 唤醒全部
condition.signalAll();
// 锁
reentrantLock.lock();
// 释放
reentrantLock.unlock(); ReentrantLock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
// 等待
condition.await();
// 唤醒
condition.signal();
// 唤醒全部
condition.signalAll();
// 锁
reentrantLock.lock();
// 释放
reentrantLock.unlock();
区别查看 读写锁使用区别
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 读锁
ReentrantReadWriteLock.ReadLock readLock1 = readWriteLock.readLock();
// 写锁
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
解决并发的线程安全问题有两种方式:
1、等待唤醒机制
如果抢不到锁,就将线程挂起,当锁释放的时候,然后将其唤醒重新抢锁。
2、自旋CAS
自旋就是设置循环CAS抢锁的意思,当CAS成功的时候才会退出循环
Atomic原子类就是利用自旋CAS来保证线程安全的。
1、基本常用方法
getAndIncrement() // 原子化 i++
getAndDecrement() // 原子化的 i--
incrementAndGet() // 原子化的 ++i
decrementAndGet() // 原子化的 --i
// 当前值 +=delta,返回 += 前的值
getAndAdd(delta)
// 当前值 +=delta,返回 += 后的值
addAndGet(delta)
//CAS 操作,返回是否成功
compareAndSet(expect, update)
// 以下四个方法
// 新值可以通过传入 func 函数来计算
getAndUpdate(func)
updateAndGet(func)
getAndAccumulate(x,func)
accumulateAndGet(x,func)
2、对象引用类型
User user = new User("jaychou",24);
AtomicReference<User> userAtomicReference = new AtomicReference<>(user);
while (true){
User updateUser = new User("jay",22);
boolean flag = userAtomicReference.compareAndSet(userAtomicReference.get(),updateUser);
if (flag){
System.out.println(userAtomicReference.get());
break;
}
}
AtomicStampedReference
AtomicReference有一个缺点,不能解决ABA问题。
ABA问题就是多个线程前后修改值,导致线程CAS前后值没有变化,但是中间却发生了修改。
AtomicStampedReference通过引入时间戳来解决了ABA问题。每次要更新值的时候,需要额外传入oldStamp和newStamp。将对象和stamp包装成了一个Pair对象。
User user = new User("jaychou",24);
AtomicStampedReference<User> userAtomicStampedReference = new AtomicStampedReference<>(user,1);
while (true){
User user1 = new User("jay",222);
int oldStamp1 = userAtomicStampedReference.getStamp();
int[] stamp = new int[1];
User oldUser = userAtomicStampedReference.get(stamp);
boolean flag = userAtomicStampedReference.compareAndSet(oldUser,user1,stamp[0],stamp[0]+1);
if (flag){
break;
}
}
int[] s = new int[1];
System.out.println(userAtomicStampedReference.get(s));
System.out.println(s[0]);
AtomicMarkableReference是无法解决ABA问题的,因为boolean变量的mark是有很大可能重合的,还是会导致更新成功。
3、Atomic数组 Atomic数组主要有AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray.
AtomicIntegerArray integerArray = new AtomicIntegerArray(10);//10为数组长度
while (true){
boolean flag = integerArray.compareAndSet(0,integerArray.get(0),2);
if (flag){
System.out.println(integerArray.get(0)+"---"+flag);
break;
}
}
AtomicReferenceArray<User> referenceArray = new AtomicReferenceArray<>(10);
while (true){
boolean flag2 = referenceArray.compareAndSet(0,referenceArray.get(0),new User("jaychou",22));
if (flag2){
System.out.println(referenceArray.get(0)+"---"+flag2);
break;
}
}
4、对象属性原子更新器 有三类:AtomicIntegerFieldUpdater(修改对象中的)、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
User user = new User("jaychou",22);
//初始化参数分别为:对应对应的类,属性对应的类,属性的名字
AtomicReferenceFieldUpdater fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class,String.class,"name");
//初始化参数分别为:对应对应的类,属性的名字。属性对应的类可以忽略,因为类名中已经记录了
AtomicIntegerFieldUpdater integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
fieldUpdater.compareAndSet(user,user.name,"666");
integerFieldUpdater.compareAndSet(user,user.age,1000);
System.out.println(user);
class User{
int age;
volatile String name;
public User(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
Java中提供了几类原子操作类,通过自旋+CAS来解决线程不安全问题。
1、AtomicLong、AtomicInteger、AtomicBoolean
这些类实现了++,–,+delta的原子操作。
2、AtomicReference
AtomicReference的作用是引用类型的原子修改,保证引用的修改不会出现线程问题。
并且通过AtomicStampedReference解决了ABA问题。
3、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
这个就是数组类型,和单独的对象操作基本一致,只不过在设置的时候需要填入下标罢了。
4、AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdater、AtomicLongFieldUpdater
AtomicReference是修改引用,而AtomicReferenceFieldUpdater这三个类是用来修改实例对象中的属性的值的。
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater是AtomicReferenceFieldUpdater的一个特殊例子,是用来专门分别修改int和long属性的变量的。
出以上被修改变量必须用volatile修饰
1.Sychronized 内置的java关键字,Lock锁是一个java类
2.Sychronized 无法判断获取锁的状态,Lock锁可以判断是否获取到了锁.
3.Sychronized 会自动释放锁lock必须手动释放锁,如果不释放锁,死锁
4.Sychronized 线程一(获得锁,阻塞),线程二(等待,傻傻的等),Lock锁就不一定会等待下去.
5.Sychronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以中断锁,非公平(可以自己设置)
6.Sychronized 适合锁少量的代码的同步问题,Lock适合锁大量的代码同步问题.
private native void start0();
//本地方法,调用底层c++,java运行在虚拟机之上,无法直接操作硬件,由c++开启多线程
public enum State {
//就绪
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
来自不同的类wait=》Object sleep=》Thread
wait释放锁,sleep抱着锁睡觉
wait必须在同步代码快中,sleep可以在任何地方睡觉
wait不需要捕获异常,sleep需要捕获异常(可能发生超时等待)
volatile是java虚拟机提供的轻量级的同步机制
1.保证可见性
2.不保证原子性
3.由于内存屏障,禁止指令重排
什么是JMM
JMM:java的内存模型,不存在的东西,概念,约定
关于JMM的一些同步的约定:
1.线程解锁前,必须把共享变量立刻刷回主存
2.线程枷锁前,必须读取主存中的最新值到工作的内存中
3.加锁和解锁是同一把锁
线程:工作内存 ,主内存
也就是取使用这个变量的时候,就会去主内存下刷一下值 要结合锁去使用
不是线程安全,一般用于原子性操作 工作流程JMM 取值到线程自己的工作空间 原子性操作被volatile修饰加锁时候是安全的
原子性操作下加锁安全 列入单例下不加关键字的话就会出现原本有的话 但是工作空间没有 就会新创建对象赋值 就不能达到单例的效果
class Singleton {
// 不是一个原子性操作
//private static Singleton instance;
//改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
private static volatile Singleton instance;
// 构造器私有化
private Singleton() {
}
// 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时保证了效率, 推荐使用
//如果不加关键字 那么 线程a中的和线程b中通过jmm刷内存 a赋值操作了 但是b没有去刷新内存 所以b是空的 又创建一次对象赋值操作
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
证明非原子性操作不是线程安全
public class Test {
// volatile不保证原子性
// 原子性:保证数据一致性、完整性
volatile int number = 0;
public void addPlusPlus() {
number++;
}
public static void main(String[] args) {
Test volatileAtomDemo = new Test();
for (int j = 0; j < 20; j++) {
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
volatileAtomDemo.addPlusPlus();
}
}, String.valueOf(j)).start();
}// 后台默认两个线程:一个是main线程,一个是gc线程
System.out.println(Thread.currentThread().getName() +
" final number result = " + volatileAtomDemo.number);
}
}
包名首字母 java.util.concurrent
当前前程只能获取当前线程的值,内部有个ThreadLocalMap
应用场景:
1、每个线程需要有自己单独的实例
2、实例需要在多个方法中共享,但不希望被多线程共享