多线程与高并发一

1.创建线程方式

  1. 继承Thread类,重写run()方法,调用start方法开启线程

  2. 实现Runnable接口

  3. 实现Callable接口(可获取返回值)

  4. Executors.newCachedThreadPool() 通过线程池创建

    阿里推荐创建线程池的方式: new ThreadPoolExecutor()

//1.继承Thread类,重写run()方法,调用start方法开启线程
public class ThreadTest extends Thread {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 2010; i++) {
    
    
            System.out.println("打豆豆");
        }
    }
    public static void main(String[] args) {
    
    
        new ThreadTest().start();
    }    
}
//2.实现Runnable接口
public class RunnableTest implements Runnable {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 2010; i++) {
    
    
            System.out.println("打豆豆");
        }
    }
    public static void main(String[] args) {
    
    
        new Thread( new RunnableTest()).start();
        //也可用lambda表达式
        new Thread(()->{
    
    System.out.println("打豆豆");}).start();
    }
}
//3.实现Callable接口
public class CallableTest implements Callable<Boolean> {
    
    
    @Override
    public Boolean call() {
    
    
        System.out.println("11111");
        return true;
    }

    public static void main(String[] args) {
    
    
        CallableTest p1 = new CallableTest();       

        //1.创建线程池
        ExecutorService serve = Executors.newFixedThreadPool(3);
        //阿里推荐创建线程池的方式
        threadPool = new ThreadPoolExecutor(8, 16, 60, TimeUnit.SECONDS,
                            new LinkedBlockingQueue<>(32), new ThreadPoolExecutor.CallerRunsPolicy());

        //2.提交执行
        Future<Boolean> r1 = serve.submit(p1);
        //3.获取结果
        Boolean res1 = r1.get();
        //4.关闭服务
        serve.shutdownNow();
    }
}

注意:创建线程调用方式

1.new T1().run();     仅是执行run()方法,并不开启一个线程
2.new T1().start()	  开启一个线程,执行run()方法

2.线程方法

  1. 线程睡眠: Thread.sleep(1000)
    1. Thread.sleep(0) :触发一次cpu竞争,防止在cpu抢占式算法中,一个线程长时间占用cpu资源
  2. 线程礼让: Thread.yeild() 重新进入等待队列(让出一线cpu,返回就绪状态,同时也会参加cpu的竞争)
  3. 线程插队: t1.join() : 需要在t2(其他线程)执行的时候,t1插队,必须等t1执行完才执行t2(其他线程)
  4. 获取线程状态: t1.getState();

Ready和Running 合为一个 Runnable状态

在这里插入图片描述

在这里插入图片描述

3.Synchronized关键字(隐式定义,出了作用域自动释放)

1.同步代码块:(默认锁的对象为Obj),执行完代码块就会释放锁

synchronized(Obj){
    
    //Obj称为同步监视器,Obj可以锁任意对象,但是推荐使用共享资源作为同步监视器
    ...
}

2.同步方法 (默认锁的是方法的对象)

public synchronized void method(int args){
    
    
    ...
}   

synchronized 方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程阻塞,方法一旦执行,就独占该锁,直到该方法返回才能释放锁,后面被诸塞i的线程才能获得这个锁,继续执行。
缺陷:若将一个大的方法申明为synchronized将会影响效率
同步方法的同步监视器是 this就是对象本身,或者的class(反射)

注意:

1.同一个类中:synchronized(this ){…} 与 public synchronized void method(){…} 锁的为同一个对象,两种表达等价

class T{
    
    
	//方法1和2表达的效果一样
    public void method1(){
    
    
        synchronized(this){
    
    
            System.out.println("000");
        }
    }
    public synchronized void method2(){
    
    
        System.out.println("000");
    }
    
    //synchronized 静态方法  锁的是class对象
    public synchronized static void method(){
    
    
    	...
    }
}

2.public synchronized static void method(){…} 锁的是 T.class

