java并发编程

jopen 10年前

锁的持有者,谁是锁

    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 ,读写锁。场景:如我们的系统缓存常量数据一般都是读,很少的修改。


java并发编程

并发类介绍-----原子基本变量

   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内存模型):

    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机器有限制