JAVA并发编程学习笔记

线程安全:

就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

多个线程多个锁:

多个线程,每个线程都可以拿到自己指定的锁,分别获得锁后执行synchronized方法体的内容

关键字synchronized获取的锁都是对象锁,而不是将一段代码(方法)当做锁,所以示例中哪个线程先执行synchronized关键字的方法,哪个线程就有该方法所属对象的锁(lock),两个对象,线程获得的就是两个不同的锁,他们互不影响.

有一种情况就是相同的锁,在静态方法上加上synchronized关键字,表示锁定class类,类一级别的锁,独占class类

 

对象锁的同步和异步

同步 synchronized

就是共享,如果不是为了共享的话就没有必要使用同步

异步 asynchronized

就是独立,互相之间不受约束.就好像ajax的请求,默认是异步,当我们发起ajax请求时,我们可以继续操作页面内容,不受影响,

 

同步的目的是线程安全,对于线程安全来说需要满足两个特性:原子性,可见性

 

脏读

打个比方,一个业务场景:一个人给一条数据去修改值,但是这个修改的过程时间比较长,然后我另一个人在他修改的时间中又去查询这条数据,得到的结果可能是之前那个人修改到一半的数据,也就是有些数据时修改前有些事修改后,导致数据不一致,这就是数据脏读

 

volatile关键字

主要作用是使变量在多个线程间可见

在java中,每一个线程都会有一块工作内存区,其中存放着所有线程共享的主内存中的变量值的拷贝,当线程执行时,他在自己的工作区有区中操作这些变量。

为了存取一个共享的变量,一个线程通常先获取锁定并去清除它的内存工作区:

把这些共享变量从所有线程的共享内存区中正确的装入到他自己所在的工作内存区中,当 解锁时保证该工作内存区中变量的值写回到共享内存中。

而volatile的作用就是强制线程到主内存(共享内存)去读取变量,而不是去线程工作区里去读取,这样就能实现了多个线程间的变量可见,也就满足了线程安全的可见性.

 

volatile虽然拥有多个线程之间的可见性,但是不具备同步性(原子性),可以算上是一个轻量级的sycnchronized,性能比sycnchronized好很多,不会阻塞.一般volatile用于只针对于多个线程可见的变量操作,并不能代替sycnchronized的同步功能.适合读取,不适合写入场景.

 

AtomicInteger这个类系列的操作具有原子性,能保证同步(atomic类只保证本身方法原子性,并不保证多次操作的原子性)

(在多线程的情况下或者在项目中尽量不要使用System.out.println);

 

线程之间的通信

线程是操作系统中独立的个体,但这些个体不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用手段之一.当线程存在通信时,系统的交互性会更强,会提高cpu利用了和对线程任务在处理过程中进行有效把控与监督.

使用wait/notify实现线程之间的通信(这两个方法都是object的方法)

  1. wait和notify必须配合synchronized关键字使用
  2. wait方法释放锁,notify方法不释放锁

 

CountDownLatch只适用在一个线程中要去通知另一个线程的时候比较合适,如果为了是为了保证原子性的话还是要用上synchronized同步

CountDownLatch为阻塞,countDown方法为通知,也就是打开阻塞,而await方法就是阻塞着这个线程,当countDown被调用时await方法后的代码才会被执行,这就是阻塞.

 

同步类容器

如Vector,HashTable,这些容器都是有jdk的Collections.synchronized***等工厂方法去实现的.

底层结构就是用了synchronized关键字对每个公用方法都进行同步.

 

jdk1.5后提供了并发类容器代替同步类容器:ConcurrentHashMap来代替HashTable CopyOnWriteArrayList代替vector,

并发的CopyonWriteArraySet以及ConcurrentLinkedQueue和LinkedBlockingQueue前者是高性能的

队列,后者是用阻塞形式的队列.

具体实现Queue还有很多,例如ArrayBlockingQueue.PriorityBlockingQueue, SynchronousQueue等。

ConcurrentMap接口有下有两个重要的实现:ConcurrentHashMap

