day12【线程安全解决synchronized,并发包】
第一章 synchronized关键字
1.1 多行代码的原子性问题
讲解
-
之前的AtomicInteger类只能保证"变量"的原子性操作,而对多行代码进行"原子性"操作,使用AtomicInteger类就不能达到效果了。
-
我们通过一个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟。
模拟票:
public class MyRunnable implements Runnable {
int tickets = 100;// 4个窗口共同卖的票 共享变量
@Override
public void run() {
// 实现卖票的操作
// 死循环卖票
while (true){
// 当票卖完了,就结束
if (tickets < 1){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":正在出售第"+tickets+"张票");
tickets--;
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
/*
多行代码的原子性问题:
通过案例演示该问题: 电影院4个窗口卖票,卖的是同一份票,这份票总共有100张票
分析:
1. 电影院4个窗口 相当于 4条线程
2. 电影院4个窗口 卖票的操作是一样 相当于每条线程的任务是一样的
*/
// 电影院4个窗口 去卖票
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"窗口1");
Thread t2 = new Thread(mr,"窗口2");
Thread t3 = new Thread(mr,"窗口3");
Thread t4 = new Thread(mr,"窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
程序执行后,结果会出现的问题
发现程序出现了两个问题:
- 相同的票数,比如100这张票被卖了四回。
- 不存在的票,比如0票与-1票,-2票,是不存在的。
这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。
卖票案例问题分析:
1.2 synchronized关键字概述
-
synchronized关键字:表示“同步”的。它可以对“多行代码”进行“同步”——将多行代码当成是一个完整的整体,一个线程如果进入到这个代码块中,会全部执行完毕,执行结束后,其它线程才会执行。这样可以保证这多行的代码作为完整的整体,被一个线程完整的执行完毕。
-
synchronized被称为“重量级的锁”方式,也是“悲观锁”——效率比较低。
-
synchronized有几种使用方式:
a).同步代码块b).同步方法【常用】
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
根据案例简述:
窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
1.3 同步代码块
- 同步代码块:
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象 可以是任意类型。
- 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
使用同步代码块解决代码:
public class MyRunnable implements Runnable {
int tickets = 100;// 4个窗口共同卖的票 共享变量
@Override
public void run() {
// 实现卖票的操作
// 死循环卖票
while (true) {
// 当票卖完了,就结束
// 加锁
synchronized (this) {// mr钥匙
if (tickets < 1) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
tickets--;
}
// 释放锁
}
}
}
public class Test {
//static Object lock = new Object();
public static void main(String[] args) {
/*
解决多行代码的原子性问题:
同步代码块:
概述:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(锁对象){
}
锁对象:
1.锁对象可以是任意类的对象
2.多条线程想要实现同步,那么锁对象必须一致
*/
// 电影院4个窗口 去卖票
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"窗口1");
Thread t2 = new Thread(mr,"窗口2");
Thread t3 = new Thread(mr,"窗口3");
Thread t4 = new Thread(mr,"窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
/*
发现程序出现了两个问题:
1. 相同的票数,比如100这张票被卖了四回。
2. 不存在的票,比如0票与-1票,-2票,是不存在的。
*/
}
}
当使用了同步代码块后,上述的线程的安全问题,解决了。
1.4 同步方法
讲解
- 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
使用同步方法代码如下:
public class MyRunnable implements Runnable {
int tickets = 100;// 4个窗口共同卖的票 共享变量
@Override
public void run() {
// 实现卖票的操作
// 死循环卖票
while (true) {
// 当票卖完了,就结束
// 加锁
if (sellTickets()) break;
// 释放锁
}
}
private synchronized boolean sellTickets() {
if (tickets < 1) {
return true;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
tickets--;
return false;
}
}
public class Test {
//static Object lock = new Object();
public static void main(String[] args) {
/*
解决多行代码的原子性问题:
同步方法:
概述:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
修饰符 synchronized 返回值类型 方法名(参数列表){}
锁对象:
1.非静态同步方法: 锁对象是this
2.静态同步方法: 锁对象是该方法所在类的字节码文件对象 类名.class
线程A中使用的是同步代码块,线程B中使用的是同步方法,线程A和线程B要实现同步,
那么线程A中的同步代码块锁对象和线程B中的同步方法的锁对象要一致,否则锁不住
*/
// 电影院4个窗口 去卖票
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"窗口1");
Thread t2 = new Thread(mr,"窗口2");
Thread t3 = new Thread(mr,"窗口3");
Thread t4 = new Thread(mr,"窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
/*
发现程序出现了两个问题:
1. 相同的票数,比如100这张票被卖了四回。
2. 不存在的票,比如0票与-1票,-2票,是不存在的。
*/
}
}
1.5 Lock锁
讲解
java.util.concurrent.locks.Lock
机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock()
:加同步锁。public void unlock()
:释放同步锁。
使用如下:
public class MyRunnable implements Runnable {
int tickets = 100;// 4个窗口共同卖的票 共享变量
Lock lock = new ReentrantLock();
@Override
public void run() {
// 实现卖票的操作
// 死循环卖票
while (true) {
// 当票卖完了,就结束
// 加锁
lock.lock();
if (tickets < 1) {// 窗口1 0
lock.unlock();
break;// 结束卖票
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
tickets--;
// 释放锁
lock.unlock();
}
}
}
第二章 并发包
2.1 CopyOnWriteArrayList
ArrayList的线程不安全:
- 定义线程类:
public class MyThread extends Thread {
public static List<Integer> list = new ArrayList<>();//线程不安全的
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
System.out.println("添加完毕!");
}
}
- 定义测试类:
public class Demo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("最终集合的长度:" + MyThread.list.size());
}
}
最终结果可能会抛异常,或者最终集合大小是不正确的。
CopyOnWriteArrayList是线程安全的:
- 定义线程类:
public class MyThread extends Thread {
// public static List<Integer> list = new ArrayList<>();//线程不安全的
//改用:线程安全的List集合:
public static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
System.out.println("添加完毕!");
}
}
- 测试类:
public class Demo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("最终集合的长度:" + MyThread.list.size());
}
}
结果始终是正确的。
2.2 CopyOnWriteArraySet
HashSet仍然是线程不安全的:
- 线程类:
public class MyThread extends Thread {
public static Set<Integer> set = new HashSet<>();//线程不安全的
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
set.add(i);
}
System.out.println("添加完毕!");
}
}
- 测试类:
public class Demo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.start();
//主线程也添加10000个
for (int i = 10000; i < 20000; i++) {
MyThread.set.add(i);
}
Thread.sleep(1000 * 3);
System.out.println("最终集合的长度:" + MyThread.set.size());
}
}
最终结果可能会抛异常,也可能最终的长度是错误的!!
CopyOnWriteArraySet是线程安全的:
- 线程类:
public class MyThread extends Thread {
// public static Set<Integer> set = new HashSet<>();//线程不安全的
//改用:线程安全的Set集合:
public static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
set.add(i);
}
System.out.println("添加完毕!");
}
}
- 测试类:
public class Demo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.start();
//主线程也添加10000个
for (int i = 10000; i < 20000; i++) {
MyThread.set.add(i);
}
Thread.sleep(1000 * 3);
System.out.println("最终集合的长度:" + MyThread.set.size());
}
}
可以看到结果总是正确的!!
2.3 ConcurrentHashMap
HashMap是线程不安全的。
- 线程类:
public class MyRunnable implements Runnable {
// HashMap线程不安全
HashMap<Integer,Integer> map = new HashMap<>();// 2条线程共享的map集合
// Hashtable线程安全
//Hashtable<Integer,Integer> map = new Hashtable<>();// 2条线程共享的map集合
// ConcurrentHashMap线程安全
//ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();// 2条线程共享的map集合
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
map.put(i,i);
}
System.out.println("添加完毕");
}
}
- 测试类:
public class Test {
public static void main(String[] args) throws InterruptedException {
/*
ConcurrentHashMap:
- 演示HashMap线程不安全
- 演示Hashtable线程安全
- 演示ConcurrentHashMap线程安全
案例: 线程1对HashMap集合添加1000个键值对,线程2也对HashMap集合添加1000个键值对
*/
// 创建MyRunnable任务对象
MyRunnable mr = new MyRunnable();
// 创建2条线程执行任务
Thread t1 = new Thread(mr);
t1.start();
Thread t2 = new Thread(mr);
t2.start();
Thread.sleep(2000);
System.out.println("最终map集合键值对的个数:"+mr.map.size());
/*
根据分析,共享的map集合中的键值对个数应该是1000个,但实际运行的结果是大于1000的
*/
}
}
运行结果可能会出现异常、或者结果不准确!!
Hashtable是线程安全的,但效率低:
我们改用JDK提供的一个早期的线程安全的Hashtable类来改写此例,注意:我们加入了"计时"。
- 线程类:
public class MyRunnable implements Runnable {
// Hashtable线程安全
Hashtable<Integer,Integer> map = new Hashtable<>();// 2条线程共享的map集合
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
map.put(i,i);
}
System.out.println("添加完毕");
}
}
- 测试类:
public class Test {
public static void main(String[] args) throws InterruptedException {
// 创建MyRunnable任务对象
MyRunnable mr = new MyRunnable();
// 创建1000条线程
for (int i = 0; i < 1000; i++) {
Thread t = new Thread(mr);
t.start();
}
Thread.sleep(5000);
System.out.println("最终map集合键值对的个数:"+mr.map.size());
/*
根据分析,共享的map集合中的键值对个数应该是1000个,但实际运行的结果是大于1000的
*/
}
}
能看到结果是正确的,但耗时较长。
改用ConcurrentHashMap
- 线程类:
public class MyRunnable implements Runnable {
// ConcurrentHashMap线程安全
ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();// 2条线程共享的map集合
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
map.put(i,i);
}
System.out.println("添加完毕");
}
}
- 测试类:
public class Test {
public static void main(String[] args) throws InterruptedException {
// 创建MyRunnable任务对象
MyRunnable mr = new MyRunnable();
// 创建1000条线程
for (int i = 0; i < 1000; i++) {
Thread t = new Thread(mr);
t.start();
}
Thread.sleep(5000);
System.out.println("最终map集合键值对的个数:"+mr.map.size());
/*
根据分析,共享的map集合中的键值对个数应该是1000个,但实际运行的结果是大于1000的
*/
}
}
可以看到效率提高了很多!!!
- HashTable效率低下原因:
public synchronized V put(K key, V value)
public synchronized V get(Object key)
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。
2.4 CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作。
例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行。
CountDownLatch构造方法:
public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象
CountDownLatch重要方法:
public void await() throws InterruptedException// 让当前线程等待
public void countDown() // 计数器进行减1
- 示例
1). 制作线程1:
public class MyRunnable1 implements Runnable {
CountDownLatch cdl;
public MyRunnable1(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
System.out.println("A");
// 暂停,等待线程2执行打印B,执行完回到这里来执行打印C
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C");// 打印C之前一定要去执行打印B
}
}
2). 制作线程2:
public class MyRunnable2 implements Runnable {
CountDownLatch cdl;
public MyRunnable2(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
System.out.println("B");
// 计数器-1
cdl.countDown();// 计数器的值为0
}
}
3).制作测试类:
public class Test {
public static void main(String[] args) throws InterruptedException {
/*
CountDownLatch:
作用: 允许一个或多个线程等待其他线程完成操作。
常用方法:
public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象
public void await() throws InterruptedException// 让当前线程等待
public void countDown() // 计数器进行减1
案例演示:
线程1的任务是打印A和C,线程2的任务是打印B,要求打印B一定要在打印C的前面
*/
CountDownLatch cdl = new CountDownLatch(1);
// 创建2条线程,执行任务
MyRunnable1 mr1 = new MyRunnable1(cdl);
Thread t1 = new Thread(mr1);
t1.start();
MyRunnable2 mr2 = new MyRunnable2(cdl);
Thread t2 = new Thread(mr2);
t2.start();//
}
}
4). 执行结果:
会保证按:A B C的顺序打印。
说明:
CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉。
CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch。
await()方法的线程阻塞状态解除,继续执行。
2.5 CyclicBarrier
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
例如:公司召集5名员工开会,等5名员工都到了,会议开始。
我们创建5个员工线程,1个开会线程,几乎同时启动,使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。
CyclicBarrier构造方法:
public CyclicBarrier(int parties, Runnable barrierAction
//parties: 代表要达到屏障的线程数量
//barrierAction:表示达到屏障后要执行的线程
CyclicBarrier重要方法:
public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
- 示例代码:
1). 制作员工线程:
public class MyRunnable implements Runnable {
CyclicBarrier cb;
public MyRunnable(CyclicBarrier cb) {
this.cb = cb;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":到会议室了...");
// 当前线程到达会议室(屏障),暂停
try {
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":离开会议室");
}
}
2). 制作开会线程:
public class MeetingRunnable implements Runnable {
@Override
public void run() {
System.out.println("会议开始,会议的内容是...");
}
}
3). 制作测试类:
public class Test {
public static void main(String[] args) {
/*
CyclicBarrier类:
作用:它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,
所有被屏障拦截的线程才会继续运行。
常用方法:
public CyclicBarrier(int parties, Runnable barrierAction)
parties: 代表要达到屏障的线程数量
barrierAction:表示达到屏障后要执行的线程
public int await() 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
案例演示:
例如:公司召集5名员工开会,等5名员工都到了,会议开始
*/
// 创建CyclicBarrier对象
CyclicBarrier cb = new CyclicBarrier(5,new MeetingRunnable());
MyRunnable mr = new MyRunnable(cb);
new Thread(mr,"员工1").start();
new Thread(mr,"员工2").start();
new Thread(mr,"员工3").start();
new Thread(mr,"员工4").start();
new Thread(mr,"员工5").start();
}
}
4). 执行结果:
使用场景
使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。
需求:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。
2.6 Semaphore
Semaphore的主要作用是控制线程的并发数量。
synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore可以设置同时允许几个线程执行。
Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
Semaphore构造方法:
public Semaphore(int permits) permits 表示许可线程的数量
Semaphore重要方法:
public void acquire() throws InterruptedException 表示获取许可
public void release() release() 表示释放许可
- 示例一:同时允许1个线程执行
1). 制作一个ClassRoom类:
public class ClassRoom {
// 创建Semaphore对象,指定线程的并发数量是1
Semaphore sp = new Semaphore(2);
public void into(){
// 获得许可证,才进来了
try {
sp.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":进来了...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 出去了,就释放许可
System.out.println(Thread.currentThread().getName()+":出去了...");
sp.release();
}
}
2). 制作线程类:
public class MyRunnable implements Runnable {
ClassRoom cr;
public MyRunnable(ClassRoom cr) {
this.cr = cr;
}
@Override
public void run() {
cr.into();// 任务就是进入教室
}
}
3). 测试类:
public class Test {
public static void main(String[] args) {
/*
Semaphore类:
作用: 控制线程的并发数量。
常用方法:
public Semaphore(int permits) permits 表示许可线程的数量
public void acquire() throws InterruptedException 表示获取许可
public void release() release() 表示释放许可
案例演示:
假设有个教室,只能容纳3个人,这个时候有5个人需要进来
*/
ClassRoom cr = new ClassRoom();
// 创建5条线程,执行任务
MyRunnable mr = new MyRunnable(cr);
new Thread(mr,"1号").start();
new Thread(mr,"2号").start();
new Thread(mr,"3号").start();
new Thread(mr,"4号").start();
new Thread(mr,"5号").start();
}
}
2.7 Exchanger
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
A线程 exchange方法 把数据传递B线程
B线程 exchange方法 把数据传递A线程
Exchanger构造方法:
public Exchanger()
Exchanger重要方法:
public V exchange(V x)
- 示例一
public class MyRunnable1 implements Runnable {
Exchanger<String> ex;
public MyRunnable1(Exchanger<String> ex) {
this.ex = ex;
}
@Override
public void run() {
// 线程1 传递数据给 线程2
try {
// 线程1,把"信息1"传递给线程2
String message2 = ex.exchange("信息1");
System.out.println("线程2 传递给 线程1的信息是:"+message2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyRunnable2 implements Runnable {
Exchanger<String> ex;
public MyRunnable2(Exchanger<String> ex) {
this.ex = ex;
}
@Override
public void run() {
// 线程2 传递数据给 线程1
try {
String message1 = ex.exchange("信息2");
System.out.println("线程1 传递给 线程2的信息:"+message1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2). 制作main()方法:
public class Test {
public static void main(String[] args) {
/*
Exchanger类:
作用:是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
常用方法:
public Exchanger()
public V exchange(V x) 参数就表示当前线程需要传递的数据,返回值是其他线程传递过来的数据
案例演示:
*/
Exchanger<String> ex = new Exchanger<>();
MyRunnable1 mr1 = new MyRunnable1(ex);
new Thread(mr1).start();
MyRunnable2 mr2 = new MyRunnable2(ex);
new Thread(mr2).start();
}
}
使用场景:可以做数据校对工作
需求:比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,
并对两个文件数据进行校对,看看是否录入一致,
总结
- 能够使用同步代码块解决线程安全问题
使用场景: 如果线程中某一段代码需要实现互斥的效果,就可以使用同步代码块对这段代码进行加锁
格式:
synchronized (锁对象){
// 实现互斥的代码\需要加锁的代码
}
锁对象:
1.锁对象可以是任意对象
2.如果多条线程要实现同步,锁对象必须是唯一的(多条线程的锁对象要一致) - 能够使用同步方法解决线程安全问题
使用场景: 如果线程中某个方法中的所有代码需要实现互斥的效果,就可以使用同步方法对这个方法中的所有代码进行加锁
格式:
方法的返回值类型前面加上 synchronized
锁对象:
1.非静态同步方法: 锁对象this
2.静态同步方法: 锁对象是该方法所在的类的字节码对象 类名.class - 能够说明volatile关键字和synchronized关键字的区别
volatile关键字: 保证可见性 多个线程可以一起操作,但是对共享变量操作后,其他线程是可见的
synchronized关键字: 加锁 一个线程在操作其他线程得等着 - 能够描述ConcurrentHashMap类的作用
线程安全,效率相对高 - 能够描述CountDownLatch类的作用
允许一个或多个线程等待其他线程完成操作。 - 能够描述CyclicBarrier类的作用
它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。 - 能够表述Semaphore类的作用
控制线程的并发数量。 - 能够描述Exchanger类的作用
线程间的数据交换
必须练习—>同步代码块,同步方法,Lock锁
理解做好笔记—> 并发包中的类