1.JMM(java内存模型) 之可见性
JMM(Java内存模型Java Memory Model,简称JMM)本身是-种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM关于同步的规定:
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一- 个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
2volatile禁止指令重排
CAS
CAS的好处就是保证的数据致性的同时 ,也保证了并发性
CPU底层的指令原语的原子性是在修改的时候保证不受其他线程抢断一
所以在A线程的10秒内,A并没有进行修改写回主物理内存其他线程可以随意修改主内存的变量值.
z3,li4没变,atomReference在变。
ABA问题
解决:(版本号)
ArrayList线程不安全例子:
解决方法:
锁机制:
公平锁是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。在高并发的情况下,有可能会造成优先级反转或者饥饿现象
可重入锁(也叫做递归锁)
指的是同一-线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。
ReentrantLock和Synchronized就是典型的(非公平)可重入锁。
作用:防止死锁
自旋锁(spinlock)
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU.
阻塞队列
.当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。
CopyOnWriteList
写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。
synchronized和Lock有什么区别?用新的Lock有什么好处?你举例说说。
1原始构成
synchronized是关键字属于JVM层面,
monitorenter(底层是通过monitor对象来完成,其实wait/notify 等方法也依赖Fmonitor对象只有在同步块或方法中才能漏wait/notify等方monitorexit
Lock是具体类( java. util. concurrent. locks . Lock)是api层面的锁。
2使用方法
synchronized不需要用户去手动释放锁,当synchronized代码执 行完后系统会自动让线程释放对锁的占用。
Reentrantlock则需要用户去手动释放锁若没有主动释放锁,就有可能导致出现死锁现象。
需要lock()和lunlock()方法配合try/finally语句块来完成。
3等待是否可中断
synchronized不可中断,除非抛出异常或者正常运行完成
ReentrantLock可中断,1.设置超时方法tryLock(long timeout, TimeUnit unit)2. lockInterruptibly()放代码块中,调用interrupt() 方法可中断
4加锁是否公平
synchronized非公平锁
ReentrantLock两者都可以,默认公平锁,构造方法可以传入boolean值, true 为公平锁, false 为非公平锁
5锁绑定多个条件condition
synchronized没有
ReentrantLodk用来实现分组唤醒需要唤醒的线程们,可以精确唤醒, 而不是像synchronized要么随机唤醒一个线程 要么唤醒全部线程。
同一个futureTask任务,AA,BB线程只进入一次call方法,可以复用。
多个futureTask才会算多次。
线程池
线程池重要参数(5+2参数)
7.handler 拒绝策略:
等待队列也已经排满了,再也塞不下新任务了
同时,线程池中的max线程也达到了,无法继续为新任务服务。
这时候我们就需要拒绝策略机制合理的处理这个问题。
拒绝策略:(均实现了RejectedExecutionHandler接口)
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),
而在单核CPU上(悲剧吧? (; ’ A ')心), 无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。CPU密集型任务配置尽可能少的线程数量:一般公式: CPU核 数+1个线程的线程池.
由于IO密集型任务线程并不是一直在执行任务,则应该配置尽可能多的线程,如cpu核数*2.
I0密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行|O密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。I0密集型时,大部分线程都阻塞,故需要多配置线程数:
参考公式: CPU核数/(1- 阻塞系数)
阻塞系数在0.8~0.9之间
比如8核CPU: 8/(1 -0.9)= 80个线程数
死锁
阻塞队列的作用