和ConcurrentSkipListMap(支持排序).ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段就是一个小的HashMap,它们有自己的锁,只要多个修改操作发生在不同的段上,它们就可以并发进行,把一个整体分成了16个段,也就是最高支持16个线程的并发修改操作,并且代码中大多共享变量使用volatile关键字生明.

 

Copy-On-Write容器

jdk的cow容器有两种:CopyOnWriteArrayList和CopyOnWriteArraySet.

cow容器就是写时复制的容器,通俗的说就是我们往一个容器里添加值的时候不直接往里面加,而是将当前的容器进行copy,复制一个新容器,然后往新容器里添加元素,添加完成之后,将原容器引用指向新容器,这样的好处就是我们队cow容器进行并发的读,而不需要加锁,因为当前容器不会添加任何东西,所以cow也是一种读写分离的思想,读和写不同的容器.

ConcurrentLinkedQueue

是一个适用高并发场景的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue,它是基于链接节点的无界限线程安全队列.先进先出.队列不允许有null.

重要方法: add和offer都是加入元素,在ConcurrentLinkedQueue中没有任何区别.

poll和peek都是取头元素节点,区别是前者会删除元素,后者不会

 

BlockingQueue接口

JAVA并发编程学习笔记_第1张图片

Futrue模式

JAVA并发编程学习笔记_第2张图片

在请求的时候不直接返回响应,而是另外开一个线程将本该返回的内容放入另一个装饰对象中,然后返回这个装饰对象,使的效率提高

Master-Worker模式

JAVA并发编程学习笔记_第3张图片

JAVA并发编程学习笔记_第4张图片

以上就是模拟一个这样的过程,Master接收到100个任务,存放在concurrentLinkedQueue中,放这个队列中是因为这个队列效率好,无锁的高并发情况下保证了安全(上方有介绍),而存放worker的容器用HashMap是因为在用worker线程的时候他们是分开执行任务的,HashMap只是用来存放worker线程的,不存在安全问题,所以没问题,而执行的结果用ConcurrentHashMap的原因是因为当你多个线程执行完结果后都往这里存执行结果,这是一个并发的操作所以用这个线程安全的容器.

 

生产者-消费者

通常由两类线程,即若干个生产者线程和若干个消费者线程,生产者线程负责提交用户请求,消费者线程负责具体处理生产者提交的任务,生产者和消费者之间通过共享内存缓存区进行通信.

JAVA并发编程学习笔记_第5张图片

Executor框架

这个是jdk提供创建线程池,我们可以通过Executors类创建线程池:

newFixedThreadPool()方法,这个方法返回一个固定数量的线程池,该方法的线程数始终不变,当有任务时,若线程池中空闲,则立即执行,若没有,则会被暂缓队列在一个任务队列中等待空闲的线程执行.

newSingleThreadExecutor()方法,创建一个线程的线程池,若空闲则执行,没有空闲线程则暂缓在任务队列中.

newCachedThreadPool()方法,返回一个实际情况调整线程个数的线程池,不限制最大线程数量,若有任务则创建线程,若没有则不创建.没有任务线程则在空闲60s后自动回收

newScheduledThreadPool()方法,该方法返回一个newScheduledThreadService对象,该对象可以指定线程的数量.

 

以上的创建线程池的方法都是通过ThreadPoolExecutor这个类实现的,根据参数不同出现不同的线程池.

自定义线程池ThreadPoolExecutor

构造方法

JAVA并发编程学习笔记_第6张图片

当然了,还有不同参数的构造方法.这个构造方法只是显示了所有参数,看下具体每个参数的含义吧:

int corePoolSize:核心线程,也就是我这个线程池创建了的话会有多少了线程

int maximumPoolSize:这个线程池最大有多少个线程

long keepAliveTime:线程空闲销毁时间

TimeUnit unit:和上个参数是一对的,用来设定这个时间的类型(小时,秒,分,时间单位之类的)

BlockingQueue workQueue:保存任务的队列,这个接口有多个实现类,详情见目录

ThreadFactory threadfactory:执行程序创建新线程使用的工厂(暂无意义)

RejectedExecutionHandler:当任务超过线程的能力就会走这个拒绝策略

 

关键:

JAVA并发编程学习笔记_第7张图片

你可能感兴趣的:(基础,后端)