Java并发

文字总结版本,方便记忆回顾:

 

后面会有相应的代码进行示例说明:

基本的线程同步 synchronized

(1)new Object() 对象在堆内存中, synchronized申请锁时,锁信息也是申请堆内存中的锁。互斥锁,只要有一个人在使用,其他人就使用不了。                                                                                               

(2)每次要new一个Object太麻烦,简化成以下代码。

若我们要用其中的m方法,需要new一个T类,此时this就是自身,取锁时,是把自身锁定。synchronized锁定的是一个对象,不是代码块。

(3)如果一个代码,在开始的时候就synchronized锁定this,结束的时候才释放,那么它可以简化成下面的代码。直接写在方法的声明中。

(4)在静态方法中使用synchronized锁定的是当前类的class对象。

这里不可以synchronized(this)的原因是,static方法,不需要new一个类就可以访问,所以不存在this.

(5)五个线程共享同一个T对象,所以五个线程访问的是同一个count

(6)当在run方法中加入synchronized,可以保证正确运行,一个synchronized执行的代码是原子操作,不可分。

public synchronized void run()

 

(7)同步和非同步方法可以同时调用

JAVA8 中的lamada表达式

new Thread(()->t.m1(), "t1").start();

相当于new了一个new Runnabel对象,在方法里执行了m1方法。

相当于:


new Thread(new Runnable() {

   @Override

   public void run() {

      t.m1();

   }
});


(8)对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题(dirtyRead)

所以给读方法也需要加锁。但有时候接受延迟,也可以不加锁,最终会是正确的。

(9)一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.

 也就是说synchronized获得的锁是可重入的

(10)重入锁的第二种情形,子类同步方法可以调用父类同步方法。

(11)程序在执行过程中,如果出现异常,默认情况锁会被释放

所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。因此要非常小心的处理同步业务逻辑中的异常。

如果不想锁释放可以加上 try/catch

当t1抛出异常之后释放锁,t2才会开始执行,否则不会。

(12) volatile 关键字,使一个变量在多个线程间可见,即多个线程进行操作共享数据时,可以保证内存中数据可见。

A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道。

而使用volatile关键字,会让所有线程都会读到变量的修改值

可以阅读这篇文章进行更深入的理解

http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html

volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized

volatile不保证原子性,只保证可见性

synchronized保证原子性和可见性,效率较低。

(13)解决(加减等简单运算)同样的问题的更高效的方法,使用AtomXXX类

AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的,因为两个方法中间可能有其他线程

(15)synchronized优化:同步代码块中的语句越少越好

只在需要同步时上锁,采用细粒度的锁,可以使得线程争用时间变短,从而提高效率。

(16)锁定某对象o,如果o的属性发生改变,不影响锁的使用。但是如果o变成另外一个对象,则锁定的对象发生改变

应该避免将锁定对象的引用变成另外的对象

(17)不要以字符串常量作为锁定对象

(18)练习题:实现一个容器,提供两个方法,add,size。写两个线程,线程1添加10个元素到容器中,线程 2 实现监控元素的个数,当个数到5个时,线程 2 给出提示并结束。

可以使用Latch(门闩)替代wait notify来进行通知

好处是通信方式简单,同时也可以指定等待时间

使用await和countdown方法替代wait和notify

CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行

当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了。

这时应该考虑countdownlatch/cyclicbarrier/semaphore

门闩等待,等待开门,不需要锁定任何对象,而wait需要和锁一起用。

(19)reentrantlock (重复锁)可用于替代synchronized

Reentrantock和synchronized的区别。

使用reentrantlock可以完成同样的功能

需要注意的是,必须要必须要必须要手动释放锁(重要的事情说三遍)

使用syn锁定遇到异常,jvm会自动释放锁;但是lock必须手动释放锁,因此经常在finally中进行锁的释放。

 

想要两个线程互斥,锁定一把锁就可以了。

 

使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行

可以根据tryLock的返回值来判定是否锁定

也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中。

 

使用reentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断。

lock.lockInterruptibly(); 用该方法进行锁定,可以用interrupt打断。

 

ReentrantLock还可以指定为公平锁

参数为true表示为公平锁 private static ReentrantLock lock=new ReentrantLock(true);  

默认的synchronized是非公平锁,多个线程等待锁给谁无规律。而公平锁是谁等待时间久就给谁,公平锁效率更低,但是更公平。    

(20)写一个固定容量同步容器,拥有put和get方法,以及getCount方法

能够支持2个生产者线程以及10个消费者线程的阻塞调用。

 

使用wait和notify/notifyAll来实现

使用Lock和Condition来实现

对比两种方式,Condition的方式可以更加精确的指定哪些线程被唤醒

signalAll() 通知所有对应条件的线程

 

lock和await、signal一起使用

synchronized 和 wait和notify一起使用

 

(21)ThreadLocal线程局部变量

线程中的变量赋了值也只能自己用,其他线程要用自己赋值。

 

你可能感兴趣的:(JAVA并发)