标签:
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
总结来说,Lock和synchronized有以下几点不同:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动 通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。Lock可以让等待锁的线程响应中断,而synchronized 却不行,使用synchronized时,等待的线程会一直阻塞,不能够响应中断;lock.trylock()能够避免死锁;
4)lock可以实现线程公平,但会使性能下降。
5)Lock的读写分离,因为读之间并不会线程不安全,维持多个读操作并发或者一个写操作,但读写之间互斥,类似于共享锁和排它锁。ReentrantReadWriteLock类中有两个成员变量ReadLock和WriteLock。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock 的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
实现原理对比:
Lock:AbstractQueuedSynchronizer通过构造一个基于阻塞的CLH队列(双向链表)容纳所有的阻塞线程,而对该队列的操作均通过CAS操作,但对已经获得锁的线程而言,ReentrantLock实现了偏向锁(曾经获得过该锁的对象更容易获得这个锁)的功能。
synchronized 的底层也是一个基于CAS操作的等待队列,但JVM实现的更精细,把等待队列分为ContentionList和EntryList,目的是为了降低线程的出列速度;当然也实现了偏向锁,从数据结构来说二者设计没有本质区别。但synchronized还实现了自旋锁(线程在进入阻塞队列之前,先尝试请求几次锁,无效后进入阻塞队列),并针对不同的系统和硬件体系进行了优化,而Lock则完全依靠系统阻塞挂起等待线程。
自旋锁:http://ifeve.com/java_lock_see1/
可重入锁的概念:http://ifeve.com/java_lock_see4/
ASQ同步器:双向链表构建的fifo
http://ifeve.com/introduce-abstractqueuedsynchronizer/
偏向锁:偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。它提高了单线程访问同步资源的性能。但试想一下,如果你的同步资源或代码一直都是多线程访问的,那么消除偏向锁这一步骤对你来说就是多余的。事实上,消除偏向锁的开销还是蛮大的。
并发编程基础知识总结
1,线程创建的两种方式:继承Thread类,重写run方法,Thread t1=new ...,t.start();
实现runnable接口,Thread t2 = new Thread(new runnable),t.start();
2,每个线程有自己的ID,name,优先级,状态(new,runnable,blocked(阻塞),waiting(无限期等待,wait(),join(),),time waiting(有限期等待,wait(),join(),sleep),terminated);
3,线程终断控制的方式:调用interrupt()线程中断函数使某线程中断后,可以通过调用t.isInterrupt()检测或者Thread.interrpted()检测,然后可以抛出InterrputedException传递这个中断信息。
5,sleep()方法让线程睡眠,不占用CPU,让低优先级线程执行。但与wait的不同在于sleep()期间始终占着锁。而wait不占锁。
yield()方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
6,join()与wait()的区别在于wait()让自己等别人,join让别的线程等自己。例如:在主线程中调用t.Join(),也就是在主线程中加入了t线程的代码,必须让t线程执行完毕之后,主线程(调用方)才能正常执行。
7,守护线程是一个优先级最低的线程,当他运行结束时,整个程序也就结束了。他常作为其他线程的服务线程,不能做重要的事儿
8,线程异常处理:java的异常分为非运行时和运行时异常,非运行时需要抛出和捕获(IOex,classnotfound),运行时不需要(numberformat)。run方法不支持异常抛出,需要特殊处理。
9,继承了Thread或者实现了runnable接口的类的一个对象,如果被多个线程并发,成员变量是线程共享的,如果不想共享,可通过Threadlocal声明自己线程的私有变量。
http://blog.csdn.net/lufeng20/article/details/24314381
10,可以通过实现ThreadFactory创建线程工厂,统一new线程。执行器框架和forkjoin框架用该方式建立线程,只不过执行器框架中所创建的是普通Thread,而forkjoin中创建的是工作者ForkJoinWorkerThread。这些类都可以被继承实现自己的线程类,但同时要实现工厂类。如果实现了自己的工厂,初始化框架的线程池时当参数传入即可。
线程是指进程中的一个执行流程,cpu调度的基本单位,虽然共享一块内存空间,但可以独立调度,独立的栈,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。
2),Fork/Join 框架(使用分治策略,将主任务分为多个小任务,jdk7)
与执行器框架的不同在于工作窃取算法。主任务等待他所创建子任务完成,执行这个任务的线程为工作者线程(ForkJoinWorkerThread类,thread的子类),工作者线程寻找仍未被执行的任务,并在子线程执行时间里利用所有线程优势(其实就是分治策略)。
任务继承ForkJoinTask(ForkJoinTask是个抽象类,它有两个实现类RecursiveTask和RecursiveAction前者用于不返回结果,后者用于返回结果,类似Callable和Runnable。但如果传入Runnable或者Callable,就不会使用工作窃取算法了),实现compute()(类似Run()),如果主任务范围太大,new两个范围小的子任务,然后使用invokeAll(t1,t2)进行分治。在主函数中用ForkJoinPool执行主任务。
task extends RecursiveAction {
int start,end
compute() {
if(end-start<num){
}
else {
task1 = new(midlle,end);
task2 = new(start,midlle);
invokeAll(task1,task2);
}
}
}
main() {
ForkJoinPool pool = new ForkJoinPool();
pool.excute(task);
pool.shutdown();
}
任务可以继承RecursiveTask(ForkJoinTask的子类)通过任务的get(),获得compute()的返回值,然后将子任务的返回值进行合并,作为主任务的返回值返回。
task extends RecursiveTask<>{
int start,end
compute() {
if(end-start<num){
return
}
else {
task1 = new(midlle,end);
task2 = new(start,midlle);
invokeAll(task1,task2);
return mergeRs(task1.get(),task2.get()) //这是自定义的方法
}
}
}
同步和异步执行:当用invokeAll(t1,t2)时,必须等到任务执行完毕后,方法返回;当采用fork(t)发送任务(递归),发送任务完成后立即返回,继续执行其他,然后在调用join()方法,等待子任务完成后将结果合并,并返回结果。
task extends RecursiveTask<>{
int start,end
compute() {
if(end-start<num){
return
}
else {
task1 = new(midlle,end);
task2 = new(start,midlle);
task1.fork();
task2.fork();
return mergeRs(task1.join(),task2.join());
}
}
}
compute()中可以抛出异常,调用task中的方法判断是否有异常和异常的类型,但异常抛出后程序不会停止。
任务在执行之前可以被取消。
14,并发集合(线程安全)
1)阻塞式集合:当集合满或者空时,被调用添加或者删除方法的线程被阻塞,直到方法能够被成功执行。阻塞队列接口和阻塞栈接口.实现原理,事实它和我们用Object.wait()、Object.notify()和非阻塞队列实现生产者-消费者的思路类似.有了这样的功能,就为多线程的排队等候的模型实现开辟了便捷通道,在线程池初始化时传入一个阻塞队列,存放任务。
BlockingQueue:BlockingQueue是阻塞队列的总接口,它的大致成员包括ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue和java7引进的TransferQueue。
ArrayBlockingQueue,LinkedBlockingQueue:分别维护一个数组和链表作为数据缓冲区。在线程同步方面,ArrayBlockingQueue中的生产和消费用一个锁,所以生产和消费不能同时进行,而LinkedBlockingQueue是两个锁。
SynchronousQueue:没有数据缓冲区,一个插入和一个删除必需交替进行。使用两个队列(一个用于正在等待的生产者、另一个用于正在等待的消费者)和一个用来保护两个队列的重入锁。
http://ifeve.com/java-synchronousqueue/
http://wsmajunfeng.iteye.com/blog/1629352/
TransferQueue:是上述阻塞队列的超集,CAS实现锁同步。
http://ifeve.com/java-transfer-queue/
2)非阻塞集合:。。。。。。。。。。。。。。。。。。。。线程不会被阻塞,方法会返回null或者异常。
3)并发集合实现原理:
ConcurrentHashMap:http://www.iteye.com/topic/344876(重点)
基本思路:并发的HashMap利用Sync将Hash表中的各个方法进行加锁,包括读和写方法。这样相当于对整张Hash表加锁,因此这样不但读写互斥,读和读之间也会互斥。
ConcurrentHashMap将整个哈希表分成了多个Segment,即多个小的Hash表,每个Segment单独加锁,这叫锁分离技术。数据存入时,先Hash定位到segment,在Hash存入之中。
写操作(put和remove)需要加锁,put一个节点采用头插法;remove节点时,由于各个节点除了val都是final,所以需要将删除节点之前的节点复制后从新指向。
读操作(get)不需要加锁,第一步是访问count变量(记录每个segment键值对的数量),如果是0就不读了。最后找到get的值时,用return readValueUnderLock(e);在加锁方式下检查一遍读出的值,以避免在读的过程中其他线程修改了这个值。
size方法对所有segment中元素数量求和,先在无锁条件下执行,如果失败,再在有锁条件下执行,判断失败的条件是利用modCount和CAS算法。
这样处理使得ConcurrentHashMap读和读不互斥,读和写也不互斥。
标签:
原文地址:http://blog.csdn.net/guo1992jing/article/details/51636714