3.**脏读:写方法加锁,读方法不加锁,(读到未被修改的值)**解决:同时加锁,(但效率低)

4.synchronized:保证可见性,一致性,可重入锁

5.可重入锁:广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁 。某个线程已经获得某个锁,可以再次获取锁而不会出现死锁

6.程序产生异常时会自动释放锁

Synchronized锁升级

锁的4中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高)

1.偏向锁:MarkWord记录线程ID,判断其他线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁 ; 如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。(多个线程争用,升级为自旋锁,自旋消耗cpu资源)
2.轻量级锁

自旋次数超过(10/100次)时升级为重量级锁。

3.重量级锁(向操作系统申请,进入等待队列,)

***注意:**为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁 可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。

在这里插入图片描述

总结:

  1. 加锁代码执行时间短或线程数少, 用轻量级锁
  2. 加锁代码执行时间长或线程数多, 用重量级锁

synchronized(Object)

锁的对象不能是基础数据类型: 向String常量,Interger,Long

4.volatile 关键字

​ volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

1.保证可见性(通过MESI 缓存一致性协议)

​ 当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去,这个写会操作会导致其他线程中的volatile变量缓存无效。

线程之间不可见: 一个线程修改对copy到自己线程工作内存的共享变量,另一个线程无法看到共享变量的改变

2.禁止指令重排序(读屏障,写屏障,原语操作)
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
​ 1.它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

​ 2.它会强制将对缓存的修改操作立即写入主存;

​ 3.如果是写操作,它会导致其他CPU中对应的缓存行无效。

3.不保证原子性

//1.双重检测实现单例
public class SingleInstance {
    
    

    //加volatile 禁止指令重排,因为new SingleInstance() 并不是一个原子性的操作(1.分配内存(默认值) -> 2.为成员变量赋值 ->3.将值指向内存)
    //在超高并发的情况下:第一个线程进行分配内存(此时对象已不为空),第二个线程判断该实例不为空,进行取值时取的是初始化的默认值,并不是第二步的真实值
    public static volatile SingleInstance singleInstance;

    private SingleInstance() {
    
    
    }

    public static SingleInstance getInstance(){
    
    
        if(singleInstance==null){
    
    
            synchronized (SingleInstance.class){
    
    
                if(singleInstance==null){
    
    
                    singleInstance=new SingleInstance();
                }
            }
        }
        return singleInstance;
    }

    public static void main(String[] args) {
    
    
        for (int i = 0; i < 20; i++) {
    
    
            new Thread(()->{
    
    
                System.out.println(SingleInstance.getInstance());
            }).start();
        }
    }
}
//2.通过枚举实现单例
class Resource{
    
    

}
public enum SingleInstanceEnum {
    
    

    INSTANCE;
    
    private Resource resource;
    
    SingleInstanceEnum(){
    
    
        resource=new Resource();
    }
    
    public Resource getInstance(){
    
    
        return resource;
    }

    public static void main(String[] args) {
    
    
        for (int i = 0; i < 20; i++) {
    
    
            new Thread(()->{
    
    
                System.out.println(SingleInstanceEnum.INSTANCE.getInstance());
            }).start();
        }
    }
}

5.CAS(compare and set/swap)无锁优化,自旋

CPU 原语支持:不可被打断

CAS(Expected, NewValue) 循环:当 当前值 刚好为期望的值(Expected)(即没有其他线程进来改过值)时, 值改为新值(NewValue) ,若不为期望值,就表示有线程进来改过值,需要重新读当前值,重新设期望值

  • Expected :期望的值
  • NewValue : 要改为的值

ABA问题:cas会导致ABA问题(一个线程将原值A修改为B值,然后又将B值修改为A值,但对于另一个线程来说并不知道他发生了改变)

基本数据类型:不影响

引用类型: 有影响(通俗的解释为,你的女朋友跟你复合,你女朋友中间经历了n个男人)

解决办法: 加一个版本号vision时间戳

