文章目录
为什么要使用同步??
java允许多线程并发控制,当多线程同时操作一个可共享的资源变量时,将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程调用,从而保证了该变量的唯一性和准确性
使用特殊变量volitale实现线程同步
a.volatile关键字为域变量的访问提供一种免锁机制
b.使用volatile修饰域相当于告诉虚拟机该域可能被其他现象更新
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
使用ThreadLocal局部变量实现线程
如果使用ThreadLocal管理变量,则每一个使用变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
使用原子变量实现线程同步
需要使用线程同步的根本原因在于对普通变量的操作不是原子的。
那么什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,
使用该类可以简化线程同步。
重入锁
)
synchronized
ReentranLock
重入锁可以完全替代
synchronized
关键字,在JDK5的早期版本中,重入锁的性能远远好于synchronized
,但从JDK6开始,JDK在Synchronized上做了大量的优化,使得二者的性能差距并不大。
读写锁
读写锁分离可以有效的帮助减少锁竞争,以提升系统性能。
基于AQS的一些同步器
倒计时器 CountDownLatch
这个工具类通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。
package Multithread.Test;
import java.util.concurrent.CountDownLatch;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/5/5 0005 17:09
* 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助,‘
* 定义一个倒计时器 只有当其他线程中执行的操作完成 这个线程才能继续执行否则 这个线程一直保持一种阻塞的状态
* 原理:
* countDownLatch.countDown() //数量-1
* countDownLatch.await() //等待技术器归零,然后再向下执行
* 每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行!
*
* 这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行
*
* 一个线程只有等其他的准备工作做完了才呢个执行 卡在 await()上
*/
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
for(int i=0;i<5;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"go out");//
countDownLatch.countDown();
System.out.println(123);
}).start();
}
//在这里设了一到们 ,只有当倒计时为0时才能执行后虚的操作
//主线程在countDownLatch上等待,当所有检查任务全部执行完成后,主线程方能继续执行
countDownLatch.await();
System.out.println("倒计时完成");
}
}
循环栅栏 CylicBarrier
package Multithread.Test;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/5/5 0005 17:24
* 允许一组线程全部等待彼此达到共同屏障点的同步辅助。
* 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。
* 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
*/
public class TestCyclicBarrier {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
System.out.println("召唤神龙成功");
});
for (int i=0;i<6;i++){
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"执行了");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("主线程执行");
}
}
信号量 Semphore:允许多个线程同时访问
信号量为多线程协作提供了更为强大的控制方法,广义上来是哦,信号量是对锁的扩展。无论是内部锁synchronized还是重入锁ReentranLock,一次都只允许一个线程范根同一个资源,而信号量却可以指定多个线程,同时访问某一资源,信号量主要提供了以下构造函数:
public Semaphore(int permits)
public Semphore(int permits,boolean fair)
在构造信号量对象时,必须要指定信号量的准入数,即同时能申请多少个许可,每个线程每次只能申请许可证
package Multithread.Test;
import java.util.concurrent.Semaphore;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/5/5 0005 17:37
* 一个信号及数量。在概念上,信号量是一组许可证。
* 当信号不够用的时候,每个acquire()都会阻塞,直到许可证可用,然后才能使用它
*
* 原理:
* semaphore.acquire() 获得,假设如果已经满了,等待,等待被释放为止
* semaphore.release() 释放,会将当前的信号量释放+1,然后唤醒等待的线程
* 作用:多个共享资源互斥的使用,并发限流,控制最大的线程数!
*/
public class TestSemphore {
public static void main(String[] args) {
//3个信号量 ,6个线程来抢
Semaphore semaphore = new Semaphore(3);
for(int i=0;i<6;i++){
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"获得了资源");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"执行完毕释放了资源");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
通过阻塞队列
线程安全的一些类/容器
除了以上并发包中专有数据结构外,java.util下的Vector是线程安全的,另外Collection工具类可以帮助我们将任意集合包装成线程安全的集合。
- ConcurrentHashMap:高效的HashMap
- CopyOnWriteArrayList
- ConcurrentLinkedQueue:高效的并发队列,使用链表实现
- Blocking:这是一个接口,JDK内部通过链表,数组等方式实现了这个接口,表示阻塞队列,非常适用于作为数据共享通道