day12【线程安全解决、并发包】
反馈和复习
1.原子类不太懂(CAS机制)
2.建议早上把课程讲完,下午自己有时间可以复习
今日内容
1.Synchronized关键字【重点】
2.高并发下JDK提供一堆的线程安全有关类【理解】
第一章 synchronized关键字【重点】
1.1 AtomicInteger的不足之处
回顾: AtomicInteger能解决什么问题?
可以保证对"变量"操作(比如++,--)是原子性(有序的,可见性)
无法解决: 多行代码的原子性
System.out.println("A");
System.out.println("B");
1.2 多行代码的原子性安全问题–卖票案例
卖票案例:
/**
* 卖票任务
*/
public class TicketTask implements Runnable{
//定义变量,表示初始有100张票
int count = 100;
@Override
public void run() {
while (true){
if (count > 0) {
//先判断,后卖票
try {Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("卖出第"+count+"张票!");
//票数要减少
count--;
}else {
break;
}
}
}
}
public class TestDemo {
public static void main(String[] args) {
//0.创建卖票任务
TicketTask tt = new TicketTask();
//1.创建卖票窗口
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
Thread t3 = new Thread(tt);
//2.开始卖票
t1.start();
t2.start();
t3.start();
//线程出现安全问题:
//a.可能出现重复数据
//b.可能出现0,甚至-1这种非法数据
}
}
出现了线程安全问题(原子性问题):
//a.可能出现重复数据
原因: 当一个线程执行完卖票后,还没有来得及对票数-1,被其他线程抢走了CPU,导致其他线程也卖出一样的票
//b.可能出现0,甚至-1这种非法数据
原因: 当剩下最后一张票时,多线程都经过了if判断 进入卖票的代码块中,然后依次卖出第1张,第0张,第-1
1.3 synchronized关键字介绍
a.synchronized是什么??
synchronized是Java提供的关键字
b.synchronized的作用??
可以让多句代码具有原子性
(当某个线程执行多句代码的过程中不被其他线程打断)
1.4 解决方案1_同步代码块
格式:
synchronized(锁对象){
需要同步的代码(需要保持原子性的代码)
}
解决代码:
/**
* 卖票任务
*/
public class TicketTask implements Runnable{
//定义变量,表示初始有100张票
int count = 100;
Object obj = new Object();
@Override
public void run() {
while (true){
//同步代码块
synchronized (obj){
if (count > 0) {
//先判断,后卖票
System.out.println("卖出第"+count+"张票!");
//票数要减少
count--;
}
}
}
}
}
1.5 解决方案2_同步方法
格式:
public synchronized void 方法名(){
需要同步的代码(需要保证原子性的代码)
}
解决代码:
/**
* 卖票任务
*/
public class TicketTask implements Runnable{
//定义变量,表示初始有100张票
int count = 100;
@Override
public void run() {
while (true){
sellTicket();
}
}
public synchronized void sellTicket(){
if (count > 0) {
//先判断,后卖票
System.out.println("卖出第"+count+"张票!");
//票数要减少
count--;
}
}
}
注意:
a.同步代码块和同步方法其实原理是差不多,同步代码块的同步锁我们需要自己指定,而同步方法的同步锁
,默认使用当前对象this
b.同步方法能否是静态方法呢? 可以是static的,如果同步方法是static的,
默认使用当前类的字节码文件作为锁对象
1.6 解决方案3_Lock锁
Lock是一个接口,我们需要使用其实现类 ReentrantLock
ReentrantLock的API:
public ReentrantLock();
public void lock(); //加锁
public void unlock();//解锁(释放锁)
格式:
ReentrantLock lock = new ReentrantLock();
lock.lock(); //加锁
需要同步的代码块(需要保持原子性的代码)
lock.unlock();//解锁
解决代码:
/**
* 卖票任务
*/
public class TicketTask implements Runnable{
//定义变量,表示初始有100张票
int count = 100;
//创建一个Lock锁
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//加锁
lock.lock();
if (count > 0) {
//先判断,后卖票
System.out.println("卖出第"+count+"张票!");
//票数要减少
count--;
}
lock.unlock();
}
}
}
第二章 并发包【理解】
什么是并发包:
这是JDK提供的,包含一个在高并发情况使用集合或者工具,使用这些集合或者工具类时,能保证高并发情况下是安全的
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 TestArrayList { 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<>();//线程不安全的 public static List<Integer> list = new CopyOnWriteArrayList<>();//保证线程安全 @Override public void run() { for (int i = 0; i < 10000; i++) { list.add(i); } System.out.println("添加完毕!"); } }
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 TestSet { 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<>();//线程不安全的 public static Set<Integer> set = new CopyOnWriteArraySet<>();//线程安全的 @Override public void run() { for (int i = 0; i < 10000; i++) { set.add(i); } System.out.println("添加完毕!"); } }
2.3 ConcurrentHashMap
-
HashMap是线程不安全的
public class MyThread extends Thread { public static Map<Integer, Integer> map = new HashMap<>(); @Override public void run() { for (int i = 0; i < 10000; i++) { map.put(i, i);//0,0 1,1 2,2 3,3 ... 9999,9999 } } } public class TestMap { public static void main(String[] args) throws InterruptedException { MyThread t1 = new MyThread(); t1.start(); for (int i = 10000; i < 20000; i++) { MyThread.map.put(i, i); } Thread.sleep(1000 * 2); System.out.println("map最终大小:" + MyThread.map.size()); } }
-
Hashtable是线程安全的,但效率低
public class MyThread1 extends Thread { public static Map<Integer, Integer> map = new Hashtable<>(); //线程是安全 @Override public void run() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { map.put(i, i); } long end = System.currentTimeMillis(); System.out.println((end - start) + " 毫秒"); } } public class TestMap1 { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { new MyThread1().start();//开启1000个线程 } Thread.sleep(1000 * 20);//由于每个线程执行时间稍长,所以这里多停顿一会 System.out.println("map的最终大小:" + MyThread1.map.size()); } }
-
ConcurrentHashMap既安全又效率高
public class MyThread1 extends Thread { // public static Map<Integer, Integer> map = new Hashtable<>(); //线程是安全,但是效率低 public static Map<Integer, Integer> map = new ConcurrentHashMap<>(); //线程是安全,但是效率高 @Override public void run() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { map.put(i, i); } long end = System.currentTimeMillis(); System.out.println((end - start) + " 毫秒"); } } 小结: HashMap 线程不安全(多线程不能操作同一个HashMap) HashTable 线程安全的(多线程可以操作同一个HashTable),但是效率较低 ConcurrentHashMap 线程安全的,并且效率比较高
-
为什么Hashtable效率低而ConcurrentHashMap效率高?
因为HashTable对哈希表进行全表加锁 而ConcurrentHashMap只对某个桶(链表)局部加锁,并且也同时使用CAS机制
2.4 CountDownLatch
-
CountDownLatch的作用
允许当前线程,等待其他线程完成某种操作之后,当前线程继续执行
-
CountDownLatch的API
构造方法: public CountDownLatch(int count); 需要传入计数器,需要等待的线程数 成员方法: public void await() throws InterruptedException// 让当前线程等待 public void countDown() // 计数器进行减1
-
CountDownLatch的使用案例
需求: 线程1要执行打印:A和C,线程2要执行打印:B 我们需要这样的结果: 线程1 先打印A 线程2打印B 之后 线程1再打印C A B C public class TestDemo { public static void main(String[] args) throws InterruptedException { //0.创建一个CountDownLatch CountDownLatch latch = new CountDownLatch(1); //1.创建两个线程 Thread t1 = new MyThread1(latch); Thread t2 = new MyThread2(latch); t1.start(); Thread.sleep(5000); t2.start(); } } public class MyThread1 extends Thread { private CountDownLatch latch; public MyThread1(CountDownLatch latch){ this.latch = latch; } @Override public void run() { System.out.println("A...."); try { latch.await();//让当前线程等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("C...."); } } public class MyThread2 extends Thread { private CountDownLatch latch; public MyThread2(CountDownLatch latch){ this.latch = latch; } @Override public void run() { System.out.println("B...."); //让latch的计数器减少1 latch.countDown(); } }
2.5 CyclicBarrier
-
CyclicBarrier的作用
让多个线程,都到达了某种要求之后,新的任务才能执行
-
CyclicBarrier的API
构造方法: public CyclicBarrier(int parties, Runnable barrierAction); 需要多少个线程 所有线程都满足要求了,执行的任务 成员方法: public int await();当某个线程达到了,需要调用await()
-
CyclicBarrier的使用案例
需求: 部门开会,假设部门有五个人,五个人都到达了才执行开会这个任务 public class TestPersonThread { public static void main(String[] args) throws InterruptedException { //0.创建一个CyclicBarrier CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() { @Override public void run() { System.out.println("人都齐了,开会吧"); } }); //1.创建五个线程 PersonThread p1 = new PersonThread(barrier); PersonThread p2 = new PersonThread(barrier); PersonThread p3 = new PersonThread(barrier); PersonThread p4 = new PersonThread(barrier); PersonThread p5 = new PersonThread(barrier); //2.开启 p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); //Thread.sleep(6000); //System.out.println("人都到了,开会吧..."); //要求,人没到不开会,都到了立刻开会!!! } } public class PersonThread extends Thread { private CyclicBarrier barrier; public PersonThread(CyclicBarrier barrier){ this.barrier = barrier; } @Override public void run() { try { Thread.sleep(new Random().nextInt(6)*1000); System.out.println(Thread.currentThread().getName() + " 到了! "); //调用 barrier的await 表示线程到了 try { barrier.await(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } catch (InterruptedException e) { e.printStackTrace(); } } } 补充: Math的静态方法 public static double random(); //获取一个0(包括)到1(不包括)的正小数
2.6 Semaphore
-
Semaphore的作用
用于控制并发线程的数量!!
-
Semaphore的API
构造方法: public Semaphore(int permits); //参数 permits 表示最多允许有多少个线程并发执行 成员方法: public void acquire(); //获取线程的许可证 public void release(); //释放线程的许可证
-
Semaphore的使用案例
public class MyThread extends Thread { private Semaphore semaphore; public MyThread(Semaphore semaphore) { this.semaphore = semaphore; } @Override public void run(){ //从Semaphore获取线程的许可 try { semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 进入 时间=" + System.currentTimeMillis()); try { Thread.sleep(100*new Random().nextInt(10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 结束 时间=" + System.currentTimeMillis()); //归还semaphore线程的许可 semaphore.release(); } } public class TestDemo { public static void main(String[] args) { //0.创建Semaphore Semaphore semaphore = new Semaphore(3); //最多的并发线程数量为1 for (int i = 0; i < 10; i++) { new MyThread(semaphore).start(); } } }
2.7 Exchanger
-
Exchanger的作用
用于线程间的数据交换
-
Exchanger的API
构造方法: public Exchanger<V>(); 成员方法: public V exchange(V x);//参数为发给别的线程的数据,返回值别的线程发过来的数据
-
Exchanger的使用案例
public class TestExchanger { public static void main(String[] args) throws InterruptedException { //0.创建一个线程间数据交互对象 Exchanger<String> exchanger = new Exchanger<String>(); //1.创建线程A ThreadA aThread = new ThreadA(exchanger); aThread.start(); //休眠 Thread.sleep(5000); ThreadB bThread = new ThreadB(exchanger); bThread.start(); } } public class ThreadA extends Thread { private Exchanger<String> exchanger; public ThreadA(Exchanger<String> exchanger) { this.exchanger = exchanger; } @Override public void run() { System.out.println("线程A,要将礼物AAA,送给线程B..."); //调用exchanger String result = null; try { result = exchanger.exchange("AAA");//阻塞 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程A,获取到线程B的礼物:"+result); } } public class ThreadB extends Thread { private Exchanger<String> exchanger; public ThreadB(Exchanger<String> exchanger) { this.exchanger = exchanger; } @Override public void run() { System.out.println("线程B,要将礼物BBB,送给线程A..."); String result = null; try { result = exchanger.exchange("BBB"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程B,获取到线程A的礼物:"+result); } }
总结:
能够使用同步代码块解决线程安全问题【重点】 synchronized(锁对象){ 需要同步的代码(需要保证原子性的代码) } 锁对象,可以是任意对象 能够使用同步方法解决线程安全问题牌【重点】 public synchronized void 方法名(){ 需要同步的代码(需要保证原子性的代码) } 能够使用Lock锁解决线程安全问题牌【重点】 Lock lock = new ReentrantLock(); lock.lock(); 需要同步的代码(需要保证原子性的代码) lock.unlock(); 能够说明volatile关键字和synchronized关键字的区别 volatile 能解决有序性和可见性 原子类 能解决变量操作的原子性(有序性和可见性) synchronized 能解决多句代码的原子性(有序性和可见性) 能够描述CopyOnWriteArrayList类的作用 代替多线程的情况,线程安全的ArrayList集合 能够描述CopyOnWriteArraySet类的作用 代替多线程的情况,线程安全的HashSet集合 能够描述ConcurrentHashMap类的作用 代替多线程的情况,线程安全的HashMap集合(比HashTable效率更好) 能够描述CountDownLatch类的作用 可以允许一个线程等待另外一个线程执行完毕后再继续执行 能够描述CyclicBarrier类的作用 让一组线程都到达某种条件后再执行某个任务 能够表述Semaphore类的作用 控制多线程并发的最大数量 能够描述Exchanger类的作用 用于线程间的通信(数据交换)