Unsafe类:直接操作jvm内存(类似于c++的指针

所有 atomic 类(如:AtomicInteger)都是通过unsafe类里的 compareAndSwap 来操作的

高并发操作同一个数的方案:

  1. long类型加锁
  2. atomicLong 类
  3. LongAdder (线程数多时有优势,分段锁)

6.lock锁(手动开启和关闭)

jdk1.5开始的
使用Lock锁,jvm将花费较少的时间来调度线程,性能更好,并且具有良好的扩展性(提供更多的子类)

1.java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得锁

可重入锁:锁的对象为同一个时,无需再次获取锁

2.**ReentrantLock(可重入锁)**类实现了Lock,他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,常用ReentrantLock,可以显式加锁,释放锁

使用优先级: lock>同步代码块>同步方法

lock优点:

  1. lock.tryLock(5,TimeUnit.SECONDS) 尝试在5s内获取锁
// lock的使用方式
private final ReentrantLock lock =new ReentrantLock(); //参数可指定公平/非公平锁,默认为非公平锁
try{
    
    
    lock.lock();
    ........
}finally {
    
    
    lock.unlock();
}

JUC 辅助类

1.CountDownLatch :减法计数器 用于等待几个线程结束,countDown为0时唤醒

import java.util.concurrent.CountDownLatch;

//闭锁,用于等待时间
//1.总数为6的计数器
public class CountDownLatchTest {
    
    
    public static void main(String[] args) {
    
    
        //1.创建一个总数为6的计数器
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; i < 6; i++) {
    
    
            new Thread(()->{
    
    
                System.out.println(Thread.currentThread().getName()+"走了");
                //2.计数器减一
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        try {
    
    
            //等待计数器归零时,唤醒countDownLatch.await(),继续向下执行
            countDownLatch.await();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("结束");
    }
}

2.CyclicBarrier 加法计数器(等待线程数满了调用指定动作(BarrierAction),不满就等待)

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

//栅栏:用于等待其他线程(全部完成才会向下执行)
//2.加法计数器

public class CyclicBarrierTest {
    
    
    public static void main(String[] args) {
    
    

        //创建总数为7个的计数器
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
    
    
            System.out.println("成功!!!");
        });
        for (int i = 0; i < 7; i++) {
    
    
            //
            final int temp=i;
            new Thread(()->{
    
    
                System.out.println(Thread.currentThread().getName()+"现在是"+temp);
                try {
    
    
                    //等待集齐7个线程时才会执行下一步
                    cyclicBarrier.await();

                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
    
    
                    e.printStackTrace();
                }
            }).start();
        }

    }
}
3.Semaphore 信号量 限流(限制多个线程 最多只能运行的数量)

​ 例: 4个车道(多个线程进入),2个收费出口(最多只能允许两个线程运行)

import java.util.concurrent.Semaphore;

//3.信号量
//semaphore.acquire()  获得,如果已经满了,将会阻塞等待
//semaphore.release()  释放,将当前信号量+1,同时唤醒等待的线程
//作用,多个共享资源互斥的使用,并发限流,控制最大的线程数

public class SemaphoreTest {
    
    
    public static void main(String[] args) {
    
    
        //参数为线程数量,限流
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 6; i++) {
    
    
            new Thread(()->{
    
    
                try {
    
    
                    //1.获取(Semaphore--),semaphore为0时阻塞
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到了车位");
                    //2.释放(Semaphore++)
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName()+"离开了车位");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

    }
}
4.Phaser : 阶段操作,达到一个阶段后,才会继续往下执行

注册机制:与其他barrier不同的是,Phaser中的“注册的同步者(parties)”会随时间而变化,Phaser可以通过构造器初始化parties个数,也可以在Phaser运行期间随时加入(register)新的parties,以及在运行期间注销(deregister)parties。运行时可以随时加入、注销parties。

package com.example.demo.util;

import java.util.concurrent.Phaser;

public class PhaserTest {
    
    

	public static void main(String[] args) {
    
    
		WorkPhaser workPhaser = new WorkPhaser();
		// 4个工人参与工作
		for (int i = 0; i < 4; i++) {
    
    
			// 注册
			workPhaser.register();
			// 启动线程
			new Thread(new Worker(workPhaser), "work-" + (i + 1)).start();
		}
	}
}

/**
 * 工作阶段器
 */
class WorkPhaser extends Phaser {
    
    

	//所有线程满足条件时,自动调用 onAdvance()方法
	@Override
	protected boolean onAdvance(int phase, int registeredParties) {
    
    
		switch (phase) {
    
    
		case 0:
			System.out.println("第一阶段工作完成,进入第二阶段>>>");
			return false;
		case 1:
			System.out.println("第二阶段工作完成,进入第三阶段>>>");
			return false;
		case 2:
			System.out.println("第三阶段工作完成,退出.");
			return true;
		default:
			return true;
		}
	}
}

/**
 * 工人
 */
class Worker implements Runnable {
    
    

	private WorkPhaser workPhaser;

	public Worker(WorkPhaser workPhaser) {
    
    
		this.workPhaser = workPhaser;
	}

	@Override
	public void run() {
    
    
		String playerName = Thread.currentThread().getName();
		System.out.println(playerName + " 工人完成了第一阶段的工作.");
		// 到达阶段,等待进入下一阶段
		workPhaser.arriveAndAwaitAdvance();

		System.out.println(playerName + " 工人完成了第二阶段的工作.");
		// 到达阶段,等待进入下一阶段
		workPhaser.arriveAndAwaitAdvance();

		System.out.println(playerName + " 工人完成了第三阶段的工作.");
		// 到达阶段,等待进入下一阶段
		workPhaser.arriveAndAwaitAdvance();
	}

}

//   workPhaser.arriveAndDeregister()     //不用等,自己注销

5.ReadWriteLock 读写锁

读写时互斥,单独读时是共享锁(只读不加锁会出现脏读),单独写时为独占锁

package com.it.juc.readWriteLock;

import javax.swing.plaf.IconUIResource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {
    
    
    public static void main(String[] args) {
    
    
        MyCacheLock myCacheLock = new MyCacheLock();

        for (int i = 0; i < 5; i++) {
    
    
            final int temp =i;
            new Thread(()->{
    
    
                myCacheLock.put(temp+"",temp);
            },"A").start();
        }
        for (int i = 0; i < 5; i++) {
    
    
            final int temp =i;
            new Thread(()->{
    
    
                myCacheLock.get(temp+"");
            },"B").start();
        }
    }
}
class MyCacheLock{
    
    
    private volatile Map<String,Object> map=new HashMap<>();

    //读写锁,更加细粒度的控制
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();

    public void put(String key,Object value){
    
    
        readWriteLock.writeLock().lock();
        try {
    
    
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写入完成");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            readWriteLock.writeLock().unlock();
        }
    }
    public void get(String key){
    
    
        readWriteLock.readLock().lock();
        try {
    
    
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取完成");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            readWriteLock.readLock().unlock();
        }
    }
}
6.exchange

俩线程之间交换信息,第一个线程需要等待第二个线程的进入才会交换,等待过程阻塞,交换完继续执行。 (场景:交换装备)

package com.securitit.serialize.juc;

import java.util.concurrent.Exchanger;

public class ExchangerTester {
    
    

	// Exchanger实例.
	private static final Exchanger<String> exchanger = new Exchanger<String>();

	public static void main(String[] args) {
    
    
		// 模拟阻塞线程.
		new Thread(() -> {
    
    
			try {
    
    
				String wares = "红烧肉";
				System.out.println(Thread.currentThread().getName() + "商品方正在等待金钱方,使用货物兑换为金钱.");
				Thread.sleep(2000);
				String money = exchanger.exchange(wares);
				System.out.println(Thread.currentThread().getName() + "商品方使用商品兑换了" + money);
			} catch (InterruptedException ex) {
    
    
				ex.printStackTrace();
			}
		}).start();
		// 模拟阻塞线程.
		new Thread(() -> {
    
    
			try {
    
    
				String money = "人民币";
				System.out.println(Thread.currentThread().getName() + "金钱方正在等待商品方,使用金钱购买食物.");
				Thread.sleep(4000);
				String wares = exchanger.exchange(money);
				System.out.println(Thread.currentThread().getName() + "金钱方使用金钱购买了" + wares);
			} catch (InterruptedException ex) {
    
    
				ex.printStackTrace();
			}
		}).start();
	}

}

transient 关键字

transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。

作用:
Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的

7.死锁(deadlock)

多个线程各自占用有限资源,并且互相等待其他线程占有的资源才能运行,而导致两个多多个线程都在等待对方释放资源,都停止执行的情形,某个同步代码块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”

死锁的4个必要条件

1.互斥条件: 一个资源每次只能被一个进程使用

2.请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放

3.不剥夺条件:进程对以获得的资源,在未使用完之前,不能强行剥夺·

4.循环等待条件:若干进程之间形成一种头尾相交的循环等待资源的关系

8.线程通信:

在这里插入图片描述

9.线程池:

//线程池
public class ThreadPool {
    
    
    public static void main(String[] args) {
    
    
        //1.创建服务,创建线程池
        ExecutorService service = Executors.newFixedThreadPool(5);
        //2.执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //3.关闭连接
        service.shutdownNow();
    }
}
class MyThread implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName());
    }
}

10.生产者消费者模型:

package com.it.MulThread.produceConsumer;

//1.管程法: 利用缓冲区解决

public class PC {
    
    
    public static void main(String[] args) {
    
    
        SynContainer synContainer = new SynContainer();

        new Producer(synContainer).start();
        new Consumer(synContainer).start();
    }
}

class Chicken{
    
    
    int id;

    public Chicken(int id) {
    
    
        this.id = id;
    }
}

class Producer extends Thread{
    
    

    SynContainer synContainer;

    public Producer(SynContainer synContainer){
    
    
        this.synContainer=synContainer;
    }

    //生产
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            synContainer.push(new Chicken(i));
        }
    }
}

