并发编程学习
来源:互联网 发布:自动生成声音软件 编辑:程序博客网 时间:2024/06/04 23:43
注意:1,在使用synchronized块来同步方法时,非静态方法可以通过this来同步,而静态方法必须使用class对象来同步,但是非静态方法也可以通过使用class来同步静态方法。但是静态方法中不能使用this来同步非静态方法。这点在使用synchronized块需要注意。
2,所以一个对象内同时存在加锁的动态方法和静态方法时,可以被并发访问。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
循环的内容是
1.取得当前值
2.计算+1后的值
3.如果当前值还有效的话设置那个+1后的值
总结来说,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进程中可以运行很多线程。
1),执行器框架(Executor Framework)
(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
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读和读不互斥,读和写也不互斥。
- CopyOnWriteArrayList:通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
- 写数据时加锁,否则会copy出多个副本出来;读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。
- 这样实现读写不互斥,读和读也不互斥。但不能保证数据的强一致性。
- http://www.cnblogs.com/dolphin0520/p/3938914.html
如何避免死锁:java中,增大锁的粒度,Lock中的tryAcquire
死锁实现(会写):注意此处Object前加上static的原因是让所有的TestDeadLock类共享同一个o1和o2对象,因为在main中new了两个TestDeadLock。如果不写static,每个TestDeadLock对象都会有自己的o1和o2. 这里并不是类锁哦。
- 并发编程学习总结
- 并发编程学习总结
- 并发编程学习总结
- Java并发编程学习
- 并发编程学习记录
- 并发编程学习
- 并发编程学习链接
- 并发编程学习小记
- 并发编程学习
- java并发编程实践学习
- Java并发编程学习2
- Java多线程并发编程学习
- scala学习十一 并发编程
- java7并发编程学习笔记
- java的并发编程学习
- Java并发编程学习路线图
- JAVA并发编程学习笔记
- Scala学习--并发编程--socket
- 访问者模式
- Centos接触 (七)安装JDK和tomcat
- 有关opencv光流法的解释
- 解决 ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务
- EventBus 3.0使用基础
- 并发编程学习
- acm之搜索题目1
- C#操作word:将rtf中的ole对象通过MathType转换成MathML
- TCP数据报首部
- 如何来看单片机外设A/D转换器ADC0804时序图
- 设计模式总结
- 分布式系统里session同步的那些事儿
- Java基础知识
- Spring提供的单元测试