一、多线程
1.1 进程和线程
- 进程
- 运行中的程序称之为进程
- 特点:
- 在单核CPU下,通过一个时间点上只能有一个程序在运行。交替执行
- 宏观并行、微观串行
- 线程
- 进程组成部分,线程用进程中,单一的执行的顺序控制流程。同时也是CPU的调度单位
- 进程是可以有多个线程的,他们之间独立运行。交替执行。称之为多线程
- 区别
- 进程是系统分配资源的单位,线程是CPU的调度单位
- 一个程序,至少包含一个进程
- 一个进程至少包含一个线程。线程是不能独立运行的,必须依附在进程中
- 进程之间是不能共享数据段地址,但是同一个进程下的线程是可以共享的
1.2 线程的组成部分
- CPU时间片
- 操作系统会为每个线程分配时间
- 运行数据
- 堆空间:存储线程需使用的对象。多个线程可以共享堆中的对象
- 栈空间:存储线程需使用的局部变量。每个线程都拥有自己的栈空间
- 线程的逻辑代码
二、创建线程【重点】
2.1 方式1:通过继承Thread类
public class Demo01 {
public static void main(String[] args) {
//创建并启动线程
//创建线程对象
MyThread t1 = new MyThread();
//启动线程
t1.start();
MyThread t2 = new MyThread();
t2.start();
}
}
/**
* 1、写一个类继承Thread类
* 2、重写run方法,编写线程逻辑代码
* 3、创建并启动线程
*/
//创建方式1:继承Thread类
class MyThread extends Thread{
//重写父类的run方法(线程的逻辑代码)
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//Thread.currentThread()获取当前线程对象 getName(); 获取线程的名称
System.out.println(Thread.currentThread().getName()+"----->"+i);
}
}
}
2.2 方式2:通过实现Runnable接口
public class Demo02 {
public static void main(String[] args) {
//创建并启动线程
//1、创建线程对象
Thread t1 = new Thread(new MyThread2());
t1.start();
Thread t2 = new Thread(new MyThread2());
t2.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
/**
* 1、写一个类继承实现Runnable接口
* 2、重写run方法,编写线程逻辑代码
* 3、创建并启动线程
*/
//创建方式2:实现Runnable接口
class MyThread2 implements Runnable{ //并不是一个线程类,只是一个线程任务
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
2.3 两种方式的区别
两种方式的区别:
- 1、java只支持单继承,所以继承Thread类就没办法继承其他的类,所以使用Runnable接口更灵活
- 2、继承自Thread类,表示这个类就是一个线程类,可以直接启动线程实现Runnable接口,表示这个类是一个线程任务,需要创建线程对象从而执行这个线程任务
2.4 启动线程需要注意的问题
- 1、不要调用run方法
- 2、一个线程只能调用一次start方法
2.5 线程的状态(基本)
初始状态 ------ 就绪状态 ------- 运行状态 -------- 终止状态
2.6 线程常见的方法
- 1、设置线程名称 (setName、getName、Thread.currentThread获取当前线程对象)
- 如果没有设置线程名称,那么默认的名称为Thread-0 Thread-N
- 2、设置线程的优先级 (setPriority、getPriority)
- 如果没有设置线程的优先级,那么默认的优先级为5
- 线程有的优先级为1~10之间,设置优先级只是提高了抢占CPU的概率
- 3、线程休眠 (Thread.sleep(毫秒数))
- 让当前线程进入到休眠状态,并让出CPU使用权,直到休眠结束,才会继续抢占CPU
- 4、线程礼让(Thread.yeild())
- 让出CPU使用权,但是立马又会去重新抢占CPU
- 5、线程加入(join())
- 在当前线程中加入另一线程,必须要将另一个线程执行完之后才会继续执行当前线程
2.7 线程的状态(等待)
三、线程安全
3.1 线程安全问题
当多线程并发访问临界,如果破坏原子操作,可能会造成数据不一致
临界资源:共享资源(同一个对象),一次只可以有一个线程操作,才可以保证准确性
原子操作:不可拆分的步骤,被视作一个整体。其步骤不能打乱和缺省
3.2 线程同步
- 方式一:同步代码块
- synchronized(临界资源对象){ //互斥锁标记
- //原子代码
- }
- 方式二:同步方法
- public synchronized void sale(){ //互斥锁标记是this对象
- //原子代码
- }
3.3 线程同步卖票案例
3.3.1 线程不安全
- 循环中的代码并非原子操作,所以在线程执行的过程中,会有其他线程执行,会造成ticket < 0无效
- ticket-- 并非原子操作,其包含有三个步骤
- 1、从内存中(主内存)拿ticket变量
- 2、将ticket-1(工作内存)
- 3、将结果赋值给内存中(主内存)
class TicketRunnable implements Runnable{
int ticket = 100;
@Override
public void run() {
while(true) {
if(ticket < 0) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取线程的名称
System.out.println(Thread.currentThread().getName()+"卖出了:"+ ticket-- +"号票");
}
}
}
3.3.2 同步代码块解决
class TicketRunnable implements Runnable{
int ticket = 50;
final Object obj = new Object();
@Override
public void run() {
while(true) {
synchronized (obj) { //互斥锁对象(可以是任意的java对象,但是要保证对象唯一)
if(ticket < 0) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取线程的名称
System.out.println(Thread.currentThread().getName()+"卖出了:"+ ticket-- +"号票");
}
}
}
}
3.3.3 同步方法解决
class TicketRunnable implements Runnable{
int ticket = 50;
final Object obj = new Object();
@Override
public void run() {
while(true) {
sale();
}
}
//同步方法的锁对象是this,所以要保证当前对象的唯一
public synchronized void sale() {
if(ticket < 0) {
System.exit(0);
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取线程的名称
System.out.println(Thread.currentThread().getName()+"卖出了:"+ ticket-- +"号票");
}
}
四、线程的生命周期
线程的5种状态:创建状态---->就绪状态---->运行状态---->阻塞\等待状态---->终止状态
五、线程死锁
当第一个线程拥有A对象的锁标记,并等待B对象的所标记。同时第二个线程拥有B对象锁标记,同时等待A对象的锁标记时,产生死锁
public class TestDeadLock {
public static void main(String[] args) {
Boy boy = new Boy();
Gilr gilr = new Gilr();
boy.start();
gilr.start();
}
}
class MyLock{
static Object obj1 = new Object(); //左筷子
static Object obj2 = new Object(); //右筷子
}
class Boy extends Thread{
@Override
public void run() {
synchronized (MyLock.obj1) { //拥有左筷子 锁
System.out.println("boy获取到了左筷子,等待右筷子");
synchronized (MyLock.obj2) {
System.out.println("boy可以吃饭");
}
}
}
}
class Gilr extends Thread{
@Override
public void run() {
synchronized (MyLock.obj2) {
System.out.println("Gilr拥有右筷子,等待左筷子");
synchronized (MyLock.obj1) {
System.out.println("Gilr可以吃饭");
}
}
}
}
六、线程通信
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。
- wait()
- 当前线程释放锁对象, 并处于阻塞状态,进入等待队列,直到有别人唤醒
- notify()、notifyAll()
- 随机唤醒一个正在等待的线程
- 唤醒所有等待队列中的线程
**[注意:所有的等待、通知方法必须在对加锁的同步代码块中
public class TestProducerAndComsuer {
static Shop shop = new Shop();
public static void main(String[] args) {
ProducerThread producerThread = new ProducerThread();
ConsumerThread consumerThread = new ConsumerThread();
producerThread.start();
consumerThread.start();
}
}
/**
* 商品
*/
class Phone{
String name;
}
/**
* 缓冲区
*/
class Shop{
//表示买卖的商品 如果为null表示没有商品需要生产,否则表示已有商品需要消费
Phone phone;
//进货 (生产)
public synchronized void putPhone(Phone phone) { //参数表示进货的商品
if(this.phone != null) { //表示有商品,需要等待
try {
System.out.println("表示有商品,需要等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//表示没有商品需要生产
System.out.println("正在生产商品"+phone.name);
//1、模拟生产商品
this.phone = phone;
//2、通过消费者进行消费
this.notify();
}
//卖货(消费)
public synchronized void getPhone() {
if(this.phone == null) { //表示没有商品需要等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//表示有商品需要消费
System.out.println("正在消费商品"+ phone.name);
//1、模拟消费商品
this.phone = null;
//2、通知生产者生产
this.notify();
}
}
/**
* 消费者线程
*/
class ConsumerThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
TestProducerAndComsuer.shop.getPhone();
}
}
}
/**
* 生产者线程
*/
class ProducerThread extends Thread{
@Override
public void run() {
for (int i = 10; i < 20; i++) {
Phone phone = new Phone();
phone.name = "IPhone"+i;
TestProducerAndComsuer.shop.putPhone(phone);
}
}
}
七、线程池
7.1 线程池概念
- 如果有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样频繁的创建和销毁线程。
- 频繁创建和销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用
- 线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。
7.2 线程池实现原理
7.3 线程池中常见的类
常用的线程池接口和类(所在包java.util.concurrent)。
Executor:线程池的顶级接口。
ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。
Executors工厂类:通过此类可以获得一个线程池。
方法名 | 描述 |
---|---|
newFixedThreadPool(int nThreads) | 获取固定数量的线程池。参数:指定线程池中线程的数量。 |
newCachedThreadPool() | 获得动态数量的线程池,如不够则创建新的,无上限。 |
public class TestThreadPool1 {
public static void main(String[] args) {
//1、获取线程池对象 线程池数量为3
ExecutorService es = Executors.newFixedThreadPool(3);
//2、通过线程池提交并执行线程任务 (线程会自动启动线程并执行线程任务(执行run方法))
es.submit(new MyTask());
es.submit(new MyTask());
es.submit(new MyTask());
es.submit(new MyTask());
//3、关闭线程池 (当所有的线程任务都执行完成之后关闭)
es.shutdown();
}
}
class MyTask implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
public class TestThreadPool2 {
public static void main(String[] args) {
//1、创建线程池对象 动态个数的线程池 (如果线程任务执行完,会执行下一个线程任务)
ExecutorService es = Executors.newCachedThreadPool();
//2、通过线程池对象启动并执行线程任务
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
});
//3、关闭线程池
es.shutdown();
}
}
7.4 线程池 ThreadPoolExecutor类
线程池7大核心参数
- corePoolSize
- 线程池中常驻核心线程数
- maximumPoolSize
- 线程池能够容纳同时执行的最大线程数,此值必须大于1
- keepAliveTime
- 多余空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到剩下corePoolSize为止。
- unit
- keepAliveTime的单位
- workQueue
- 里面放了被提交但是尚未执行的任务
- ①ArrayBlockingQueue
- ②LinkedBlockingQuene
- ③SynchronousQuene
- ④PriorityBlockingQueue
- 里面放了被提交但是尚未执行的任务
- threadFactory
- 表示线程池中工作线程的线程工厂,用于创建线程
- handler
- 拒绝策略,当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时,对任务的拒绝方式。
- ①CallerRunsPolicy
- 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
- ②AbortPolicy
- 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
- ③DiscardPolicy
- 该策略下,直接丢弃任务,什么都不做。
- ④DiscardOldestPolicy
- 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
- ①CallerRunsPolicy
- 拒绝策略,当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时,对任务的拒绝方式。
7.5 Callable接口
- JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
- Callable具有泛型返回值、可以声明异常。
public interface Callable< V >{
public V call() throws Exception;
}
public class TestCallable1 {
public static void main(String[] args) {
//1、创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
//2、通过线程池提交线程并执行任务
es.submit(new MyCallable());
//3、关闭线程池
es.shutdown();
}
}
class MyCallable implements Callable{//此泛型规定了方法的返回值
@Override
public Object call() throws Exception {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
return null;
}
}
7.6 Future接口
- Future接口表示将要执行完任务的结果。
- get()以阻塞形式等待Future中的异步处理结果(call()的返回值)。
public class TestCallable2 {
/**
* Runnable接口和Callable接口的区别?
* 1、这两个接口都可以当做线程任务提交并执行
* 2、Callable接口执行完线程任务之后有返回值,而Runnable接口没有返回值
* 3、Callable接口中的call方法已经抛出了异常,而Runnable接口不能抛出编译异常
* Future接口:
* 用于接口Callable线程任务的返回值。
* get()方法当线程任务执行完成之后才能获取返回值,这个方法是一个阻塞式的方法
*
* 随堂案例:
* 使用两个线程,并发计算1-100的和, 一个线程计算1~50,另一个线程计算51~100, 最终汇总结果
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
//1、创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
//2、通过线程池提交线程并执行任务
Future<String> future = es.submit(new MyCallable1());
//获取线程任务的返回值
System.out.println(future.get());
System.out.println("哈哈哈哈哈哈");
//3、关闭线程池
es.shutdown();
}
}
class MyCallable1 implements Callable<String>{
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
return "这是Callable线程任务的返回值";
}
}
案例:计算1-1000结果,使用四个线程分别计算?即:第一个线程计算1-250 第二个 251~500 …
public class Test01 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//1、创建线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//2、提交两个线程任务
Future<Integer> f1 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 51; i++) {
sum = sum + i;
}
return sum;
}
});
Future<Integer> f2 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 51; i < 101; i++) {
sum += i;
}
return sum;
}
});
System.out.println(f1.get() + f2.get());
//3、关闭线程池
es.shutdown();
}
}
八、 Lock锁
- JDK5加入,与synchronized比较,显示定义,结构更灵活。
- 提供更多实用性方法,功能更强大、性能更优越。
8.1 Lock锁
- Lock接口的实现类,与synchronized一样具有互斥锁功能。
public class TestLock {
public static void main(String[] args) {
//1、创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(4);
//2、通过线程池提交线程任务
TicketTask task = new TicketTask();
es.submit(task);
es.submit(task);
es.submit(task);
es.submit(task);
//3、关闭线程池
es.shutdown();
}
}
class TicketTask implements Runnable{
static int ticket = 30;
//jdk1.5之后加入
Lock lock = new ReentrantLock();//重入锁
Object obj = new Object();
@Override
public void run() {
while(true) {
try {
lock.lock(); //上锁
if(ticket < 0) {
break;
}
//System.out.println(10/0);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->卖出了"+ticket--+"号票");
}finally {
lock.unlock(); //释放锁
}
}
}
}
8.2 读写锁
ReentrantReadWriteLock:
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
- 支持多次分配读锁,使多个读操作可以并发执行。
互斥规则:
- 写-写:互斥,阻塞。
- 读-写:互斥,读阻塞写、写阻塞读。
- 读-读:不互斥、不阻塞。
- 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
public class TestReadWriteLock {
static User user = new User();
public static void main(String[] args) {
//创建一个大小为10的线程池
ExecutorService es = Executors.newFixedThreadPool(10);
long start = System.currentTimeMillis();
//8个线程读 2个线程写
for (int i = 0; i < 2; i++) {
es.submit(new WriteThread());
}
for (int i = 0; i < 8; i++) {
es.submit(new ReadThread());
}
es.shutdown();
//判断线程池中的任务是否执行结束,如果结束了返回true
//System.out.println(es.isTerminated());
//代码空转
while(true) {
if(es.isTerminated() == true) {
break;
}
}
long end = System.currentTimeMillis();
System.out.println("耗时为:"+(end-start));
}
}
class ReadThread implements Runnable{
@Override
public void run() {
System.out.println(TestReadWriteLock.user.getName());
}
}
class WriteThread implements Runnable{
@Override
public void run() {
TestReadWriteLock.user.setName("cxk");
}
}
class User{
String name;
//创建读写锁对象
ReadWriteLock rwl = new ReentrantReadWriteLock();
//读锁
Lock readLock = rwl.readLock();
//写锁
Lock writeLock = rwl.writeLock();
public void setName(String name) {
try {
writeLock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name;
} finally {
writeLock.unlock();
}
}
public String getName() {
try {
readLock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return name;
}finally {
readLock.unlock();
}
}
}
8.3 重入锁
重入锁:
-
重入锁也叫作递归锁,指的是同一个线程外层函数获取到一把锁后,内层函数同样具有这把锁的控制权限
-
synchronized和Lock锁都可以实现锁的重入
public class TestReentrantLock {
public static void main(String[] args) {
//启动线程
new Thread(new MyRunnable()).start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
a();
}
//当前锁对象为this
public synchronized void a() {
System.out.println("a");
b();
}
//当前锁对象为this
public synchronized void b() {
System.out.println("b");
c();
}
public synchronized void c() {
System.out.println("c");
}
}
8.4 公平锁
公平锁和非公平锁
- 非公平锁:优先使用上一个线程接着执行下面的线程任务
- synchronized是非公平锁的实现,无法实现公平锁
- lock锁默认是非公平锁,如果想要实现公平锁,那么需要在构造方法设置为true
- 公平锁:让每个线程都公平去执行线程任务
- lock锁可以实现公平锁
- synchronized无法实现公平锁
//Lock锁实现公平锁 参数为true表示是公平锁,默认是false表示非公平锁
Lock lock = new ReentrantLock(true);
8.5 synchronized锁升级
锁的状态总共有四种
- 无锁
- 当锁对象被创建,还没有线程进入的时候,此时锁对象处于无锁状态
- 偏向锁
- 当有线程A进入同步代码并获得锁对象,此时会保存线程ID。以后此线程A进入到同步代码中则无需使用CAS加锁或者释放锁。只需要验证线程ID即可。
- 轻量级锁(自旋锁)
- 当前锁处于偏向锁,此时后线程B进入到同步代码。这是线程AB会使用CAS竞争,并升级为轻量级锁
- 重量级锁
- 如果线程没有获得轻量级锁,线程会通过CAS自旋获取锁对象,如果自旋次数大于阈值(10次),则升级为重量级锁
Java对象头(MarkWord) |
---|
锁升级过程 |
九、线程安全的集合
9.1 集合结构
9.2 通过Collections获取线程安全集合
Collections工具类中提供了多个可以获得线程安全集合的方法。
方法名 |
---|
public static Collection synchronizedCollection(Collection c) |
public static List synchronizedList(List list) |
public static Set synchronizedSet(Set s) |
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) |
public static SortedSet synchronizedSortedSet(SortedSet s) |
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m) |
9.3 CopyOnWriteArrayList
- 线程安全的ArrayList,加强版读写分离。
- 写有锁,读无锁,读写之间不阻塞,优于读写锁。
- 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
- 使用方式与ArrayList无异。
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();
9.4 CopyOnWriteArraySet
- 线程安全的Set,底层使用CopyOnWriteArrayList实现。
- 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组。
- 如存在元素,则不添加(扔掉副本)。
CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<>();
9.5 ConcurrentHashMap
JDK1.7实现
- 初始容量默认为16段(Segment),使用分段锁设计。
- 不对整个Map加锁,而是为每个Segment加锁。
- 当多个对象存入同一个Segment时,才需要互斥。
- 最理想状态为16个对象分别存入16个Segment,并行数量16。
- 使用方式与HashMap无异。
JDK1.8实现
- 内部使用CAS交换算法+synchronized实现多线程并发安全
- CAS有三个值
- X:要更新的值
- Y:预期值
- Z:新值
- 当X==Y的时候才会去修改,当 X!=Y的时候则放弃修改
- ABA问题
- 当Y值经过多次修改之后,又被回到了原来的Y值。此时就是ABA问题
- 使用版本号解决
ConcurrentHashMap与HashMap的比较 案例
public class Demo02 {
public static void main(String[] args) {
//HashMap<String,String> map = new HashMap<String, String>();
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>(23);
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
es.submit(new Runnable() {
@Override
public void run() {
//向map集合中添加10个元素
for (int j = 0; j < 10; j++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put("key"+j, "value"+j);
}
}
});
}
es.shutdown();
while(!es.isTerminated()) {}
System.out.println(map);
}
}