锁的持有者,谁是锁
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。称为传递规则。 可参考:并发内部隐示锁: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机器有限制