同步锁(lock):
(1)从Java5开始,Java提供了一种功能更强大的线程同步机制–通过显式的定义同步锁对象来实现同步,在这种机制下同步锁由lock对象充当
(2)Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象
(3)Lock是控制多个线程对共享资源进行访问的工具,通常,锁提供了对共享资源的独占访问,每次只能有一个对象对Lock进行加锁, 线程开始访问共享资源之前应先获得Lock对象。
(4)某些锁可能允许对共享资源进行并发访问,如ReadWriteLock(读写锁),Lock ,ReadWriteLock是Java5提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁实现类),为ReadWriteLock提供了ReentranReadWriteLock实现类。
(5)Java8新增了新型的StampedLock类,绝大对数场景中他可以替代传统的ReentrantReadWriteLock。ReentrantReadWriteLock为读写操作提供了三种锁模式,Writing ReadingOptimitic Reading。
(6)在实现线程安全的控制中,比较常用的是ReentranLock(可重入锁),使用该Lock对象可以显式的加锁,释放锁,使用其代码格式如下:
class c
{
//定义锁对象
private final ReentrantLock lock=new ReentrantLock();
//...........
//定义需要保证线程安全的方法
publi void m(){
// 加锁
lock.lock();
try
{
//需要保证线程安全的代码
}
//使用finally块来保证释放锁
finally{
lock.unlock;
}
}
}
//改写Account类:
mport java.util.concurrent.locks.ReentrantLock;
public class Account {
//定义锁对象
private final ReentrantLock lock=new ReentrantLock();
//封装用户编号,账户余额两个成员变量
private String accountNo;
private double balance;
public Account(){
}
//构造器
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
}
//因为账户余额不允许随便修改,所以只为balance提供getter
public double getBalance(){
return this.balance;
}
//提供一个线程安全的draw()方法来完成取钱操作
public void draw(double drawAccount){
//加锁
lock.lock();
try{
//账户余额大于取钱数目
if(balance>=drawAccount){
//吐出钞票
System.out.println(Thread.currentThread().getName()+"取钱成功,吐出钞票"+drawAccount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//修改余额
balance -=drawAccount;
System.out.println("\t余额为:"+balance);
}
else{
System.out.println(Thread.currentThread().getName()+"余额不足,取钱失败");
}
}
finally{
//释放锁
lock.unlock();
}
//省略hashCoe()方法和equals()方法
}
}
死锁:
当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有措施来处理死锁的情况,所以多线程编程时应该采取措施避免死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
//死锁实例代码:
class A{
public synchronized void foo(B b){
System.out.println("当前线程名:"+Thread.currentThread().getName()
+"进入A实例的foo()方法");//标记1
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()
+"企图调用B实例的last()方法"); //标记3
b.last();
}
public synchronized void last(){
System.out.println("进入了A类的last()方法内部");
}
}
//***************************************************//
class B{
public synchronized void bar(A a){
System.out.println("当前线程名:"+Thread.currentThread().getName()
+"进入B实例的bar()方法"); //标记2
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()
+"企图调用A实例的last()方法");//标记4
a.last();
}
public synchronized void last(){
System.out.println("进入了A类的last()方法内部");
}
}
//************************************************//
public class DeadLock implements Runnable{
A a=new A();
B b=new B();
public void init(){
Thread.currentThread().setName("主线程");
//调用a对象的foo()方法
a.foo(b);
System.out.println("进入主线程之后");
}
@Override
public void run() {
// TODO Auto-generated method stub
Thread.currentThread().setName("副线程");
//调用b对象的bar()方法
b.bar(a);
System.out.println("进入副线程之后");
}
//******************************************************//
public static void main(String[] args) {
DeadLock dl =new DeadLock();
//以dl为target启动新线程
new Thread(dl).start();
//调用init()方法
dl.init();
}
}
//结果
当前线程名:主线程进入A实例的foo()方法
当前线程名:副线程进入B实例的bar()方法
当前线程名:主线程企图调用B实例的last()方法
当前线程名:副线程企图调用A实例的last()方法结果
//产生原因:
(1)A对象和B对象都是同步方法,也就是A对象和B对象都是同步锁。
(2)程序中两个线程执行,run()方法和init()方法
(3)run()方法让让B对象调用bar()方法,init()方法让A对象调用foo()方法
(4)结果显示init()方法先执行,调用了A对象的foo()方法,进入foo()之前,该线程对A对象加锁–程序执行到标记1时,主线程暂停200ms;CPU切换到另一个线程,让B对象执行bar()方法
(5)进入bar()方法之前,该线程对B对象加锁–程序执行到标记2时,副线程也暂停200ms
(6)接下来主线程会先醒来,继续向下执行,直到标记3希望调用B对象的last()方法,执行该方法前必须对B对象加锁,但此时副线程正保持对B对象的锁,所以主线程阻塞;
(7)接下来副线程也应该醒来了,继续向下执行,直到在标记4时希望调用A对象的last()方法,但此时主线程保持着对A对象的锁,。
(8)此时,主线程保持A对象的锁,等待对B对象加锁;副线程保持对B对象的锁,等待对A对象加锁,两个线程互相等待对方先释放,所以就出现了死锁。
**由于Thread类的suspend()方法也很容易导致死锁,所以Java不再推荐使用该方法来暂停的执行。