synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
注意事项:
1. synchronized关键字不能继承。
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
这两种方式的例子代码如下:
在子类方法中加上synchronized关键字
class Parent{
public synchronized void method() { }
}
class Child extends Parent{
public synchronized void method() { }
}
在子类方法中调用父类的同步方法
class Parent{
public synchronized void method() { }
}
class Child extends Parent{
public void method() {
super.method();
}
}
1.在定义接口方法时不能使用synchronized关键字。
2.构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步
A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
重入锁
(1)重进入:
1.定义:重进入是指任意线程在获取到锁之后,再次获取该锁而不会被该锁所阻塞。关联一个线程持有者+计数器,重入意味着锁操作的颗粒度为“线程”。
2.需要解决两个问题:
线程再次获取锁:锁需要识别获取锁的现场是否为当前占据锁的线程,如果是,则再次成功获取;
锁的最终释放:线程重复n次获取锁,随后在第n次释放该锁后,其他线程能够获取该锁。要求对锁对于获取进行次数的自增,计数器对当前锁被重复获取的次数进行统计,当锁被释放的时候,计数器自减,当计数器值为0时,表示锁成功释放。
3.重入锁实现重入性:每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁
(2)ReentrantLock是的非公平类中通过组合自定义同步器来实现锁的获取与释放。
/**
* Sync中的nonfairTryAcquire()方法实现
* 这个跟公平类中的实现主要区别在于不会判断当前线程是否是等待时间最长的线程
**/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if(c == 0) {
// 跟FairSync中的主要区别,不会判断hasQueuedPredecessors()
if(compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}else if(current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if(nextc < 0)// overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
nonfairTryAcquire()方法中,增加了再次获取同步状态的处理逻辑,通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。
成功获取锁的现场再次获取锁,只是增加了同步状态值,要求ReentrantLock在释放同步状态时减少同步状态值。
/**
* Sync中tryRelease()
**/
protected final boolean tryRelease(int releases) {
// 修改当前锁的状态
// 如果一个线程递归获取了该锁(也就是state != 1), 那么c可能不等0
// 如果没有线程递归获取该锁,则c == 0
int c = getState() - releases;
// 如果锁的占有线程不等于当前正在执行释放操作的线程,则抛出异常
if(Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free =false;
// c == 0,表示当前线程释放锁成功,同时表示递归获取了该锁的线程已经执行完毕
// 则设置当前锁状态为free,同时设置锁的当前线程为null,可以让其他线程来获取
// 同时也说明,如果c != 0,则表示线程递归占用了锁资源,
// 所以锁的当前占用线程依然是当前释放锁的线程(实际没有释放)
if(c == 0) {
free =true;
setExclusiveOwnerThread(null);
}
// 重新设置锁的占有数
setState(c);
return free;
}
如果该锁被获取n次,则前(n-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了,才返回true,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
对于公平锁而言
/**
* FairSync中tryAcquire()的实现
* 返回
* true: 获取锁成功
* false: 获取锁不成功
**/
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取锁资源的状态
// 0: 说明当前锁可立即获取,在此种状态下(又是公平锁)
// >0并且当前线程与持有锁资源的线程是同一个线程则state + 1并返回true
// >0并且占有锁资源的不是当前线程,则返回false表示获取不成功
int c = getState();
if(c == 0) {
// 在锁可以立即获取的情况下
// 首先判断线程是否是刚刚释放锁资源的头节点的下一个节点(线程的等待先后顺序)
// 如果是等待时间最长的才会马上获取到锁资源,否则不会(这也是公平与不公平的主要区别所在)
if(!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}else if(current == getExclusiveOwnerThread()) {//线程可以递归获取锁
int nextc = c + acquires;
// 超过int上限值抛出错误
if(nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
与非公平唯一的区别是判断条件中多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回了true,则表示有线程比当前线程更早地请求获取锁,所以需要等待前驱线程获取并释放锁后才能继续获取该锁。
但是非公平锁是默认实现:非公平性锁可能使线程“饥饿”,但是极少的线程切换,可以保证其更大的吞吐量。而公平性锁,保证了锁的获取按照FIFO原则,代价是进行大量的线程切换。
(3)synchronized可重入性
同一线程在调用自己类中其他synchronized方法/块或调用父类的synchronized方法/块都不会阻碍该线程的执行,就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入。
Lock和synchronized有以下几点不同:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
网友评论