class Consumer extends Thread{
    
    

    SynContainer synContainer;

    public Consumer(SynContainer synContainer){
    
    
        this.synContainer=synContainer;
    }

    //消费
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            synContainer.pop();
        }
    }
}

class SynContainer{
    
    
    //容器
    Chicken[] chickens =new Chicken[10];
    //容器计数器
    int count=0;

    //生产者放入产品
    public synchronized void push(Chicken chicken){
    
    
        //如果日期满了
        if(count==chickens.length){
    
    
            try {
    
    
                //生产者等待
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        //没有存满,就生产进容器
        chickens[count++]=chicken;
        System.out.println("生产了"+chicken.id+"只级");

        //通知消费者消费
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Chicken pop(){
    
    
        //判断能否消费
        if(count==0){
    
    
            //等待生产者生产
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Chicken chicken = chickens[count];
        System.out.println("消费了第"+chicken.id+"只级");

        //吃完了,通知生产者生产
        this.notifyAll();
        return chicken;
    }
}

问题:为什么ArrayList线程不安全?

首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。如图,List接口下面有两个实现,一个是ArrayList,另外一个是vector。从源码的角度来看,因为Vector的方法前加了,synchronized 关键字,也就是同步的意思,sun公司希望Vector是线程安全的,而希望arraylist是高效的,缺点就是另外的优点。说下原理(百度的,很好理解):一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:

  1. 在 Items[Size] 的位置存放此元素;
  2. 增大 Size 的值。

在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

线程安全的list : CopyOnWriteArrayList

猜你喜欢

转载自blog.csdn.net/The_xiaoke/article/details/124235213