网站案例代码,辽宁建设工程信息网诚信库官网,如何创网站,组织建设方面存在的问题什么是并发编程#xff1f;
并行#xff1a;在同一个时间节点上#xff0c;多个线程同时执行(是真正意义上的同时执行)
并发#xff1a;一个时间段内#xff0c;多个线程依次执行。
并发编程#xff1a;在例如买票、抢购、秒杀等等场景下#xff0c;有大量的请求访问…什么是并发编程
并行在同一个时间节点上多个线程同时执行(是真正意义上的同时执行)
并发一个时间段内多个线程依次执行。
并发编程在例如买票、抢购、秒杀等等场景下有大量的请求访问同一个资源。会出现线程安全的问题所以需要通过编程来解决多个线程依次访问资源称为并发编程。
并发编程的根本原因
多核cpu的出现真正意义上可以做到并行执行java内存模型JMM
java内存模型规范了Java虚拟机与计算机内存是如何协同工作的。
将内存分为主内存和工作内存。两个线程同时操作会导致出错本质原因在于内存模型设计。 共享数据存储在主内存中每个线程都有各自的工作内存。操作共享数据时会将主内存中的数据复制一份到工作内存中操作操作完成后再写回到主内存中。
但是一旦两个线程同时进行操作读取共享数据两个线程各自在工作内存中修改后同时又写到主内存这样就会与预期的结果不同。AB两个线程同时操作变量n
一、并发编程核心问题
由于java内存模型的设计多线程操作一些共享的数据时出现以下3个问题
1不可见性A线程在工作内存中操作共享数据时B线程不知道A线程已经修改了数据。
2无序性为了优化性能有时候会改变程序中语句的先后顺序以提高速度。 int a 10; io.read();//从其他地方读数据 int b 5; int cab; 但是为了优化第2行需要从其他地方读数据 需要时间系统可能将3行代码乱序执行例如 1、3、2的顺序执行。 有时看似没有关系的代码乱序执行可能会对后面的代码产生影响。
3非原子性
一个或多个操作在CPU执行的过程中不被中断的特性我们称为原子性。 原子性是拒绝多线程交叉操作的同一时刻只能有一个线程来对它进行操作。
高级语言里一条语句往往需要多条CPU指令完成。如 count至少需要三条CPU指令。
首先需要把变量 count 从主内存加载到工作内存之后在工作内存执行 1 操作最后将结果写入主内存;
解决办法
让不可见变为可见让无序变为有序非原子执行变为原子(加锁)由于线程切换执行导致
缓存(工作内存) 带来了不可见性指令重排优化带来了无序性线程切换带来了非原子性。
volatile可以解决前两个问题加锁可以解决所有问题。
二、volatile关键字
volatile修饰的共享变量类的成员变量、类的静态成员变量被一个线程修改后可以同步更新到其他线程让其他线程中立即可见。volatile修饰的共享变量指令是有顺序的。
但是volatile不能解决原子性问题原子性问题由于线程切换执行导致。
volatile底层实现原理
使用内存屏障(指令)进行控制。
有序性实现volatile修饰的变量在操作前添加内存屏障来禁止指令重排序。可见性实现volatile修饰的变量添加内存屏障之外还通过缓存一致性协议(MESI)将数据写回到主内存其他工作内存嗅探后如果自己工作内存中的数据过期重新从主内存读取最新的数据。
三、如何保证原子性
同一时刻只有一个线程执行称之为互斥。如果我们能够保证对共享变量的修改是互斥的那么就能保证原子性了。
1、锁
只有通过加锁的方式让线程互斥执行来保证一次只有一个线程对共享资源进行访问。
synchronized关键字修饰代码块、方法自动获取锁自动释放锁
ReentrantLock类只能对某段代码修饰需要手动加锁手动释放锁
2、原子变量
在java中还提供一些原子类在低并发情况下使用是一种无锁实现。
JUC(java.util.concurrent包)中里面的locks包和atomic包它们可以解决原子性问题。 1.原子类原理AtomicInteger 为例
原子类的原子性是通过volatileCAS实现原子操作的。
低并发情况下使用原子类 AtomicInteger底层有一个变量通过volatile关键字修饰的结合CAS机制实现。
2.CAS重点
采用CAS机制Compare-And-Swap比较并交换是一种无锁实现在低并发情况下使用。CAS是乐观锁的方式采用的是自旋的思想。
采用自旋思想:
1第一次从内存中读到内存值V
2对数据进行修改将改变后的值写入到内存时需要重新读取内存中最新的值作为预期值A
3在写入前比较预期值与内存值看是否一致
如果一致说明其他线程没有修改内存中的值将更新后的值写入到内存如果不一致说明其他线程修改了主内存中的值就需要重新计算变量值反复这一过程。---自旋 优点
不加锁所有的线程都可以对共享数据操作适合低并发使用因为所有线程不会进入阻塞状态
缺点
大并发时不停自旋判断导致cpu占用率高
3.ABA问题 ABA问题即线程1读取到内存值线程2将内存值由A改为了B再由B改为了A。当线程1去判断时预期值与内存值相同无法分辨内存值是否发生过变化。
通过设置版本号每次操作改变版本号来避免ABA问题。如原先的内存值为A1线程修改为B2再修改为A3。此时另一个线程使用预期值A1与内存值A3进行比较只需要比较版本号1和3即可发现该主内存中的数据被更新过了。
四、java中的锁
一些锁的名称指的是锁的特性、设计、状态并不是都是锁。
1、乐观锁/悲观锁
乐观锁没有加锁不加锁的方式是没有问题的。例如CAS机制
悲观锁必须加锁。悲观的认为不加锁的并发操作一定会出问题。
2、可重入锁
synchronized和ReentrantLock是可重入锁可以避免死锁。 A方法和B方法是两个同步方法在同一个类中用同一把锁先进入到同步方法A中锁被使用在方法A调用方法B依然可以进入到方法B。此时方法A还没有释放锁
如果不是可重入锁的话方法B不会被当前线程执行。
3、读写锁
ReentrantReadWriteLock里面有一个读锁和写锁。
读读不互斥只有读没有写可以多个线程同时读读写互斥一旦有写操作读写不同同时进行。写写互斥多个写互斥
4、分段锁
不是锁是一种锁实现思想用于将数据分段并在每个分段上都会单独加锁以提高并发效率。
举例Hashtable是将整合hash表格锁住了一次只能有一个线程操作并发量低效率低。
ConcurrentHashMap将每个哈希位置当做一个锁可以有多个线程对map进行操作一次只能有一个线程操作一个位置.
5、自旋锁
不是锁。是自己重试当线程抢锁失败后重试几次如果抢到锁了就继续如果抢不到就阻塞线程。
6、共享锁/独占锁
共享锁一个锁可被多个线程共享例如读写锁中的 读锁。
独占锁一次只能有一个线程操作。例如Synchronized、ReentrantLock读写锁中的 写锁。
7、公平锁/非公平锁
公平锁按照请求的顺序执行排队先来来执行。
非公平锁不按照请求顺序执行谁先抢到谁先执行。
synchronized是一种非公平锁。ReentrantLock默认是非公平锁但是底层可以通过AQS来实现线程调度使其变成公平锁。
五、synchronized锁
1、锁的状态
在synchronized锁的底层实现中提供4种锁的状态又来区别对待。锁的状态在同步锁对象的对象头中有一个区域叫Mark Word中存储
无锁状态没有线程进入。偏向锁始终只有一个线程访问同步代码快记录线程的编号快速的获取锁。轻量级锁当锁状态为偏向锁时还有其他线程访问此时升级为轻量级锁。特点当一个线程获取锁之后其他线程不会阻塞会通过自旋方式获取锁提高效率。重量级锁当锁的状态为轻量级锁时线程自旋达到一定的次数还没有获取到锁就会进入到阻塞状态锁状态升级为重量级锁等待操作系统调度。
2、对象结构
在Hotspot虚拟机中对象在内存中分为三块区域对象头、实例数据和对齐填充synchronized使用的锁对象是存储在对象头里。 对象头中有一块为Mark Word用于存储对象自身的运行时数据如哈希码HashCode、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID等等。
32位操作系统Mark Word为32bit64 位操作系统Mark Word为64bit。下面就是对象头的一些信息 3、synchronized锁实现
synchronized锁是依赖底层编译后的指令添加锁的监视器实现需要我们提供一个同步对象来记录是否加锁、以及锁的状态。
六、AQS
全称为AbstractQueuedSynchronizer这个类在java.util.concurrent.locks包下面。抽象同步队列是java代码实现线程同步非常重要的一个底层实现类。
思路:
在类中定义了一个state变量初始化为0表示有没有线程访问共享资源和一个双向链表队列head结点代表当前占用的线程。有线程访问时第一个抢到执行权的线程放在头节点将state加1。期间如果有其他的线程访问时如果state1将其他线程添加到队列中等待锁的释放。
state由于是多线程共享变量所以定义成volatile以保证state的可见性但不能保证原子性所以AQS提供了对state的原子操作方法保证了线程安全。
队列由Node对象组成Node是AQS中的内部类。 AQS 的锁模式分为独占和共享
独占锁每次只能有一个线程持有锁比如ReentrantLock是以独占方式实现的。
共享锁允许多个线程同时获取锁并发访问共享资源比如ReentrantReadWriteLock
ReentrantLock锁实现
ReentrantLock是java.util.concurrent.locks包下的类实现Lock接口。
public class ReentrantLock implements Lock, java.io.Serializable{ }
ReentrantLock基于AQS在并发编程中可以实现公平锁和非公平锁来对共享资源进行同步。ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类NonfairSync与FairSync类继承自Sync类Sync类继承自AbstractQueuedSynchronizer抽象类。 ReentrantLock构造方法
无参构造方法默认是非公平实现有参构造方法可以选择true—公平实现false—非公平实现 NonfairSync类继承了Sync类表示采用非公平策略获取锁其实现了Sync类中抽象的lock方法。
static final class NonfairSync extends Sync {
//若通过 CAS 设置变量 state 成功就是获取锁成功则将当前线程设置为独占线程。
//若通过 CAS 设置变量 state 失败就是获取锁失败则进入 acquire 方法进行后续处理。final void lock() {if (compareAndSetState(0, 1))//每个线程进入到lock方法时,会尝试获取锁,有可能获取到了setExclusiveOwnerThread(Thread.currentThread());else//获取不到将线程添加到队列中排队获取锁acquire(1);}//尝试获取锁,无论是否获得都立即返回protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
}
FairSync类也继承了Sync类表示采用公平策略获取锁其实现了Sync类中的抽象lock方法。
static final class FairSync extends Sync {final void lock() {//公平锁默认排队获取锁acquire(1);}
}
七、JUC常用类
1、ConcurrentHashMap
HashMap是线程不安全的不能在多线程环境下使用
Hashtable是线程安全的但是synchronized直接锁住的是整个方法效率低public synchronized V put(K key,V value{})
ConcurrentHashMap是线程安全的效率高于Hashtable。
不像Hashtable将整个方法锁起来将每个位置的第一个节点当做锁对象将锁的力度减小进而提高了效率同时可以有多个线程对ConcurrentHashMap进行操作如果多个线程操作的是同一个位置那么必须等待因为用的是同一把锁。当算出的位置第一个节点为null时采用CAS机制添加。 Hashtable和ConcurrentHashMap不支持存储null键和null值。源码中看到为null就报空指针异常。为什么这样设计呢
为了消除歧义因为无法分辨key的值为null还是key不存在返回的null这在多线程里面是模糊不清的所以压根就不让 put null。
2、CopyOnWriteArrayList
ArraayList是线程不安全的在高并发情况下可能会出现问题
Vector是线程安全的get、add方法都加锁读读都互斥效率低。
CopyOnWriteArrayList在读的时候不加锁写入也不会阻塞读取操作只有同时写入和写入之间需要进行同步等待提高了读的效率。
CopyOnWriteArrayList在进行add、set等修改操作时是通过底层数组的副本实现的。先将底层数组进行复制修改复制出来的数组修改后将数据赋值给原来的底层数组。写入时不影响其他线程读
3、CopyOnWriteArraySet
CopyOnWriteArraySet线程安全的底层使用的是CopyOnWriteArrayList不能存储重复数据
4、辅助类 CountDownLatch
CountDownLatch允许一个线程 等待其他线程各自执行完毕后再执行。底层实现是通AQS来完成的创建CountDownLatch对象时指定一个初始值线程的数量。每当一个线程执行完毕后AQS内部的state就-1当state的值为0时表示所有线程都执行完毕然后等待的线程就可以恢复工作了。 八、对象引用
在JDK1.2版之后Java对引用的概念进行了扩充将引用分为
强引用软引用SoftReference弱引用WeakReference虚引用PhantomReference
这4种引用强度依次逐渐减弱。除强引用外其他3种引用均可以在java.lang.ref包中找到它们的身影。
1、强引用不是垃圾
有引用指向该对象Object obj new Object(); 这种情况下new出来的对象不能被垃圾回收的。
软引用、弱引用、虚引用都是用来标记对象的一种状态。当一些对象称为垃圾后通过不同的状态来判断什么时候被清理。可以继承SoftReference、WeakReference、PhantomReference或者把自己的对象添加到软、弱、虚的对象中。
2、软引用内存不足时回收
被软引用关联的对象被判定为垃圾时可以不用立即回收直到垃圾回收后内存仍然不够用时才会回收软引用关联的对象。
Object obj new Object();// 声明强引用
SoftReferenceObject sf new SoftReference(obj);
obj null; //销毁强引用
3、弱引用发现时回收
弱引用管理的对象只能存活到下一次垃圾回收。
4、虚引用对象回收跟踪
最弱的引用对对象的生命周期没有任何的影响跟踪对象是否被回收如果对象被回收后会给队列返回信息
Object obj new Object();
ReferenceQueue phantomQueue new ReferenceQueue();//声明引用队列
PhantomReferenceObject sf new PhantomReference(obj,phantomQueue);//声明虚引用还需要传入引用队列如果对象被回收后会给队列返回信息
obj null;
九、线程池
1、池的概念
如果并发的线程数量很多并且每个线程都是执行一个时间很短的任务就结束了频繁创建线程和销毁线程需要时间。 可以事先创建出一些连接对象每次使用时从集合中直接获取用完不销毁。减少频繁创建、销毁。
在 JDK5 版本中增加了内置线程池实现 ThreadPoolExecutor同时提供了Executors来创建不同类型的线程池。
池的好处减少频繁创建销毁时间统一管理线程提高速度。
2、ThreadPoolExecutor类
Java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类因此如果要透彻地了解Java中的线程池必须先了解这个类。 ThreadPoolExecutor继承了AbstractExecutorService类并提供了四个构造器但是前三个构造器都是调用的第四个构造器进行的初始化工作。
3、构造器中各个参数的含义
1.corePoolSize
核心池的大小一旦创建不会被销毁的非核心池中的线程在没有被使用时可以被回收。
2.maximumPoolSize
线程池最大线程数量包含核心池中的数量。
3.keepAliveTime
非核心线程池中的线程在不被使用后多久就终止。假如核心线程池5个最大数量10但是任务少的情况下核心线程池够用了等多长时间就把非核心线程池中的线程终止
4.unit
为keepAliveTime设置时间单位有7种取值。 5.workQueue
一个阻塞队列用来存储执行的任务。有以下工作队列
ArrayBlockingQueue数组实现的有界阻塞队列创建时必须设置长度按FIFO排序。LinkedBlockingQueue链表结构的阻塞队列按FIFO排序任务容量可以选择进行设置不设置是一个最大长度为 Integer.MAX_VALUE
6.threadFactory
创建线程的工厂。
7.handler
拒绝策略。当线程池中的核心池、阻塞队列、非核心池已满时如果有任务继续到达如何执行。有以下四种拒绝策略
AbortPolicy()直接抛出异常拒绝执行。CallerRunsPolicy()交由当前提交任务的线程执行如果任务被拒绝了则由提交任务的线程(例如:main)直接执行此任务DiscardOldestPolicy()丢弃等待时间最长的任务。DiscardPolicy()直接丢弃不执行。
4、线程池的执行
创建完成ThreadPoolExecutor之后当向线程池提交任务时通常使用execute方法。 execute方法的执行流程图如下 当请求到来时如果核心线程池没有满就提交到核心线程池如果核心线程池已满则添加到队列中前提是队列没有满如果队列中已满则在非核心线程中创建线程直到到达最大线程数量如果非核心线程池也已经满了那么则使用适当的拒绝策略处理。
execute与submit的区别
execute() 提交任务没有返回值submit() 提交任务可以有返回值任务需要实现callable接口
关闭线程池
shutdownNow() 直接关闭对还未开始执行的任务全部取消shutdown() 等待任务执行完关闭
//任务
public class MyTask implements Runnable {private int taskNum;public MyTask(int num) {this.taskNum num;}Overridepublic void run() {try {Thread.currentThread().sleep(4000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName():task taskNum执行完毕);}
}
public class Test {public static void main(String[] args) {//创建线程池ThreadPoolExecutor executor new ThreadPoolExecutor(2,5, 200,TimeUnit.MILLISECONDS,new ArrayBlockingQueue(2),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());executor.prestartAllCoreThreads();for(int i1;i8;i){MyTask myTask new MyTask(i);executor.execute(myTask);//添加任务到线程池//Future? submit executor.submit(myTask);//submit.get();//返回值}executor.shutdown();}
}
十、ThreadLocal
本地线程变量可以为每个线程都创建一个属于自己的变量副本使得多个线程之间隔离不影响。在每一个线程里都有一个自己的localNum
package com.ffyc.javapro.thread.threadlocal;public class ThreadLocalDemo {//创建一个ThreadLocal对象,复制保用来为每个线程会存一份变量,实现线程封闭private static ThreadLocalInteger localNum new ThreadLocalInteger(){Overrideprotected Integer initialValue() {return 0;}};public static void main(String[] args) {new Thread(){Overridepublic void run() {localNum.set(1);try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}localNum.set(localNum.get()10);System.out.println(Thread.currentThread().getName():localNum.get());//11}}.start();new Thread(){Overridepublic void run() {localNum.set(3);try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}localNum.set(localNum.get()20);System.out.println(Thread.currentThread().getName():localNum.get());//23}}.start();System.out.println(Thread.currentThread().getName():localNum.get());//0main线程}
}
ThreadLocal底层实现
在一个线程中使用ThreadLocal时为每个当前线程创建了一个ThreadLocalMap看似用唯一的ThreadLocal对象作为键其实每个线程中都有一个属于自己的ThreadLocalMap所以每个线程中都有一个自己的变量副本。
ThreadLocal会造成内存泄漏
由于ThreadLocal被弱引用关联有可能在下一次垃圾回收时被回收掉会导致key为null而value还存在着强引用。但是value却被Entry对象关联Entry又被ThreadLocalMap关联ThreadLocalMap又被Thread关联要是当前线程长期不结束value就不能被销毁但是key有可能已被回收就获取不到value造成内存泄漏。
正确的使用不再使用这个本地线程变量后将其主动删除掉调用remove方法删除。