java并发编程
锁的持有者,谁是锁
public class Lock (
public synchronized void fun1(){
//业务运算.
};
public static synchronized void fun2(){
//业务运算.
}
}
Lock a = new Lock();
fun1:锁是 Lock 对象也就是 a this,持有锁这调用的线程。
fun2:锁是 Lock.class,持有锁这调用的线程。
当线程持有了锁,当要进入需要相同锁的地方,可以进入。
synchronized 注意地方,缺点:
注意地方:
锁是用在多线程并发操作:当线程获取到了锁,调用了sleeep(休眠),线程不会释放资源,释放锁,
wait,线程会释放锁,当再次醒来后又要重新获取锁,需要在同步块。
notify:唤醒由于该条件等待的线程中的一个线程,需要在同步块。
notifyAll:唤醒所有。 一般就调用notifyAll:不然可能会造成某些线程假死,点背一直没有唤醒过他。需要在同步块。
缺点:
当并发时候需要超时中断,不能实现。只能傻等到得到锁业务计算完毕退出。
显示锁:ReentrantLock:
语义上还有和 synchronized 完全相同,只是更多的功能
写法:
lock.lock();// 获取锁
lock.unlock();// 释放锁,一定要和数据库连接一样,放到 finally 中
由于和数据库连接一样,增加了危险性。
常用 api 解释:
tryLock(long timeout, TimeUnit unit ) // 获取锁,不能返回 false 或一个时间后 不能获取返回。
ReentrantLock的wait,notify, notifyAll:
和synchronized 的wait,notify,notifyAll对应。
如果使用了ReentrantLock不能使用wait,notify,notifyAll方法。
//生产者消费者的生产环境,有限的数组,当数组满了后需要等待,消费者清除了数据后需要唤醒生产者线程。
ReentrantLock lock = new ReentrantLock();
Condition full = lock.newCondition();
public void put(String str){
if(isFull()){//是否已经满了
full.await();
}
}
public void get(){
//清除业务
full.notifyAll();
}
觉得更加有针对性的面向对象的编程。
数据库的类似问题:
脏读:第一个事物读取第二事物正在更新的数据,如果更新语句尚未完成,则第一个事物读取到的只是一个过程中的数据,而并非真实的结果。oracle的事物默认是:read committed(提交读), 不会出现该问题
排他锁 :当变更数据,获取排他锁。
共享锁:查询获取共享锁,数据可以被多线程获取多个共享锁。获取了共享锁,再获取排他锁,需要等待共享锁结束。
Java解决数据库的类似问题
脏读:violation 关键字,可见性 如64bit的long,double。
排他锁 :默认synchronized,ReentrantLock都是排他锁。
共享锁:实现类ReentrantReadWriteLock ,读写锁。场景:如我们的系统缓存常量数据一般都是读,很少的修改。
并发类介绍-----原子基本变量
Boolean:AtomicBoolean
Long:AtomicLong
引用:AtomicReference
链表中的大量数据需要包装,使用域的更新,AtomicReferenceFieldUpdater
案例:系统的访问次数,你肯定用一个long sum = 0;
sum++方法,
从计算机的原子操作看是有3步 1.取出sum=0 2.加1 sum+1 3.把加的值放回到sum的区域 sum=1;
并发操作要讲究原子操作,我们一般使用 隐藏锁(synchronized)或显示锁(lock).
并发类介绍---CAS(compare and swap)
伪代码:
addOne(){
for(;;){
int old = 当前的值;
int new = old+1;
if(cas(old,new)){
return;
}
}
}
/**
*这一块是cpu指令实现原子,当替换成功返回true,否则false.
*/
cas(int old,int new){
if(old==当前的值){//就是刚才的当前值,相同表示没有变化。
当前的值=new;
return true;
}else{//如果不相同表示已经被修改,那么就返回false,上面函数再调用cas..
return false
}
}
并发类介绍-----CAS与锁实现比较
锁的基本实现:A 线程获取锁,B获取锁等待,B释放锁唤醒所有等待线程,B获取锁
休眠等待需要操作系统的上下文切换,从用户态到系统态的切换,比较慢。
如果用cas的话,直接是jvm计算,当超级大并发,竞争异常激烈时候,cas就不一定比锁性能更好了,从这些业务算法上看,计算机的科学也是为了解决具体的事情,那些牛人想破脑袋想出来的。
并发类介绍-----缓存队列
数组(array)如:ArrayList
数组链相结合产物:HashMap;
为了并发操作更快速,使用更加简便设计。并发包java.util.concurrent
有限数组(array):ArrayBlockingQueue,
并发数组链map:ConcurrentHashMap。
当map很大时候,添加修改一次需要花费资源越来越大,可以设置成map中多个map,然后计算hash取模,加锁只加其中的一个小map。和数据库的分区类似。分离锁,只对一块数据中的一个区锁定。
并发类介绍----工具类
信号量,semaphore:如最多5个信号,业务运算时候需要先得到信号(acquire),在运算,结束后再release。有些像连接池一样,限制的计算量。
关卡,barrier,实现类:CyclicBarrier,执行完的线程在最后等待,等待最后的线程执行完,然后大家一起结束。
闭锁,latch, CountDownLatch:等到所有资源集合完毕,等待的线程才能统一的都运行。
并发类介绍----线程池工具类
当创建很多运行时间很多的线程时候,jvm为分配资源的代价越来越高,线程池和数据库连接池类似
Executors 类下的静态方法,
newFixedThreadPool(int nThreads);--定长线程池。
newCachedThreadPool(); --根据系统需要创建,然后重用等方法。
缓慢的劣质化:
当用池后,你的线程不会无限的增长导致内存溢出,在你的控制下,你的系统负载很高时,用户提交的数据在你的jvm中被阻塞,后来又在操作系统层面缓存提交,操作系统不够后只能在路由器缓存,最后路由器就timeout给用户。
并发测试:
垃圾回收会影响你的测试报告,禁止测试时候执行垃圾回收:-verbose:gc
方法首先运行使用解释字节码方式执行,足够频繁时候会动态编译, 打印编译信息,-XX:+PrintCompilation
Jdk有client,server 多种运行模式,发布时候肯定运行于server模式下,该模式下更擅长优化死代码:-server
公式定律
Amdahl定律:当计算资源必须使用串行化占比,然后计算出可提升性能的公式:
定制java 线程池大小:等待时间(WT)与服务时间(ST)之间的比例。如果我们将这一比例称之为 WT/ST,那么对于一个具有 N 个处理器的系统,需要设置大约 N*(1+WT/ST) 个线程来保持处理器得到充分利用
happen-before :
编译器为了提高多线程性能,会对代码进行重新排序,基于happen-before 规则才能确定代码执行的先后顺序。
1.单线程规则:同一个线程中,书写在前面的操作happen-before书写在后面的操作。这条规则是说,在单线程 中操作间happen-before关系完全是由源代码的顺序决定的。
2.对锁的unlock操作happen-before后续的对同一个锁的lock操作。这里的“后续”指的是时间上的先后关系,unlock操作发 生在退出同步块之后,lock操作发生在进入同步块之前。必须对同一个变量的 所有 读写同步,才能保证不读取到陈旧的数据,仅仅同步读或写是不够的 。
3.如果操作A happen-before操作B,操作B happen-before操作C,那么操作A happen-before操作C。称为传递规则。
可参考:http://www.iteye.com/topic/260515
并发内部隐示锁:synchronized
特性:可见性:和 violation 一样,
原子性:把一些不是原子操作组合成原子操作。
当一个 final 变量时候不需要做同步 , 但是一个对像需要内部的成员变量是否 final 。
当一个变量创建后要变化,需要在修改和获取时候都要加锁。不然遍历时可能 抛出 ConcurrentModificationException 被变化异常。
系统运行也不是单个计算机或现在的单核cpu能够很好解决运行:系统需要 显示多机多cpu负载计算,为了响应当前的低碳生活,我们需要提高单机的计算能 力,更好的学习设计并发。
关键字:
原子操作:原子为不可再分操作。
Violation :可见关键字。
Synchronized:内部隐示锁
ReentrantLock:显示锁
ReentrantReadWriteLock:读写锁
final:创建后不变
jmm(java内存模型):
线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递 均需要通过主存完成
线程 thread,runable常用方法
Interrupt():中断该线程,只是java语法上说要中断,具体实现需要业务上判断,
interrupted():判断是否中断,并且清除中断状态。
isInterrupted() :判断是否中断,不清除中断状态。
join() :等待该线程终止。
yield() :暂停执行当前线程,让出资源,让那个jvm的线程排程器 从可执行状态的线程中重新进行排程。也许该线程立马就又可以运行。
线程状态:new-》可运行-》排程器调度到运行-》等待,阻塞,睡眠-》运行完毕
中断:
如 sock 通讯, read , write 被阻塞,不好中断,可通过关闭 sock.close() 实现中断 ;
并发可见关键字:violation
当多线程修改同一个数据时候,由于jmm限制,并不能立马让other thread察觉。
硬件上实现:cpu硬件厂商提供了,各个cpu核心数据同步的关卡或栅栏。Java提供了这样的关键字机制:violation
会主动同步各个工作内存的数据到主内存中.
volatile字段的写操作happen-before后续的对同一个字段的读操作 :详见组后的happen-before规则。
如 系统线程:
//表示是否运行
private volatile boolean running = false;
64位的 long,double 读写分为2个32位的操作,声明为violation,jmm会规定为原子操作。是否会在64bit机器有限制