线程安全&并发包
能够使用同步代码块解决线程安全问题(重点)
synchronized(锁对象){
访问了共享数据的代码(产生了线程安全问题的代码)
}
能够使用同步方法解决线程安全问题(重点)
1.把访问了共享数据的代码,提取出来放到一个方法中
2.在方法上添加一个同步关键字synchronized
权限修饰符 synchronized 返回值类型 方法名(参数){
访问了共享数据的代码(产生了线程安全问题的代码)
}
能够说明volatile关键字和synchronized关键字的区别
volatile关键字:只能修饰变量,可以解决变量的可见性,有序性,不能解决原子性
synchronized关键字:不能修饰变量,可以修饰方法,代码块,使用的范围比volatile广,可以解决:可见性,有序性,原子性
能够描述ConcurrentHashMap类的作用(重点)
多线程安全的Map集合,效率比Hashtable高
能够描述CountDownLatch类的作用
作用:计数器 一个线程先执行一部分,然后等待其他线程执行完毕,然后线程在继续执行.
能够描述CyclicBarrier类的作用
作用:计数器 一个线程等待其他多个线程全部执行完毕,再执行(5个人都到,在开会)
能够描述Semaphore类的作用
作用:控制并发数量,可以允许几个线程同时进入执行 synchronized只允许一个线程进入执行
能够描述Exchanger类的作用
作用:两个线程信息交换
第一章 线程安全
之前我们讲过的AtomicInteger可以对“int类型的变量”做原子操作。但如果需要将“很多行代码”一起作为“原子性”执行——一个线程进入后,必须将所有代码行执行完毕,其它线程才能进入,可以使用synchronized关键字——重量级的同步关键字。
AtomicInteger:只能解决一个变量的原子性
synchronized:可以解决一段代码的原子性
1.卖票产生线程安全问题的概述(了解)
2.卖票产生线程安全问题的代码实现(重点)
package com.itheima.demo01payTicket;
/*
卖票案例:
创建3个线程,卖同100张票(共享的数据)
*/
public class Demo01PayTicket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建3个线程,卖同100张票
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
package com.itheima.demo01payTicket;
public class RunnableImpl implements Runnable{
//定义一个供所有线程共享的票源
private int ticket = 100;
//线程任务:卖票
@Override
public void run() {
//增加一个死循环,让线程重复卖票
while (true){
//判断ticket大于0,进行卖票
if(ticket>0){
//卖票需要10毫秒,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
ticket--;
}else{
break;
}
}
}
}
Thread-0线程正在卖第100张票!
Thread-1线程正在卖第100张票!
Thread-2线程正在卖第100张票!
Thread-2线程正在卖第97张票!
Thread-1线程正在卖第96张票!
Thread-0线程正在卖第95张票!
Thread-2线程正在卖第94张票!
Thread-0线程正在卖第94张票!
Thread-1线程正在卖第94张票!
...
Thread-2线程正在卖第13张票!
Thread-1线程正在卖第13张票!
Thread-1线程正在卖第10张票!
Thread-0线程正在卖第10张票!
Thread-2线程正在卖第10张票!
Thread-0线程正在卖第7张票!
Thread-2线程正在卖第7张票!
Thread-1线程正在卖第7张票!
Thread-1线程正在卖第4张票!
Thread-2线程正在卖第4张票!
Thread-0线程正在卖第4张票!
Thread-0线程正在卖第1张票!
Thread-2线程正在卖第0张票!
Thread-1线程正在卖第-1张票!
继承方式卖票代码
package com.itheima.demo01payTicket;
public class MyThread extends Thread {
//定义一个供所有线程共享的票源
private static int ticket = 100;
//线程任务:卖票
@Override
public void run() {
//增加一个死循环,让线程重复卖票
while (true){
//判断ticket大于0,进行卖票
if(ticket>0){
//卖票需要10毫秒,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
ticket--;
}else{
break;
}
}
}
}
package com.itheima.demo01payTicket;
public class Demo02PayTicket {
public static void main(String[] args) {
//创建3个线程,卖同100张票
MyThread t0 = new MyThread();
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t0.start();
t1.start();
t2.start();
}
}
3.线程安全问题的产生原理(了解-扩展知识点)
4.解决线程安全问题的第一种方式使用同步代码块(重点)
package com.itheima.demo02synchronized;
/*
卖票案例出现了线程安全问题,卖出了重复的票和不存在的票
解决线程安全问题的第一种方式:使用同步代码块
格式:
synchronized(锁对象){
访问了共享数据的代码(产生了线程安全问题的代码)
}
注意:
1.同步代码块中的锁对象可以任意的对象 new Person() new Object "aaa"
2.必须保证所有线程使用的都是同一个锁对象
原理:
使用一个锁对象,把同步代码块中的代码锁住,只让一个线程获取锁对象,进入到同步中执行,保证安全
*/
public class RunnableImpl implements Runnable{
//定义一个供所有线程共享的票源
private int ticket = 100;
//定义一个锁对象
//Person p = new Person();
//Object obj = new Object();
String str = "abc";//new char[]{'a','b','c'};
//线程任务:卖票
@Override
public void run() {
//增加一个死循环,让线程重复卖票
while (true){
//创建同步代码块
synchronized (str){
//判断ticket大于0,进行卖票
if(ticket>0){
//卖票需要10毫秒,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
ticket--;
}else{
break;
}
}
}
}
}
package com.itheima.demo02synchronized;
/*
卖票案例:
创建3个线程,卖同100张票(共享的数据)
*/
public class Demo01PayTicket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建3个线程,卖同100张票
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
5.同步的原理(了解-扩展知识点)
6.解决线程安全问题的第二种方式:使用同步方法(重点)
package com.itheima.demo03synchronized;
/*
卖票案例出现了线程安全问题,卖出了重复的票和不存在的票
解决线程安全问题的第二种方式:使用同步方法
原理:
把访问了共享数据的代码,提取出来放到一个方法中
在方法上添加一个同步关键字synchronized
底层也是使用一个锁对象,把同步方法锁住,只让一个线程进入到方法中执行,保证安全
格式:
权限修饰符 synchronized 返回值类型 方法名(参数列表){
访问了共享数据的代码(产生了线程安全问题的代码)
}
*/
public class RunnableImpl implements Runnable{
//定义一个供所有线程共享的票源
private static int ticket = 100;
//线程任务:卖票
@Override
public void run() {
System.out.println("this:"+this);
//增加一个死循环,让线程重复卖票
while (true){
//调用同步方法
//payTicket();
//调用静态同步方法
payTicketStatic();
if(ticket<=0){
break;
}
}
}
/*
定义一个静态的同步方法(了解)
静态同步方法的锁对象是谁?
是this吗? 不是 静态方法优先加载到静态区中,还没有this那(对象)
静态方法使用的锁对象是本类的class文件对象(反射)
RunnableImpl.class==>唯一
*/
public static /*synchronized*/ void payTicketStatic(){
synchronized (RunnableImpl.class){
//判断ticket大于0,进行卖票
if(ticket>0){
//卖票需要10毫秒,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
ticket--;
}
}
}
/*
定义一个同步方法
同步方法的锁对象是谁?
是this==>本类对象引用==>RunnableImpl run = new RunnableImpl();
run:com.itheima.demo03synchronized.RunnableImpl@4554617c
this:com.itheima.demo03synchronized.RunnableImpl@4554617c
*/
public synchronized void payTicket(){
//判断ticket大于0,进行卖票
if(ticket>0){
//卖票需要10毫秒,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
ticket--;
}
}
/*public void payTicket(){
synchronized (this){
//判断ticket大于0,进行卖票
if(ticket>0){
//卖票需要10毫秒,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
ticket--;
}
}
}*/
}
package com.itheima.demo03synchronized;
/*
卖票案例:
创建3个线程,卖同100张票(共享的数据)
*/
public class Demo01PayTicket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
System.out.println("run:"+run);
//创建3个线程,卖同100张票
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
7.解决线程安全问题的第三方式:使用Lock锁(重点)
package com.itheima.demo04Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
卖票案例出现了线程安全问题,卖出了重复的票和不存在的票
解决线程安全问题的第三种方式:使用Lock
java.util.concurrent.locks.Lock:接口
Lock锁是JDK1.5之后出现的
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
接口中的方法:
void lock() 获取锁。
void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock implements Lock
使用步骤:
1.在成员(唯一)位置创建ReentrantLock对象
2.在可能出现线程安全问题的代码前,调用lock方法获取锁对象
3.在可能出现线程安全问题的代码后,调用unlock方法释放锁对象
原理:
使用lock方法和unlock把一段代码包裹住,只让一个线程获取锁对象,进入执行
*/
public class RunnableImpl implements Runnable{
//定义一个供所有线程共享的票源
private int ticket = 100;
//1.在成员(唯一)位置创建ReentrantLock对象
private ReentrantLock l = new ReentrantLock();
//线程任务:卖票
@Override
public void run() {
//增加一个死循环,让线程重复卖票
while (true){
//2.在可能出现线程安全问题的代码前,调用lock方法获取锁对象
l.lock();
//判断ticket大于0,进行卖票
if(ticket>0){
//卖票需要10毫秒,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程正在卖第"+ticket+"张票!");
ticket--;
}/*else{
//break;
System.exit(0);//终止JVM
}*/
//3.在可能出现线程安全问题的代码后,调用unlock方法释放锁对象
l.unlock();
if(ticket<=0){
break;
}
}
}
}
package com.itheima.demo04Lock;
/*
卖票案例:
创建3个线程,卖同100张票(共享的数据)
*/
public class Demo01PayTicket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建3个线程,卖同100张票
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
8.CAS与Synchronized
CAS和Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?
AtomicInteger:只能解决一个变量的原子性
仅仅是在money.getAndIncrement()方法内部采用了CAS机制
synchronized:可以解决一段代码的原子性
synchronized是从悲观的角度出发:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中的ReentrantLock也是一种悲观锁。
CAS是从乐观的角度出发:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
CAS这种机制我们也可以将其称之为乐观锁。
第二章 并发包
在JDK的并发包java.util.concurrent里提供了几个非常有用的并发容器(集合)和并发工具类。供我们在多线程开发中进行使用。这些集合和工具类都可以保证高并发的线程安全问题.
1.并发List集合_CopyOnWriteArrayList(重点)
1).java.util.concurrent.CopyOnWriteArrayList(类):它是一个“线程安全”的ArrayList,我们之前学习的java.utils.ArrayList不是线程安全的。
2).如果是多个线程,并发访问同一个ArrayList,我们要使用:CopyOnWriteArrayList
需求:
1.创建一个被多个线程共享使用静态的ArrayList集合对象
2.使用Thread-0线程往集合中添加1000个元素
3.使用main线程往集合中添加1000个元素
4.统计集合的长度
package com.itheima.demo05List;
import java.util.ArrayList;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
/*
List集合三大特点:
1.有序:存储元素和取出的元素顺序是一致的
2.允许存储重复的元素
3.包含一些带索引的方法
*/
public class MyThread extends Thread {
//1.创建一个被多个线程共享使用静态的ArrayList集合对象
/*
java.uti.ArrayList:是一个多线程不安全的集合
*/
//public static ArrayList<Integer> list = new ArrayList<>();
/*
java.util.Vector<E>集合
是JDK1.0时期的单列集合
与新 collection 实现不同,Vector 是同步的。
Vector集合采用同步技术,保证多线程使用集合的安全
同步技术使用synchronized,是一个悲观锁,效率低下
*/
//public static Vector<Integer> list = new Vector<>();
/*
java.util.concurrent.CopyOnWriteArrayList<E>集合 implements List<E>接口
是JDK1.5之后出现的
CopyOnWriteArrayList采用乐观锁,保证多线程使用集合的安全
乐观锁使用CAS机制,是一个乐观锁,效率比synchronized高
CopyOnWriteArrayList集合中的方法和List接口一模一样
*/
public static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
@Override
public void run() {
//2.使用Thread-0线程往集合中添加1000个元素
System.out.println("Thread-0线程开始执行线程任务了,往集合中添加1000个元素");
for (int i = 0; i < 1000; i++) {
list.add(i);//{0,1,2,3...999}
try {
Thread.sleep(1);//提高出现线程安全问题的概率
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread-0线程添加1000个元素完毕,线程任务结束了!");
}
}
package com.itheima.demo05List;
public class Demo01List {
public static void main(String[] args) throws InterruptedException {
//创建MyThread对象,调用start方法,开启一个新的线程执行run方法
MyThread mt = new MyThread();
mt.start();
//3.使用main线程往集合中添加1000个元素
System.out.println("main线程往集合中添加1000个元素");
for (int i = 0; i < 1000; i++) {
MyThread.list.add(i);//{0,1,2,3...999}
Thread.sleep(1);//提高出现线程安全问题的概率
}
System.out.println("main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕");
Thread.sleep(3000);
System.out.println("两个线程都添加元素结束,统计集合的长度:"+MyThread.list.size());
}
}
ArrayList集合并发的问题:
1.存储的元素个数不对
2.会引发索引越界异常(底层是一个数组,数组每次添加元素,创建新的数组)
main线程往集合中添加1000个元素
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
Thread-0线程添加1000个元素完毕,线程任务结束了!
两个线程都添加元素结束,统计集合的长度:2000
main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
Thread-0线程添加1000个元素完毕,线程任务结束了!
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
两个线程都添加元素结束,统计集合的长度:1985
main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
Thread-0线程添加1000个元素完毕,线程任务结束了!
两个线程都添加元素结束,统计集合的长度:1996
2.并发Set集合_CopyOnWriteArraySet(重点)
需求:
1.创建一个被多个线程共享使用静态的HashSet集合对象
2.使用Thread-0线程往集合中添加1000个元素
3.使用main线程往集合中添加1000个元素
4.统计集合的长度
package com.itheima.demo06Set;
import java.util.HashSet;
import java.util.concurrent.CopyOnWriteArraySet;
/*
Set集合特点:
1.不允许存储重复的元素
2.不包含带索引的方法
*/
public class MyThread extends Thread {
//1.创建一个被多个线程共享使用静态的HashSet集合对象
/*
java.uti.HashSet:是一个多线程不安全的集合
*/
//public static HashSet<Integer> set = new HashSet<>();
/*
java.util.concurrent.CopyOnWriteArraySet<E>集合
是JDK1.5之后出现的
CopyOnWriteArraySet采用乐观锁,保证多线程使用集合的安全
乐观锁使用CAS机制,是一个乐观锁
CopyOnWriteArraySet里边的方法和HashSet是一样的
*/
public static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
@Override
public void run() {
//2.使用Thread-0线程往集合中添加1000个元素
System.out.println("Thread-0线程开始执行线程任务了,往集合中添加1000个元素");
for (int i = 0; i < 1000; i++) {
set.add(i);//{0,1,2,3...999}
try {
Thread.sleep(1);//提高出现线程安全问题的概率
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread-0线程添加1000个元素完毕,线程任务结束了!");
}
}
package com.itheima.demo06Set;
public class Demo01Set {
public static void main(String[] args) throws InterruptedException {
//创建MyThread对象,调用start方法,开启一个新的线程执行run方法
MyThread mt = new MyThread();
mt.start();
//3.使用main线程往集合中添加1000个元素
System.out.println("main线程往集合中添加1000个元素");
for (int i = 1000; i < 2000; i++) {
MyThread.set.add(i);//{1000,1001,1002,1003...1999}
Thread.sleep(1);//提高出现线程安全问题的概率
}
System.out.println("main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕");
Thread.sleep(3000);
System.out.println("两个线程都添加元素结束,统计集合的长度:"+ MyThread.set.size());
}
}
HashSet集合存在并发问题:
main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
Thread-0线程添加1000个元素完毕,线程任务结束了!
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
两个线程都添加元素结束,统计集合的长度:1992
3.并发Map集合_ConcurrentHashMap(重点)
需求:
1.创建一个被多个线程共享使用静态的HashMap集合对象
2.使用Thread-0线程往集合中添加1000个元素
3.使用main线程往集合中添加1000个元素
4.统计集合的长度
package com.itheima.demo07Map;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
/*
Map集合特点:
1.是一个双列集合
2.key不允许重复的,value可以重复
3.key和value是一一对应
---------------------------------------
面向复制编程:CV大法
---------------------------------------
ctrl+r:查找并替换
*/
public class MyThread extends Thread {
//1.创建一个被多个线程共享使用静态的HashMap集合对象
/*
java.uti.HashMap:是一个多线程不安全的集合
*/
//public static HashMap<Integer,Integer> map = new HashMap<>();
/*
java.util.Hashtable<K,V>集合
是JDK1.0时期的双列集合
不像新的 collection 实现,Hashtable 是同步的
Hashtable集合采用同步技术,保证多线程使用集合的安全
同步技术使用synchronized,是一个悲观锁,效率低下
*/
//public static Hashtable<Integer,Integer> map = new Hashtable<>();
/*
java.util.concurrent.ConcurrentHashMap<K,V><集合
是JDK1.5之后出现的
ConcurrentHashMap采用乐观锁,保证多线程使用集合的安全
使用CAS机制,是一个乐观锁,效率比synchronized高
ConcurrentHashMap集合中的方法和HashMap集合一模一样
*/
public static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();
@Override
public void run() {
//2.使用Thread-0线程往集合中添加1000个元素
System.out.println("Thread-0线程开始执行线程任务了,往集合中添加1000个元素");
for (int i = 0; i < 1000; i++) {
map.put(i,i);//{0-0,1-1,2-2,3-3...1999-1999}
try {
Thread.sleep(1);//提高出现线程安全问题的概率
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread-0线程添加1000个元素完毕,线程任务结束了!");
}
}
package com.itheima.demo07Map;
public class Demo01Map {
public static void main(String[] args) throws InterruptedException {
//创建MyThread对象,调用start方法,开启一个新的线程执行run方法
MyThread mt = new MyThread();
mt.start();
//3.使用main线程往集合中添加1000个元素
System.out.println("main线程往集合中添加1000个元素");
for (int i = 1000; i < 2000; i++) {
MyThread.map.put(i,i);//{1000-1000,1001-1001,1002-1002,...1999-1999}
Thread.sleep(1);//提高出现线程安全问题的概率
}
System.out.println("main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕");
Thread.sleep(3000);
System.out.println("两个线程都添加元素结束,统计集合的长度:"+ MyThread.map.size());
System.out.println(MyThread.map);
}
}
hashMap集合存在并发问题:
main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
Thread-0线程添加1000个元素完毕,线程任务结束了!
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
两个线程都添加元素结束,统计集合的长度:1990
main线程往集合中添加1000个元素
Thread-0线程开始执行线程任务了,往集合中添加1000个元素
main线程添加1000个元素完毕,睡眠3秒钟,等待Thread-0执行完毕
Thread-0线程添加1000个元素完毕,线程任务结束了!
两个线程都添加元素结束,统计集合的长度:1995
比较ConcurrentHashMap和Hashtable的效率
Java类库中,从1.0版本也提供一个线程安全的Map:Hashtable
Hashtable和ConcurrentHashMap有什么区别:
Hashtable采用的synchronized——悲观锁,效率更低。
ConcurrentHashMap:采用的CAS 机制——乐观锁,效率更高。
需求:
1.创建一个被多个线程共享使用静态的Hashtable集合(ConcurrentHashMap集合)对象
2.开启1000个线程,每个线程往集合中存储100000个数据
package com.itheima.demo08Map;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
public class MyThread extends Thread{
//1.创建一个被多个线程共享使用静态的Hashtable集合(ConcurrentHashMap集合)对象
//public static Hashtable<Integer,Integer> map = new Hashtable<>();
public static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();
@Override
public void run() {
//每个线程往集合中存储100000个数据
long s = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
map.put(i,i);
}
long e = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+"线程存储10W个数据耗时:"+(e-s)+"毫秒!");
}
}
package com.itheima.demo08Map;
public class Demo01 {
public static void main(String[] args) {
//开启1000个线程
for (int i = 0; i < 1000; i++) {
new MyThread().start();
}
}
}
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方法来获取元素,所以竞争越激烈效率越低。
4.多线程协作_CountDownLatch(会用)
CountDownLatch允许一个或多个线程等待其他线程完成操作。
例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行打印C。
CountDownLatch构造方法:
public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象
CountDownLatch重要方法:
public void await() throws InterruptedException// 让当前线程等待,直到CountDownLatch计数器的值为0的时候,让线程继续执行
public void countDown() // 计数器进行减1
-
无协作案例
package com.itheima.demo09CountDownLatch; public class MyThread1 extends Thread{ @Override public void run() { System.out.println("A"); System.out.println("C"); } }
package com.itheima.demo09CountDownLatch; public class MyThread2 extends Thread{ @Override public void run() { System.out.println("B"); } }
package com.itheima.demo09CountDownLatch; /* 例如:线程1要执行打印:A和C,线程2要执行打印:B, 但线程1在打印A后,要线程2打印B之后才能打印C, 所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行打印C。 */ public class Demo01 { public static void main(String[] args) { new MyThread1().start(); new MyThread2().start(); } }
-
示例
1). 制作线程1:
package com.itheima.demo10CountDownLatch;
import java.util.concurrent.CountDownLatch;
public class MyThread1 extends Thread{
private CountDownLatch cdl;
public MyThread1(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
System.out.println("A");
try {
//让当前线程等待,直到CountDownLatch计数器的值为0的时候,让线程继续执行
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C");
}
}
2). 制作线程2:
package com.itheima.demo10CountDownLatch;
import java.util.concurrent.CountDownLatch;
public class MyThread2 extends Thread{
private CountDownLatch cdl;
public MyThread2(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
System.out.println("B");
cdl.countDown();//计数器进行减1
}
}
3).制作测试类:
package com.itheima.demo10CountDownLatch;
import java.util.concurrent.CountDownLatch;
/*
例如:线程1要执行打印:A和C,线程2要执行打印:B,
但线程1在打印A后,要线程2打印B之后才能打印C,
所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行打印C。
java.util.concurrent.CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
注意:
必须保证多个线程使用的是同一个CountDownLatch对象
*/
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
//创建CountDownLatch对象,给两个线程共享使用
CountDownLatch cdl = new CountDownLatch(1);//内部计数器的值为1
new MyThread1(cdl).start();
Thread.sleep(1000);//睡眠1秒钟,保证线程1先执行
new MyThread2(cdl).start();
}
}
4). 执行结果:
会保证按:A B C的顺序打印。
说明:
CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉。
CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch。
await()方法的线程阻塞状态解除,继续执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHNED831-1645620838867)(img/1638429791745.png)]
5.多线程协作_CyclicBarrier(会用)
概述
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
例如:公司召集5名员工开会,等5名员工都到了,会议开始。
我们创建5个员工线程,1个开会线程,几乎同时启动,使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。
CyclicBarrier构造方法:
public CyclicBarrier(int parties, Runnable barrierAction)// 用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景
CyclicBarrier重要方法:
public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
-
无协作案例
package com.itheima.demo11CyclicBarrier; /* 创建的员工线程 */ public class PersonThread extends Thread{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"来到了会议室!"); } }
package com.itheima.demo11CyclicBarrier; /* 会议线程 */ public class MeetingThread extends Thread{ @Override public void run() { System.out.println("人到齐了,我们开始开会!"); } }
package com.itheima.demo11CyclicBarrier; /* 例如:公司召集5名员工开会,等5名员工都到了,会议开始。 我们创建5个员工线程,1个开会线程,几乎同时启动, 使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。 */ public class Demo01CyclicBarrier { public static void main(String[] args) { PersonThread p1 = new PersonThread(); PersonThread p2 = new PersonThread(); PersonThread p3 = new PersonThread(); PersonThread p4 = new PersonThread(); PersonThread p5 = new PersonThread(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); MeetingThread mt = new MeetingThread(); mt.start(); } }
-
示例代码:
1). 制作员工线程:
package com.itheima.demo12CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/*
创建的员工线程
*/
public class PersonThread extends Thread{
private CyclicBarrier cb;
public PersonThread(CyclicBarrier cb) {
this.cb = cb;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"来到了会议室!");
try {
cb.await();//把CyclicBarrier内部计数器的值-1
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
2). 制作开会线程:
package com.itheima.demo11CyclicBarrier;
/*
会议线程
*/
public class MeetingThread extends Thread{
@Override
public void run() {
System.out.println("人到齐了,我们开始开会!");
}
}
3). 制作测试类:
package com.itheima.demo12CyclicBarrier;
import java.util.concurrent.CyclicBarrier;
/*
例如:公司召集5名员工开会,等5名员工都到了,会议开始。
我们创建5个员工线程,1个开会线程,几乎同时启动,
使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。
java.util.concurrent.CyclicBarrier
一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点
构造方法:
CyclicBarrier(int parties, Runnable barrierAction)
参数:
int parties:设置屏障的数量,想让几个线程执行完,在执行其他线程;数量就设置几
想让5个人都到齐之后,在执行会议线程
屏障的数量设置:5
Runnable barrierAction:达到屏障之后,开始执行的线程(会议线程)
成员方法:
int await() 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
*/
public class Demo01CyclicBarrier {
public static void main(String[] args) {
//创建CyclicBarrier对象,计数器的值,设置为5;当计数器的值变成0,就会执行参数传递的MeetingThread线程
MeetingThread mt = new MeetingThread();
CyclicBarrier cb = new CyclicBarrier(5,mt);
PersonThread p1 = new PersonThread(cb);
PersonThread p2 = new PersonThread(cb);
PersonThread p3 = new PersonThread(cb);
PersonThread p4 = new PersonThread(cb);
PersonThread p5 = new PersonThread(cb);
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
}
}
4). 执行结果:
Thread-1来到了会议室!
Thread-2来到了会议室!
Thread-3来到了会议室!
Thread-4来到了会议室!
Thread-5来到了会议室!
人到齐了,我们开始开会!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cw5feOis-1645620838867)(img/1638431819245.png)]
使用场景
使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。
需求:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。
6.并发数量控制_Semaphore(会用)
Semaphore的主要作用是控制线程的并发数量。
synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore可以设置同时允许几个线程执行。
Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
Semaphore构造方法:
public Semaphore(int permits) permits 表示许可线程的数量
public Semaphore(int permits, boolean fair) fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程
Semaphore重要方法:
public void acquire() 表示获取许可 lock
public void release() 表示释放许可 unlock
- 示例一:同时允许1个线程执行
1):使用同步代码块
package com.itheima.demo13Semaphore;
/*
需求:
创建5名学生线程
让5名学生线程进入到一个教室中去参观
要求每次只能一名学生进入到教室参观
*/
public class Demo01Semaphore {
public static void main(String[] args) {
//创建一个教室
ClassRoom classRoom = new ClassRoom();
//创建5名学生线程
for (int i = 0; i < 5; i++) {
new StudentThread(classRoom).start();
}
}
}
package com.itheima.demo13Semaphore;
/*
定义教室类
*/
public class ClassRoom {
//定义一个线程进入到教室参观的方法
public void intoClassRoom(){
//要求每次只能一名学生进入到教室参观,使用同步代码块
synchronized (this){
System.out.println(Thread.currentThread().getName()+"线程...进入到教室参观!");
try {
Thread.sleep(2000);//在教室参观2秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程...参观2秒钟之后离开教室!");
}
}
}
package com.itheima.demo13Semaphore;
/*
学生线程
*/
public class StudentThread extends Thread {
//定义一个教室的变量:保证5个学生进入的是同一个教室
private ClassRoom classRoom;
public StudentThread(ClassRoom classRoom) {
this.classRoom = classRoom;
}
@Override
public void run() {
//让学生进入到教室去参观
classRoom.intoClassRoom();
}
}
4). 结果:
Thread-0线程...进入到教室参观!
Thread-0线程...参观2秒钟之后离开教室!
Thread-4线程...进入到教室参观!
Thread-4线程...参观2秒钟之后离开教室!
Thread-3线程...进入到教室参观!
Thread-3线程...参观2秒钟之后离开教室!
Thread-2线程...进入到教室参观!
Thread-2线程...参观2秒钟之后离开教室!
Thread-1线程...进入到教室参观!
Thread-1线程...参观2秒钟之后离开教室!
- 示例二:同时允许2个线程同时执行
package com.itheima.demo14Semaphore;
import java.util.concurrent.Semaphore;
/*
定义教室类
java.util.concurrent.Semaphore:可以控制线程的并发数量
控制同时有几个线程执行
构造方法:
Semaphore(int permits) 参数传递允许执行的线程数量
成员方法:
void acquire() 表示获取许可,相当于lock(获取锁)
void release() 释放一个许可,相当于unlock(释放锁)
*/
public class ClassRoom {
//创建Semaphore对象,参数传递2,表示允许同时有2个线程进入执行
Semaphore semaphore = new Semaphore(2);
//定义一个线程进入到教室参观的方法
public void intoClassRoom(){
try {
//要求每次可以进入两名学生进入到教室参观
semaphore.acquire();//表示获取许可
System.out.println(Thread.currentThread().getName()+"线程...进入到教室参观!");
try {
Thread.sleep(2000);//在教室参观2秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程...参观2秒钟之后离开教室!");
semaphore.release();//释放一个许可
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2). 再次执行结果:
Thread-0线程...进入到教室参观!
Thread-1线程...进入到教室参观!
Thread-0线程...参观2秒钟之后离开教室!
Thread-1线程...参观2秒钟之后离开教室!
Thread-2线程...进入到教室参观!
Thread-3线程...进入到教室参观!
Thread-3线程...参观2秒钟之后离开教室!
Thread-2线程...参观2秒钟之后离开教室!
Thread-4线程...进入到教室参观!
Thread-4线程...参观2秒钟之后离开教室!
7.线程信息交互_Exchanger(会用)
概述
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
Exchanger构造方法:
public Exchanger()
Exchanger重要方法:
public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
- 示例一:exchange方法的阻塞特性
1).制作线程A,并能够接收一个Exchanger对象:
package com.itheima.demo15Exchanger;
import java.util.concurrent.Exchanger;
/*
java.util.concurrent.Exchanger<V>:用于两个线程数据交换
构造方法:
Exchanger() 创建一个新的 Exchanger。
成员方法:
public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
注意:
保证交互数据的两个线程使用的是同一个Exchanger对象
*/
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开始执行");
System.out.println("线程A给线程B100元钱,并从线程B获取一张火车票!");
try {
String result = exchanger.exchange("100元");
System.out.println("线程A得到的东西:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2). 制作main()方法:
package com.itheima.demo15Exchanger;
import java.util.concurrent.Exchanger;
public class Demo01 {
public static void main(String[] args) {
//创建Exchanger对象
Exchanger<String> exchanger = new Exchanger<>();
new ThreadA(exchanger).start();
}
}
3).执行结果:只有线程A执行,线程B没有执行,没有人和线程A交互数据,exchange会一直等待
线程A开始执行
线程A给线程B100元钱,并从线程B获取一张火车票!
- 示例二:exchange方法执行交换
1).制作线程A:
package com.itheima.demo15Exchanger;
import java.util.concurrent.Exchanger;
/*
java.util.concurrent.Exchanger<V>:用于两个线程数据交换
构造方法:
Exchanger() 创建一个新的 Exchanger。
成员方法:
public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
注意:
保证交互数据的两个线程使用的是同一个Exchanger对象
*/
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开始执行");
System.out.println("线程A给线程B100元钱,并从线程B获取一张火车票!");
try {
String result = exchanger.exchange("100元");
System.out.println("线程A得到的东西:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2).制作线程B:
package com.itheima.demo15Exchanger;
import java.util.concurrent.Exchanger;
/*
java.util.concurrent.Exchanger<V>:用于两个线程数据交换
构造方法:
Exchanger() 创建一个新的 Exchanger。
成员方法:
public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
注意:
保证交互数据的两个线程使用的是同一个Exchanger对象
*/
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开始执行");
System.out.println("线程B给线程A一张火车票,并从线程A得到100元钱!");
try {
String result = exchanger.exchange("火车票");
System.out.println("线程B得到的东西:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3).制作测试类:
package com.itheima.demo15Exchanger;
import java.util.concurrent.Exchanger;
public class Demo01 {
public static void main(String[] args) {
//创建Exchanger对象
Exchanger<String> exchanger = new Exchanger<>();
new ThreadA(exchanger).start();
new ThreadB(exchanger).start();
}
}
4).执行结果:
线程A开始执行
线程B开始执行
线程B给线程A一张火车票,并从线程A得到100元钱!
线程A给线程B100元钱,并从线程B获取一张火车票!
线程A得到的东西:火车票
线程B得到的东西:100元
- 示例三:exchange方法的超时
1).制作线程A:
package com.itheima.demo15Exchanger;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/*
java.util.concurrent.Exchanger<V>:用于两个线程数据交换
构造方法:
Exchanger() 创建一个新的 Exchanger。
成员方法:
V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
V exchange(V x, long timeout, TimeUnit unit)
V x:给对方的东西
long timeout:设置等待的时长
TimeUnit unit:设置等待的时间单位
常量:静态常量,通过类名可以直接使用
TimeUnit.DAYS:天
TimeUnit.HOURS:时
TimeUnit.MINUTES:分钟
TimeUnit.SECONDS:秒
注意:
保证交互数据的两个线程使用的是同一个Exchanger对象
*/
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开始执行");
System.out.println("线程A给线程B100元钱,并从线程B获取一张火车票!");
try {
//String result = exchanger.exchange("100元");
//设置等待5秒钟没有人来交换数据,抛出TimeoutException异常,结束等待
String result = exchanger.exchange("100元",5, TimeUnit.SECONDS);
System.out.println("线程A得到的东西:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
2).制作测试类:
package com.itheima.demo15Exchanger;
import java.util.concurrent.Exchanger;
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
//创建Exchanger对象
Exchanger<String> exchanger = new Exchanger<>();
new ThreadA(exchanger).start();
Thread.sleep(10000);
new ThreadB(exchanger).start();
}
}
3).测试结果:
线程A开始执行
线程A给线程B100元钱,并从线程B获取一张火车票!
java.util.concurrent.TimeoutException
at java.util.concurrent.Exchanger.exchange(Exchanger.java:626)
at com.itheima.demo15Exchanger.ThreadA.run(ThreadA.java:39)
线程B开始执行
线程B给线程A一张火车票,并从线程A得到100元钱!
使用场景
使用场景:可以做数据校对工作
需求:比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,
并对两个文件数据进行校对,看看是否录入一致,