JUC并发编程(第一部分)
准备工作
创建一个maven工程,然后检查并修改以下配置
1.JUC
学习方式:源码+官方文档
jdk参考文档
业务:普通的线程代码
Runnable:没有返回值, 效率比Callable相对较低
Callable接口
2.线程和进程
进程:是一个程序的集合,一个进程可以包含多个线程。
java默认的线程:默认线程有两个 main线程,和GC线程
java可以开启一个线程吗?
答案是:不能。
源码
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize, boolean inheritThreadLocals) {
this(group, target, name, stackSize, null, inheritThreadLocals);
}
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the {@code run} method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* {@code start} method) and the other thread (which executes its
* {@code run} method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @throws IllegalThreadStateException if the thread was already started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
有native方法,调用底层的c++ java实在虚拟机上面运行的
private native void start0();
3.并发,并行
并发编程:并发,并行
并发:多线程操作同一资源
cpu一核:模拟多线程,快速交替
并行:多个人一起走
cpu多核:多个线程同时执行,线程池
利用代码获取本机cpu
package com.zzuli;
public class domain01 {
public static void main(String[] args) {
//获取cpu的核数
//cpu密集型 IO密集性
System.out.println(Runtime.getRuntime().availableProcessors());
//查看线程有几种状态
Thread.State //这是一个枚举类型
}
}
并发编程的本质就是:提高CPU的利用率
4.简单回顾多线程
线程有几种状态
6中状态
以下是源码
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
wait/sleep的区别
来自不同的类
1,wait属于Object类
2,sleep属于Thread类
关于锁的释放
wait会释放锁,sleep不会释放锁
使用范围不同
wait必须再同步代码块中
sleep可以再任何地方
是否需要捕获异常
wait不需要捕获异常
sleep需要捕获异常
5.Lock锁
//使用线程 进行卖票操作实现使用传统的synchronized
真正的多线程开发,公司的开发,降低耦合性 线程就是一个单独的资源类,没有任何附属的操作
1,属性,方法
public class saleStaticDemo02 {
public static void main(String[] args) {
//并发,多个线程操作同一个资源类
Ticket ticket=new Ticket();
//lambda表达式 (参数)->{代码}
new Thread(()->{
for (int i=1; i<60;i++){
ticket.sale();
}
}).start();
new Thread(()->{
for (int i=1; i<60;i++){
ticket.sale();
}
}).start();
new Thread(()->{
for (int i=1; i<60;i++){
ticket.sale();
}
}).start();
}
}
//资源类 oop
class Ticket{
//属性
private int number=50;
//买票的方式
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余" + number);
}
}
}
Lock接口
公平锁:十分公平,先到先执行
非公平锁:不公平,可以插队(默认开启)
lock锁使用三部曲 (使用步骤)
1,创建实现类 new ReentrantLock();
2, 在业务方法中加锁
3,在finally中解锁
代码实现
package com.zzuli;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class saleTiketDemo03 {
public static void main(String[] args) {
//并发,多个线程操作同一个资源类
Ticket2 ticket=new Ticket2();
//lambda表达式 (参数)->{代码}
new Thread(()->{
for (int i=1; i<60;i++){
ticket.sale();
}
}).start();
new Thread(()->{
for (int i=1; i<60;i++){
ticket.sale();
}
}).start();
new Thread(()->{
for (int i=1; i<60;i++){
ticket.sale();
}
}).start();
}
}
//资源类 oop
class Ticket2{
//属性
private int number=50;
Lock lock=new ReentrantLock();
//买票的方式
public synchronized void sale(){
//加锁
lock.lock();
try {
//业务代码
if (number>0){
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余" + number);
}
}finally {
//解锁
lock.unlock();
}
}
}
使用lock和synchronzied同样都可以实现同步操作,他们的区别是什么
1,Synchronzied, 是内置的关键字,Lock是一个java类
2,Synchronzied,无法判断获取锁的状态,Lock可以判断是否获取到了锁,
3,Synchronzied,会自动释放锁,Lock必须手动释放锁,如果不释放锁,就会产生“死锁”
4,Synchronzied,线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock锁就不一定会一直等下去
5,Synchronzied,可重入锁,不可以中断的,是非公平锁,Lock,可重入锁,可以判断锁,默认为非公平锁(可以修改)
6,Synchronzied,适合锁少量的代码同步问题,Lock适合大量的同步代码
使用Synchronzied 实现生产者消费者问题,并由那些存在的问题
package com.zzuli.proCon;
/*
* 使用 synchronrized 实现生产者 ---- 消费者 模式
*
*
* 线程之间的通信问题
* */
public class Productor {
public static void main(String[] args) {
//创建共享对象
Data data=new Data();
//使用lamabda表达式开启线程对象
/*
* 开启第一个线程*/
new Thread(()->{
for (int i=1;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
/*
* 开启第二线程*/
new Thread(()->{
for (int i=1;i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
//这是一个资源类
class Data{
//共享的资源数据
private int number;
//业务方法,提供者
public synchronized void increment() throws InterruptedException {
if (number!=0){
//说明共享资源还有东西,不需要,提供,所以当前线程对象进入的等待状态
this.wait();
}
//当程序走到走到这一步,说明number==0,所以需要提供者提供资源
number++;
this.notifyAll();
System.out.println(Thread.currentThread().getName() + "--->" + number);
}
//业务方法,消费者
public synchronized void decrement() throws InterruptedException {
if (number==0){
//说明共享资源没有东西,不能继续消费了,所以当前线程对象进入的等待状态
this.wait();
}
//当程序走到走到这一步,说明number!=0,所以可以继续消费
number--;
this.notifyAll();
System.out.println(Thread.currentThread().getName() + "--->" + number);
}
}
存在的问题
以上代码线程是两个线程不会有问题,如果线程是四个或者多个,就会产生虚假唤醒,所以根据官方文档,要将if全部改为while,因为if只会判断一次
修改之后的代码,开启四个线程,并没有发生虚假唤醒的情况
package com.zzuli.proCon;
/*
* 使用 synchronrized 实现生产者 ---- 消费者 模式
*
*
* 线程之间的通信问题
* */
public class Productor {
public static void main(String[] args) {
//创建共享对象
Data data=new Data();
//使用lamabda表达式开启线程对象
/*i
* 开启第一个线程*/
new Thread(()->{
for (int i=1;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
/*
* 开启第二线程*/
new Thread(()->{
for (int i=1;i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
/*
* 开启第三线程*/
new Thread(()->{
for (int i=1;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
/*
* 开启第四线程*/
new Thread(()->{
for (int i=1;i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
//这是一个资源类
class Data{
//共享的资源数据
private int number;
//业务方法,提供者
public synchronized void increment() throws InterruptedException {
while (number!=0){
//说明共享资源还有东西,不需要,提供,所以当前线程对象进入的等待状态
this.wait();
}
//当程序走到走到这一步,说明number==0,所以需要提供者提供资源
number++;
this.notifyAll();
System.out.println(Thread.currentThread().getName() + "--->" + number);
}
//业务方法,消费者
public synchronized void decrement() throws InterruptedException {
while (number==0){
//说明共享资源没有东西,不能继续消费了,所以当前线程对象进入的等待状态
this.wait();
}
//当程序走到走到这一步,说明number!=0,所以可以继续消费
number--;
this.notifyAll();
System.out.println(Thread.currentThread().getName() + "--->" + number);
}
}
使用JUC 实现生产者消费者问题
通过Lock找到 Condition(官方文档)
使用JUC实现生产者消费者代码如下
package com.zzuli.lock;
/*
* 使用lock版锁实现生产者和消费者*/
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockPC {
public static void main(String[] args) {
//创建共享对象
Data data1=new Data();
//使用lamabda表达式开启线程对象
new Thread(()->{
for (int i=1;i<10;i++) {
data1.increment();
}
}).start();
new Thread(()->{
for (int i=1;i<10;i++) {
data1.decrement();
}
}).start();
new Thread(()->{
for (int i=1;i<10;i++) {
data1.increment();
}
}).start();
new Thread(()->{
for (int i=1;i<10;i++) {
data1.decrement();
}
}).start();
}
}
class Data{
//共享的资源数据
private int number;
//创建lock锁对象
Lock lock=new ReentrantLock();
//通过lock可以找到 condition
Condition condition=lock.newCondition();
//condition .await() 等待
//condition.signalAll() 唤醒全部
//业务方法提供者
public void increment(){
//在执行业务方法之前要使用lock进行上锁
//为了保证一定释放锁,所以一般就用 try/finally 将业务方法包裹起来,在finally进行释放锁
lock.lock();
try{
//编写业务逻辑代码
while (number!=0) {
//程序走到这里说明仓库还有资源,当前前程进入阻塞状态
condition.await();
}
//程序走到这一步,说明number=0需要提供者进行提供
number++;
//活像所有其他线程
condition.signalAll();
System.out.println(Thread.currentThread().getName()+"----->"+number);
} catch (Exception e) {
e.printStackTrace();
}finally {
//将锁释放
lock.unlock();
}
}
//业务方法的消费者
public void decrement(){
//首先对业务方法进行上锁
lock.lock();
try {
//添加业务方法
while(number==0){
//仓库没有东西了,进入等待状态
condition.await();
}
//程序在这一不说明仓库还有资源,可以进行消费
number--;
condition.signalAll();
System.out.println(Thread.currentThread().getName()+"----->"+number);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
}
我们可以利用conditon的特性,指定哪一个线程开启(唤醒),来完成线程的有序执行
package com.zzuli.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class OrderLock {
public static void main(String[] args) {
DataOL data2=new DataOL();
//开启三个线程,每一个线程都对应一个方法
new Thread(()->{
for (int i=1;i<10;i++){
data2.printA();
}
}).start();
new Thread(()->{
for (int i=1;i<10;i++){
data2.printB();
}
}).start();
new Thread(()->{
for (int i=1;i<10;i++){
data2.printC();
}
}).start();
}
}
//资源类,低耦合,oop
class DataOL{
//创建一个共享的数据
private Integer unmber=1;
//创建lock锁
Lock lock=new ReentrantLock();
//通过lock锁来得到 condition
Condition condition1=lock.newCondition();
Condition condition2=lock.newCondition();
Condition condition3=lock.newCondition();
//创建打印A的方法
public void printA(){
//给业务方法上锁
lock.lock();
try {
while (unmber!=1){
//轮不到condition1来进行打印,当前线程进入等待
condition1.await();
}
//程序走到这一步说明此时的number等于A,直接打印就行了
System.out.println(Thread.currentThread().getName()+"==>"+"AAAAAAAAAAAAAAAAAA");
//此时将number+1 指定condition2来打印
unmber=unmber+1;
//唤醒condition2
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
public void printB(){
//给业务方法上锁
lock.lock();
try {
while (unmber!=2){
//轮不到condition2来进行打印,当前线程进入等待
condition2.await();
}
//程序走到这一步说明此时的number等于2,直接打印就行了
System.out.println(Thread.currentThread().getName()+"==>"+"BBBBBBBBBBBBBB");
//此时将number+1 指定condition3来打印
unmber=3;
//唤醒condition2
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
public void printC(){
//给业务方法上锁
lock.lock();
try {
while (unmber!=3){
//轮不到condition3来进行打印,当前线程进入等待
condition3.await();
}
//程序走到这一步说明此时的number等于3,直接打印就行了
System.out.println(Thread.currentThread().getName()+"==>"+"CCCCCCCCCCCCCC");
//此时将number==1 指定condition1来打印
unmber=1;
//唤醒condition1
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
}
6.(8锁现象)
8锁现象其实就是对应不同类型 关于锁的问题。
1,第一种问题
package com.zzuli.eightLock;
import java.util.concurrent.TimeUnit;
/*
* 在标准情况下,两个线程会先输出哪一个 1发信息 2打电话
* 重点: synchronzied 锁的对象是方法的调用者 一个对象只有一把锁,这个例子中,调用的是同一个对象,用的是同一把锁,排队使用,谁先拿到,谁执行。
* */
public class OneLock {
public static void main(String[] args) {
Phone phone=new Phone();
String name;
new Thread(()->{
phone.sendMs();
}).start();
//休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}).start();
}
}
class Phone{
public synchronized void sendMs(){
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
}
2,第二种问题
package com.zzuli.eightLock;
import java.util.concurrent.TimeUnit;
/*
* 在标准情况下,两个线程会先输出哪一个 1发信息 2打电话
* 重点: synchronzied 锁的对象是方法的调用者 一个对象只有一把锁,这个例子中,调用的是同一个对象,用的是同一把锁,排队使用,谁先拿到,谁执行。
* */
public class OneLock {
public static void main(String[] args) {
Phone phone=new Phone();
String name;
new Thread(()->{
phone.sendMs();
}).start();
//休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}).start();
}
}
class Phone{
public synchronized void sendMs(){
//打印之前 睡眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
}
3,第三种问题
package com.zzuli.eightLock;
import java.util.concurrent.TimeUnit;
/*
* 在这种情况下,首先打印出来的是 1,hello 2,发信息
* sendMs()方法使用了锁,但是 hello()方法没有锁,所以并不会排队执行,所以间隔1秒之后输出hello
* */
public class TwoLock { public static void main(String[] args) {
Phone2 phone=new Phone2();
String name;
new Thread(()->{
phone.sendMs();
}).start();
//休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
}).start();
}
}
class Phone2{
public synchronized void sendMs(){
//打印之前 睡眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
4,第四种问题
package com.zzuli.eightLock;
import java.util.concurrent.TimeUnit;
/*
* 在这个例子中,创建了两个不同对象,也就是有了两个不同的锁,所以不需要排队执行,只有当两个对象 却只有一把锁的时候,才需要排队执行。
* 1,打电话 2 发信息
*
* */
public class ThreeLock {
public static void main(String[] args) {
//在这个方法中,创建了两个对象访问不同的方法。
Phone3 phone=new Phone3();
Phone3 phone3=new Phone3();
String name;
new Thread(()->{
phone.sendMs();
}).start();
//休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone3.call();
}).start();
}
}
class Phone3{
public synchronized void sendMs(){
//打印之前 睡眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
5,第五种问题
package com.zzuli.eightLock;
import java.util.concurrent.TimeUnit;
/*
* 在同步线程中,方法中都加入了static关键字,在加载类之前,就已经存在了类模板(phone.getClassLoader),而且只有一个,因为类模板只有一个锁
* 所以锁也就只有一个,而且两个方法都加了static关键字,所以就要同步排队, 1,发信息 2,打电话
* */
public class FourLock {
public static void main(String[] args) {
Phone4 phone=new Phone4();
String name;
new Thread(()->{
phone.sendMs();
}).start();
//休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}).start();
}
}
class Phone4{
//加入了static关键子,
public static synchronized void sendMs(){
//打印之前 睡眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public static synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
6,第六种问题
package com.zzuli.eightLock;
import java.util.concurrent.TimeUnit;
/*
* 创建两个对象,两个static 关键字,因为两个方法都有static关键字,所以类模板只有一个,所以类模板只有一把锁,还是需要排队执行,1,发信息 2,打电话
* */
public class FiveLock {
public static void main(String[] args) {
Phone5 phone=new Phone5();
Phone5 phone5=new Phone5();
String name;
new Thread(()->{
phone.sendMs();
}).start();
//休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone5.call();
}).start();
}
}
class Phone5{
//加入了static关键子,
public static synchronized void sendMs(){
//打印之前 睡眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public static synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
7,第七种问题
package com.zzuli.eightLock;
import java.util.concurrent.TimeUnit;
/*
*
* 一个带有static关键字的方法, 一个普通的同步方法,两个锁是不同的,一个锁的是class模板, 一个是对象锁,两个锁是不相同的的,所以不存在同步排队问题
* 只有时间延迟问题。 1,打电话 2发信息
* */
public class SixLock {
public static void main(String[] args) {
Phone6 phone=new Phone6();
String name;
new Thread(()->{
phone.sendMs();
}).start();
//休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}).start();
}
}
class Phone6{
//加入了static关键子,
public static synchronized void sendMs(){
//打印之前 睡眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
8,第八种问题
package com.zzuli.eightLock;
import java.util.concurrent.TimeUnit;
/*
* 创建两个对象,方法 一个带有static关键字的方法,一个普通的同步方法,一个锁的是class模板,一个锁的是对象,不是同一把锁,所以不存在同步排队问题
* 1,打电话 2,发信息
*
* */
public class ServenLock {
public static void main(String[] args) {
Phone7 phone=new Phone7();
Phone7 phone7=new Phone7();
String name;
new Thread(()->{
phone.sendMs();
}).start();
//休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone7.call();
}).start();
}
}
class Phone7{
//加入了static关键子,
public static synchronized void sendMs(){
//打印之前 睡眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
7.集合类不安全
1,List不安全
list在多线程的情况下会触发的情况
java.util.ConcurrentModificationException 并发修改异常
解决方案:
// 并发下list是不安全的
/*
* 解决方案:
*1,使用vector Vector list= new Vector();(vector由symchronzied 修饰 是线程安全的,但是效率低下)
*2,使用工具类将不安全的转化为安全 List list= Collections.synchronizedList(new ArrayList<>());
*3, 写入时复制 List list= new CopyOnWriteArrayList<>(); 使用首选方案
* */
public class Listtest {
public static void main(String[] args) {
//CopyOnWrite 写入时复制 Cow 计算你设计时的一种优化策略
//对线程调用的时候,对于这个唯一的list,读取的时候是固定的,写入的时候会有覆盖,而 // copeOnWrite 可以避免写入覆盖,以免 造成数据问题
List<Object> list= new CopyOnWriteArrayList<>();
//开启十个线程,去跑 拉姆达表达式
for (int i=1;i<11;i++) {
new Thread(() -> {
//随机生成一些字符串
list.add("abc"+new Random().nextInt(8888)
);
System.out.println(list);
}).start()
}
}
}
2,set不安全
/*
*
- java.util.ConcurrentModificationException 并发修改异常
- 解决HashSet线程不安全问题
- 1,使用工具类,将不安全的集合转换为安全的集合 Set set= Collections.synchronizedSet(new HashSet());
- 2,使用JUC编程,读写时复制 Set set=new CopyOnWriteArraySet<>();解决线程不安全问题
- */
代码验证
package com.zzuli.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/*
*
* java.util.ConcurrentModificationException 并发修改异常
* 解决HashSet线程不安全问题
* 1,使用工具类,将不安全的集合转换为安全的集合 Set<String> set= Collections.synchronizedSet(new HashSet<String>());
* 2,使用JUC编程,读写时复制 Set<String> set=new CopyOnWriteArraySet<>();解决线程不安全问题
* */
public class SetList {
public static void main(String[] args) {
//创建一个set集合
// Set<String> set=new HashSet<String>();
//Set<String> set= Collections.synchronizedSet(new HashSet<String>());
Set<String> set=new CopyOnWriteArraySet<>();
//开启多个线程来进行 set接入
for(int i=1;i<30;i++){
//使用拉姆达表达式开启线程
new Thread(()->{
//给set集合加入元素
set.add("abc"+new Random().nextInt(88888));
//输出set
System.out.println(set);
}).start();
}
}
}
HashSet的本质是什么
源码介绍:
public HashSet() {
map = new HashMap<>();
}
//HashSet中add方法源码
//从源码中来看,set就是map集合中的key,并且不可重复
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// PRESENT源码
//官方定义为一个不变的值
private static final Object PRESENT = new Object();
3,map不安全
回顾map源码
在工作中并不会这样用hashMap
//默认等价是什么 new HashMap<16 ,0.75>//初始化容量 加载因子 // Map<String ,String> map = Collections.synchronizedMap(new HashMap<>())=
/*java.util.ConcurrentModificationException 并发修改异常
解决方案
1,使用Collection工具将不安全的map转化为 线程安全 Map<String ,String> map = Collections.synchronizedMap(new HashMap<>());
2,JUC并发编程解决线程不安全 Map<String ,String> map=new ConcurrentReaderHashMap();==
代码实现
public class MapTest {
public static void main(String[] args) {
//在工作中并不会这样用hashMap
//默认等价是什么 new HashMap<16 ,0.75>
//初始化容量 加载因子
// Map<String ,String> map = Collections.synchronizedMap(new HashMap<>());
Map<String ,String> map=new ConcurrentReaderHashMap();
//开启多条线程,
for (int i=1;i<20;i++){
//使用拉姆达表达式开启线程
new Thread(()->{
map.put("abc"+new Random().nextInt(88888),"abc");
System.out.println(map);
}).start();
}
}
}