锁对象
有两种机制防止代码块受到并发的干扰:
1.一种是Synchronized关键字,自动提供一个锁和相关的条件对象。
2.jdk 5.0引入了Reentrantlock类。
Reentrantlock的用法:
使用RentrantLock构建的是一个可重入的锁,对于重入的概念不是非常的理解
锁的升级与对比
Java1.6为了减少获得锁和释放锁带来的消耗,引入了“偏向锁”和“轻量级锁”,
Java1.6中一共有4中锁状态,基本从低到高依次是:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。这几个锁会随着竞争情况逐步升级,锁可以升级当时不能降级。
偏向锁为什么要使用锁:
在多线程应用中,如果有两个或两个以上线程需要同时去修改一个共享的数据,这种情况就有可能产生讹误的对象,这个情况通常称为:竞争条件。
假设:线程1正在执行修改操作,但是这个操作还没有完成,它被线程调度器剥夺了运行权,这个时候第2个线程修改了数据,然后第1个线程被唤醒继续执行,这个操作将会擦除第2个线程的更新操作。
如果在这一系列的操作上加上锁机制,那么就不会出现这种数据讹误的情况,加上锁后将会这么执行:线程1开始启动,并且拿到锁,在执行结束前被停止,假定第2个线程启动了,由于第2个线程没有获得锁,将在调用lock方法的时候被阻塞,必须等待第1个线程执行完毕释放锁,才能正常执行。
为什么需要在finally里释放锁:
这是至关重要的,如果在临界区的代码抛出异常可以保证锁必须被释放,否则其他线程将永远阻塞。
条件对象
为什么使用条件:
假设一个线程获得了锁,但是这个线程并不满足某些条件,不能够有效的完成所有的工作,这个时候我们就需要手动的让线程等待,并释放锁,由其他满足条件的线程执行有效的工作。
如何使用条件:
一个线程调用wait方法,它没有办法激活自身,只能等待其他线程调用signaAll方法,来激活。如果没有其他线程来激活它,将会出现死锁现象。如果所有的线程被阻塞,做最后一个线程在解除其他线程阻塞状态前调用了wait方法,那么这个程序就挂起了。
SignalAll并不能立即激活线程,它仅仅解除线程的阻塞状态,以便在当前线程退出后,通过竞争来实现对象的访问。
另一个方法signal,是随机解除一个等待线程,这比解除所有线程跟有效,但是也很危险,如果被唤醒的线程不满足条件,再次进入等待,并且没有其他线程可以调用signal,系统就死锁了。
Synchronized关键字
用法:
将方法申明为synchroized(可以申明静态方法),要调用这个方法线程必须获取内部的对象锁,内部对象锁只有一个相关的条件对象,wait方法添加一个线程到等待集中,notifyAll/notfiy方法,解除等待线程的阻塞状态。
内部锁和条件的局限性:
1. 不能中断一个正在试图获得锁的线程
2. 试图获得锁时不能设定超时
3. 每个锁仅有一个单一条件,可能不够
在代码中应该使用那种机制?Lock和condition对象还是同步方法(synchrionized)?
用java.util.concurrent包中的一种机制,它会为你处理所有的枷锁,你会看到如何使用阻塞队列来同步完成一个共同任务。
4. 如果synchronized适合你的程序,那么尽量使用它,可以减少代码,减少出错几率。
5. 如果特别需要lock和condition结构提供的独有特性时,才使用。
锁和条件的关键之处:
1. 锁用来保护代码片段,任何时候只能由一个线程访问被保护的代码。
2. 锁可以管理试图进入被保护代码的线程
3. 锁可以拥有一个或多个条件对象
4. 每个条件对象管理已经进入被保护代码但不能运行的线程。
同步阻塞
Object lock=New Object();
每一个java对象有一个锁,通过synchronized(lock) 进入一个同步阻塞,获得Object的锁,有时候程序员使用对象的锁来实现原子性,实际上称为客户端锁定,客户端锁定非常脆弱不推荐使用。
监视器概念
没有什么实质性的作用
Volatile域
Volatile为实例域提供一种免锁机制,如果申明一个域为volatile,那么编译器和虚拟机就知道该域是有可能被另一个线程并发跟新,volatile不能提供原子性,实例域有可能在方法中被从新赋值。
同步格言:“如果向一个变量写入值,而这个变量接下来可能会被另外一个线程读取, 或者,从一个变量读值,而这个变量可能是之前被另外一个线程写入的,此时必须同步”
Final变量
除非使用锁和volatile,否则无法从多个线程安全的读取一个域,使用final变量申明一个实例域,就可以保证其他线程获取的值不会为null
原子性
要对共享变量进行原子性修饰。
死锁
所有的线程进入阻塞状态,并且没有其他线程可以唤醒,出现死锁,程序挂起。
避免死锁的几种方式:
1. 避免一个线程同时获取多个锁。
2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
3. 尝试使用定时锁,lock.tryLock(timeout),这种锁可以打破死锁。
4. 对于数据库锁,加锁和解锁必须在一个数据库链接里,否则会出现解锁失败得情况。
线程局部变量
如果需要避免共享变量,可以使用ThreadLocal为每个线程提供各自的实例域,可以为单独的线程提供生成器。
锁测试与超时
使用trylock方法试图申请一个锁,如果成功获得锁则返回true,否则立即返回false,而且当前线程可以立即离去做其他事情。
如果使用带有超时参数的trylock,线程在等待期间中断,将抛出InterruptedException异常,这是一个非常有用的特性,它允许打破死锁。
读 / 写锁
使用ReentrantReadWriteLock类可以获取一个读写锁对象,
使用RW.readlock()得到一个可以被多个读操作公用的读锁,但会排斥写操作。
使用RW.writeLock()得到一个写锁排斥所有其他读操作与写操作
为什么弃用stop和suspend方法
Stop方法天生不安全,suspend方法经常导致死锁。