Java并发
一,进程和线程
进程和线程都是CPU调用任务的概念。
进程:系统是系统分配资源的最小单位,进程包含1到多的线程,进程内的线程,可以有空闲的内存。
线程:系统调度任务的最小单位。
二,并发和并行
CPU是按进度顺序执行的,并发本质吧上是串行执行的。由于操作系统任务调度器的存在,使得多线程以分配CPU时间片的方式,实现并发执行。
并发:多线程轮流使用CPU
并行:多线程在多CPU的加持下,同时执行。
三,Java线程
1,创建线程
-
使用new Thread类
//创建线程 Thread thread = new Thread(){ @Override public void run() { super.run(); } }; //启动线程 thread.start();
-
使用 Runnable 配合 Thread
public class test2 { public static void main(String[] args) { //新建Runable的实现类 Runnable runnable = new myrunable(); //创建线程 Thread thread = new Thread(new myrunable(),"runable线程"); //启动线程 thread.start(); } } //新建实现Runable接口的类 class myrunable implements Runnable{ @Override public void run() { System.out.println("我是Runable方法创建"); } }
-
使用Callable创建,Callable可以有返回值,使用FuturnTask类接受返回值
public class Test3 { public static void main(String[] args) throws ExecutionException, InterruptedException { //创建Callable的实现类 Callable callable = new MyCallable(); //创建FuturnTask对象,并并绑定Callable对象,用来创建线程和接受返回值 FutureTask futureTask = new FutureTask(callable); //创建线程 Thread thread = new Thread(futureTask,"callable线程"); //启动线程 thread.start(); //获取线程完成后的返回值 futureTask.get(); } } //新建实现Callable的类 class MyCallable implements Callable{ @Override public Object call() throws Exception { System.out.println("我是callable创建"); return new Object(); } }
2,线程常见的方法
- start():开启一个线程,自动运行线程中的Run方法
- Run():线程启动后,会调用的方法。
- join(long n) : 将线程加入当前线程,当前线程在加入线程结束后,继续执行
- getId():获取整形的id编号值
- getName():获取线程名
- getPriority():获取优先级别
- setPriority():设置线程的优先级别
- getState() :获取线程状态(new(创建),RUNABLE(就绪),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(定时等待),TERMINATED(结束))
- isInterrupted():判断线程是否挂起中断标志,挂起返回turn,否则false
- interrupt() : 打断进程,线程不会结束,而是挂起中断标志。线程在等待(sleep wait 等到join结束),抛出中断异常
- interrupted() : 判断线程是否被打断,会清除中断标志
- yield() : 让出cpu使用权给相同优先级的线程,不释放锁,线程不会进入阻塞,而是就绪状态,等他重新获得CPU的时间片
3,线程结束
- Stop方法:结束线程,不会释放资源,程序可能出错,以弃用
- System.exit():结束所有线程
- 使用中断标记,判断标记后,结束程序,结束线程
4,主线程和守护线程
- 主线程:前台线程,我们使用的线程,当全部前台线程结束后,主线程才会结束
- 守护线程:主线程结束后,才会结束。垃圾回收是守护线程
5,线程状态
创建:虚拟机加载程序,创建了线程
就绪:虚拟机给线程分配好了资源,准备被cpu使用
运行:线程获得CPU时间片,在执行中
结束:线程zhongz
阻塞:
- 线程睡眠sleep():指定时间阻塞,不释放锁
- 线程让步yield(): 不进入阻塞,进入就绪。
- 线程融合 join():等待join方法结束
- 线程等待(同步阻塞)wait() 和 notify():wait(Long n) 释放锁,进入定时等待状态,结束后再入锁池,等他重新获得锁,获得CPU时间片。notify()唤醒等待的线程,进入就绪状态,重新获取锁。
- 线程暂停LockSuppert.park()线程暂停,LockSuppert.unpark() 线程恢复
四,共享模型之锁(管程/Monitor)线程锁synchronized
1,synchronized原理
java中的用来上锁的是对象,java对象创建有mark word信息,在mark文件中标记,当前那个线程在使用该对象,从而实现上锁
2,Monitor 原理
Monitor 被翻译为监视器或管程
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向 Monitor 对象的地址
3 , java中的锁
Java锁具体可分为悲观锁/乐观锁、自旋锁/适应性自旋锁、偏向锁、轻量级锁/重量级锁、公平锁和非公平锁、可重入锁/非可重入锁、共享锁/排他锁
具体划分如下:
悲观锁和乐观锁
-
悲观锁:认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。
-
乐观锁:认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据(可以使用版本号机制和CAS算法实现)。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)
-
应用场景
- 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
- 传统的关系型数据库使用了很多悲观锁机制如行锁,表锁都是操作之前先上锁
- Java中的synchronized和ReentrantLock体现的就是悲观锁
- 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升
- java.util.concurrent.atomic包下面的原子变量类就是基于CAS实现的乐观锁
-
乐观锁的缺点
-
ABA问题
如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。
J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题, 它可以通过控制变量值的版本来保证 CAS 的正确性。 大部分情况下 ABA 问题不会影响程序并发的正确性, 如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。 -
自旋时间长开销大
自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用, 第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源, 延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。 第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation) 而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
-
只能保证一个共享变量的原子操作 CAS只对单个共享变量有效,当操作涉及跨多个共享变量时CAS无效。 但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性, 可以把多个变量封装成对象里来进行 CAS 操作. 所以我们可以使用锁或者利用AtomicReference类把多个共享变量封装成一个共享变量来操作。
-
自旋锁和适应性自旋锁
阻塞或者唤醒一个JAVA的线程需要操作系统切换CPU状态来完成,这种状态的转换需要耗费处理器时间。如果同步代码块中的内容过于简单,很可能导致状态转换消耗的时间比用户代码执行的时间还要长。所以在短暂的等待之后就可以继续进行的线程,为了让线程等待一下,需要让线程进行自旋,在自旋完成之后,前面锁定了同步资源的线程已经释放了锁,那么当前线程就可以不需要阻塞便直接获取同步资源,从而避免了线程切换的开销。这就是自旋锁。
-
自旋锁的优缺点
在资源被其它线程锁住是,不进行阻塞,而是自旋等待资源被释放。
优点: 需要同步代码块的执行时间很短,避免了CPU切换线程造成的性能和时间的浪费。
缺点: 需要同步的代码执行时间太长,线程自旋造成CPU空转等待时间太长,浪费夏普性能
-
适应性自旋锁
自适应意味着自旋的时间不在是固定,而是根据前次自旋的时间和有锁的线程状态来确定。如果自旋的线程刚成功获得锁,并且线程正在运行。虚拟认为获得锁的概率极大,允许自旋更长的时间。反之,自旋获得锁的成功很少,虚拟机避免自旋,直接阻塞线程。
无锁和偏向锁
- 无锁
- 无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
- 无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。
- 偏向锁
- 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
- 在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。
- 在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
- 偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。
轻量级锁和重量级锁
当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
公平锁和非公平锁
- 公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
- 非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,如果的锁的状态可用,那么该线程会跳过所有等待直接获取锁,相当于有插队行为
- 优缺点:
可重入锁 和非可重入锁
- 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁
- 不可重入锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
- 不可重入锁,可能存在被当前线程所持有,且无法释放的死锁问题
独享锁VS共享锁
- 独享锁:该锁每一次只能被一个线程所持有,也叫排他锁,获得排他锁的 线程既能读数据又能写数据
- 共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占
- 独享锁与共享锁时通过AQS来实现 的
五,线程池
概念:是一种池话思路,通过重复利用已经创建的线程,减小线程创建和销毁带来的开销。使用线程时不需要创建,提高了响应速度。合理的管理线程资源,提高系统稳定性。
1,创建线程池的方法(ThreadPoolEXcutor类)
//使用ThreadPoolExcutor创建线程池
ExecutorService service = new ThreadPoolExecutor(
20, //corePoolSize : 核心线程数
30, //maximumPoolSize:最大线程数
2000, //keepAliveTime:线程存活时间
TimeUnit.MILLISECONDS, //unit:线程存活时间单位
new ArrayBlockingQueue<Runnable>(10),//workQueue:任务队列
new ThreadFactory() { //threadFactory:线程工厂
@Override
public Thread newThread(Runnable r) {
return null;
}
},
new ThreadPoolExecutor.AbortPolicy() //handler:拒绝策略
);
//给线程池提交任务,以Runable的w为任务参数
threadPool.execute(new Runnable() {
@Override
public void run() {
}
});
//关闭线程词
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow();// 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
ThreadPoolEXcutor构造方法的几个参数:
- corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- maximumPoolSize(必须):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞
- keepAliveTime(必须):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为 true 时,核心线程也会超时回收。
- unit(必须):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
- workQueue(必须):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
- ThreadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
- handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
2,线程池工作原理
3,线程池特别参数解释
-
任务队列(workQueue)
任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在 Java 中需要实现 BlockingQueue 接口。但 Java 已经为我们提供了 7 种阻塞队列的实现:
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
- LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
- PriorityBlockingQueue: 一个支持优先级排序的*阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
- DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的*优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
- SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
- LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
- LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是*的阻塞队列。
-
线程工厂(threadFactory)
线程工厂指定创建线程的方式,需要实现 ThreadFactory 接口,并实现 newThread(Runnable r) 方法。该参数可以不用指定,Executors 框架已经为我们实现了一个默认的线程工厂:
new ThreadFactory() { //threadFactory:线程工厂 @Override public Thread newThread(Runnable r) { return null; } }
-
拒绝策略(handler)
当线程池的线程数达到最大线程数时,需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过 Executors 框架已经为我们实现了 4 种拒绝策略:
AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。new ThreadPoolExecutor.AbortPolicy(); //丢弃任务并抛出 RejectedExecutionException 异常。 new ThreadPoolExecutor.CallerRunsPolicy(); //由调用线程处理该任务。 new ThreadPoolExecutor.DiscardPolicy(); //丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。 new ThreadPoolExecutor.DiscardOldestPolicy();//丢弃队列最早的未处理任务,然后重新尝试执行任务。
六,Volatile关键字
volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。
1,并发编程的3个概念
- 原子性:一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
- 可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
- 有序性:即程序执行的顺序按照代码的先后顺序执行。
2,Java的内存模型JMM和共享变量的可见性
jmm在处理线程共享变量时,有两个抽象的概念,线程本地内存和主内存。线程共享变量时存在主内存中,每个线程的本地内存有一个主内存的拷贝数据。当线修改共享变量时,是先修改线程本地内存里的数据,在同步到主内存。同步存在一个时间差,多线程修改可能出现同步数据错误。Volatile关键字可以使主内存对线程内存可见,避免了同步时间导致的数据错误
volatile保证了可见性,没有保障原子性。
七,可重入锁ReentrantLock
ReentrantLock继承了Lock接口,在内部类中定义了Sync类继承AQS类,Sync被NonfairSunc和faisSync继承。
ReentrantLock类中有很多的方法:
- getHoldCount():当前线程调用 lock() 方法的次数
- getQueueLength():当前正在等待获取 Lock 锁的线程的估计数
- getWaitQueueLength(Condition condition):当前正在等待状态的线程的估计数,需要传入 Condition 对象
- hasWaiters(Condition condition):查询是否有线程正在等待与 Lock 锁有关的 Condition 条件
- hasQueuedThread(Thread thread):查询指定的线程是否正在等待获取 Lock 锁
- hasQueuedThreads():查询是否有线程正在等待获取此锁定
- isFair():判断当前 Lock 锁是不是公平锁
- isHeldByCurrentThread():查询当前线程是否保持此锁定
- isLocked():查询此锁定是否由任意线程保持
- tryLock():线程尝试获取锁,如果获取成功,则返回 true,如果获取失败(即锁已被其他线程获取),则返回 false
- tryLock(long timeout,TimeUnit unit):线程如果在指定等待时间内获得了锁,就返回true,否则返回 false
- lockInterruptibly():如果当前线程未被中断,则获取该锁定,如果已经被中断则出现异常
八,同步和并发容器
- 同步容器:保证所有访问容器资源的线程,串行同步执行。缺点多个线程竞争容器锁,严重降低了并发效率。
- 并发容器:尽量避免使用锁,提高容器的并发访问性。
1,同步容器
- HashTable:
- 表示键/值对的集合,类似Map
- Key不能为null,Value可以为null
- Hashtable它是实现了IDictionary和ICollection接口的
- key与value都是object类型的,不支持泛型
- Vector:
- 实现了List接口的安全list
- 对线程安全的实现在于给add(),set(),get()等方法加了synchronize,使得方法共用一个锁
- Collections.synchronizedXXX
- XXX:可以为 Collection List Set Map
- 本质是对相应的容器进行包装
2,并发容器
- 概述
- ConcurrentHashMap:线程安全的HashMap
CopyOnWriteArrayList:线程安全的List
BlockingQueue:接口,表示阻塞队列,非常适合用于作为数据共享的通道。
ConcurrentLinkedQueue :高效的非阻塞并发队列,使用链表实现。可以看作是一个线程安全的LinkedList。
ConcurrentSkipListMap:是一个Map,使用跳表的数据结果进行快速查找。
- ConcurrentHashMap:线程安全的HashMap
- concurrenthashmap
- CopyOnWriteArrayList(适合读多写少的场景)
- 实现原理
- 修改的时候将内存中的值复制一份,对复制的数据进行操作。修改完成后将内存指向这个地址。
- 这样就可以实现在读的同时实现修改的操作。提高读效率,由于写的次数很少,就不用担心读取不到最新的数据。
- **创建新副本,读写分离。**最后再替换回去。
- “**不可变”**原理,旧的容器不会发生改变。
- 适用场景
- 读操作尽可能的快,写操作慢一点也没有问题。例如:黑名单,每日更新……
- 读写锁规则的升级:读取是不用加锁的,并且更厉害的是**,写入也不会阻塞读取操作**,只有写入和写入之间需要进行同步操作。
- 缺点
- 数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的数据能马上得到,就不要使用CopyOnWrite.
- 内存占用问题:因为CopyOnWrite的写是复制机制,所以再进行写操作的时候,内存里会同时驻扎两个对象。
- 实现原理
九,原子类Atomic
Atomic:是java JUC包下的Atomic包,以CAS为核心,在多线程的环境下,避免线程阻塞,以自旋的方式等待锁空闲。自旋适合占用锁时间较短,避免CPU任务调度造成的资源浪费
CAS:
- 原理概述:CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
- CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。 UnSafe类的
objectFieldOffset()
方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。另外value是一个volatile
变量,在内存中可见,因此JVM可以保证任何时刻任何线程总能拿到该变量的最新值 -
AtomicInteger
类主要利用CAS
(compare and swap
) +volatile
和 native方法来保证原子操作,从而避免synchronized的高开销,执行效率大为提升。
1,基本数据类型
-
AtomicInteger
-
AtomicLong
-
AtomicBoolean
-
*三种类的方法基本一样,下面就以AtomicInteger 为例说明常用方法
public final int set(); //设一个值 public final int get() //获取当前的值 public final int getAndSet(int newValue)//获取当前的值,并设置新的值 public final int getAndIncrement()//获取当前的值,并自增 public final int getAndDecrement() //获取当前的值,并自减 public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 boolean compareAndSet(int expect, int update) //如果当前值等于预期值,则以原子方式将该值设置为输入值(update) public final void lazySet(int newValue) //最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
2,数组类型:使用原子的方式更新数组中某个元素
-
AtomicIntegerArray
-
AtomicLongArray
-
AtomicReferenceArray
-
三种类的方法基本一样,下面就以AtomicIntegerArray 为例说明常用方法
public final int get(int i) //获取 index=i 位置元素的值 public final int set(int i, int newValue) //为 index=i 位置元素设新值 public final int getAndSet(int i, int newValue) //返回 index=i 位置的当前的值,并将其设置为新值:newValue public final int getAndIncrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自增 public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减 public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值 boolean compareAndSet(int i, int expect, int update) //如果index=i 位置的值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update) public final void lazySet(int i, int newValue) //最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
3. 引用类型:使用原子的方式更新某个对象
-
AtomicReference:引用类型原子类,引用的是复合类的数据。
-
AtomicStampedReference:AtomicReference的扩展版,增加了一个参数stamp标记,这里是为了解决了AtomicInteger和AtomicLong的操作会出现ABA问题。
-
AtomicMarkableReference
:与AtomicStampedReference差不多,只不过第二个参数不是用的int作为标志,而用boolean类型做标记,具体用法看后面讲解。 -
解释:
-
AtomicReference类,方便对复合类型的数据进行原子操作
-
AtomicStampedReference类,为了避免,ABA问题,引入了一个新的整形参数Stamp。只有当数据和参数都相同是,才认为一直
-
AtomicMarkableReference,和Stamped基本一致,maekable参数类型为布尔型
//定义类对象数据 User user1 = new User("金厂长",35); User user2 = new User("木易阿婆",26); /*****引用Atomic类******/ //新建引用类对象 AtomicReference atomicReference = new AtomicReference(User.class); //设置值 atomicReference.set(user1); //获取值 atomicReference.get(); //比较值,期望值和对象值一直,则修改 atomicReference.compareAndSet(atomicReference.get(),user2); //输出值 System.out.println(atomicReference.get()); /*******引用带盖戳类************/ //新建类对象 AtomicStampedReference<User> atomicStampedReference = new AtomicStampedReference<>(user1,10); //期望值和对象值一样,包括Stamp一直,则修改 atomicStampedReference.compareAndSet(atomicStampedReference.getReference(),user2,atomicStampedReference.getStamp(),10); //获取值 System.out.println("数据值wei: " + atomicStampedReference.getReference()); System.out.println("Stamp值为: "+atomicStampedReference.getStamp()); /*****AtomicMarkableReference*****/ //和AtomicStampedReference基本一张,Markable类型为布尔值 AtomicMarkableReference<User> atomicMarkableReference = new AtomicMarkableReference<>(user1,false);
-
4, 对象的属性修改类型:使用原子的方式更新某个类中某个字段
-
AtomicIntegerFieldUpdater:原子更新整形字段的更新器
-
AtomicLongFieldUpdater:原子更新长整形字段的更新器
-
AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器
-
解释:
-
用来修改对象的属性,可以修改的属性类型是固定的。
-
能被修改的属性 不能是私有的 且必须配volatile修饰
-
参考使用
User类只有两个属性 volatile int age 和 volatile String name
//定义类对象数据 User user1 = new User("金厂长",35); User user2 = new User("木易阿婆",26); //新建修改引用类型类和int类 AtomicReferenceFieldUpdater<User,java.lang.String> arfu = AtomicReferenceFieldUpdater.newUpdater(User.class,java.lang.String.class,"name"); AtomicIntegerFieldUpdater<User> aifu = AtomicIntegerFieldUpdater.newUpdater(User.class,"age"); //修改数据 arfu.getAndSet(user1,"王老板"); aifu.addAndGet(user2,5); System.out.println(user1); System.out.println(user2);
-
5,原子累加器:JDK1.8之后出现, AtomicLong和AtomicDouble的升级类型
- 专门用于数据统计,性能更高
十,生产消费模式
理解:多线程工作,有产生数据,有消费数据,生产者消费者之间通过共享内存交换数据。
注意:
- 数据池大小是确定的,当数据池满,暂停生产者,阻塞生产线程,唤醒消费线程
- 数据池大小是确定的,当数据池空,暂停消费者,阻塞消费线程,唤醒生产线程
- 确保数据安全,保证线程安全。
设计思路:
-
以数据词为核心,对外提供两个结构,消费和生产,为确保线程安全,生产消费共用一把锁。
-
这样设计便于理解阻塞队列的原理
-
没有使用wait() 和notify()方法。
-
使用了Locksupper.park()和Locksupper.unpark()。暂停继续线程功能
public class DataResprt { public static void main(String[] args) { //创建数据仓库对象 DataResprt dataResprt = new DataResprt(50); //创建生产消费线程池 ExecutorService productionServicer = Executors.newFixedThreadPool(10); ExecutorService consumerServicer = Executors.newFixedThreadPool(10); for (int i=0;i<10;i++){ productionServicer.execute(()->{ while (true){ try { TimeUnit.MILLISECONDS.sleep(1000); dataResprt.production(); } catch (InterruptedException e) { e.printStackTrace(); } } }); consumerServicer.execute(()->{ while (true){ try { TimeUnit.MILLISECONDS.sleep(500); dataResprt.comsumer(); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } //数据池大小 private int inventory=0; private final int MAX = 100; //构造函数 DataResprt(int inventory){ this.inventory =inventory; } //引入公平锁 ReentrantLock lock = new ReentrantLock(true); Condition condition = lock.newCondition(); //消费 消费 线程组 private List<Thread> consumerThreads = new ArrayList<>(); private List<Thread> productionThreads = new ArrayList<>(); //生产线程组 //消费接口 /** * 消费成功返回turn,否则falsh * **/ public Boolean comsumer() throws InterruptedException { //上锁 lock.lock(); if (this.inventory>0){ //库存大于0 //业务代码 this.inventory--; System.out.println(Thread.currentThread().getName() + "消费者:消费了一个数据,库存:"+this.inventory); //唤醒消费线程 if (consumerThreads.size()>0){ for(Thread thread:consumerThreads){ LockSupport.unpark(thread); } consumerThreads=new ArrayList<>(); } }else { //库存空 //添加到消费者线程组 consumerThreads.add(Thread.currentThread()); //阻塞当前线程, //condition.await(); //还是暂停当前线程吧,先释放锁 System.out.println(Thread.currentThread().getName() + "消费者:库存为空,任务暂停,释放锁。"); lock.unlock(); //释放锁 LockSupport.park(); //暂停线程 //线程继续后返回 return false; } //释放锁 lock.unlock(); return true; } //消费线程接口 /*** * 生产成功返回true,否则falsh */ public boolean production() throws InterruptedException { lock.lock(); if (MAX>this.inventory){//库存没满 //业务代码 this.inventory+=1; System.out.println(Thread.currentThread().getName() + "生产者:生产了一个数据,库存:"+this.inventory); //唤醒生产者 if (productionThreads.size()>0){ for(Thread thread:productionThreads){ LockSupport.unpark(thread); } consumerThreads=new ArrayList<>(); } }else{ //保存早生产组 productionThreads.add(Thread.currentThread()); //暂停生产者 System.out.println(Thread.currentThread().getName() + "生产者:库存满了,任务暂停,释放锁。"); lock.unlock(); //释放锁 LockSupport.park(); //暂停线程 //线程继续后返回 return false; } lock.unlock(); return true; } }
十一,单例模式
理解:一个只能能创建出一个对象,保证类的方法使用权只能有一个对象。
设计模式:
-
将构造方法私有化,外部不能调用。
-
预留一个对象的存储空间
-
获取时在创建对象
-
考虑多线程问题,getinstance()方法加锁,线程锁必须只能一个,使用类上锁
-
java能实现单例模式,受益于java对象的赋值是引址引用,获得的实例实际只是地址,并非复制
class MySingle{ //预留唯一对象存储空间 static volatile private MySingle mySingle = null; //私有化构造方法 private MySingle(){ System.out.println("我是MySingle唯一的构造方法,你看不到我第二次"); } //提供一个获得实例的结构 synchronized static public MySingle getMySingle(){ if (mySingle==null){ mySingle = new MySingle(); } return mySingle; } }