文字总结版本,方便记忆回顾:
后面会有相应的代码进行示例说明:
基本的线程同步 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线程局部变量
线程中的变量赋了值也只能自己用,其他线程要用自己赋值。