目的
解决在多线程访问同一资源时,线程之间的同步互斥问题
Synchronized
对象锁
在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问
在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块
Example:
public class MySynchronized {
public static void main(String[] args) {
final Business business = new Business();
new Thread(new Runnable() {
@Override
public void run() {
business.outSub();
}
}).start();
business.outMain();
}
}
class Business {
public void outSub() {
for(int i=0;i<10;i++) {
System.out.println("Sub Thread loop sequence: " + i);
}
}
public void outMain() {
for(int i=0;i<10;i++) {
System.out.println("Main Thread loop sequence: " + i);
}
}
}
//由于未使用同步机制,Main线程和Sub Thread会交替打印内容
Result:
Main Thread loop sequence: 0
Sub Thread loop sequence: 0
Main Thread loop sequence: 1
Sub Thread loop sequence: 1
Main Thread loop sequence: 2
Sub Thread loop sequence: 2
Main Thread loop sequence: 3
Sub Thread loop sequence: 3
Main Thread loop sequence: 4
Sub Thread loop sequence: 4
Sub Thread loop sequence: 5
Main Thread loop sequence: 5
Sub Thread loop sequence: 6
Sub Thread loop sequence: 7
Main Thread loop sequence: 6
Sub Thread loop sequence: 8
Sub Thread loop sequence: 9
Main Thread loop sequence: 7
Main Thread loop sequence: 8
Main Thread loop sequence: 9
Example2:
public class MySynchronized {
public static void main(String[] args) {
final Business business = new Business();
new Thread(new Runnable() {
@Override
public void run() {
business.outSub();
}
}).start();
business.outMain();
}
}
class Business {
//我们在线程需要执行的方法上加上Synchronized关键字,使线程对该方法的调用达到同步与互斥
public synchronized void outSub() {
for(int i=0;i<10;i++) {
System.out.println("Sub Thread loop sequence: " + i);
}
}
//我们在线程需要执行的方法上加上Synchronized关键字,使线程对该方法的调用达到同步与互斥
public synchronized void outMain() {
for(int i=0;i<10;i++) {
System.out.println("Main Thread loop sequence: " + i);
}
}
}
//由于使用Synchronized实现线程之间的同步与互斥,线程执行个字的方法时由于获取到了对象的锁,而两个线程使用的同一个类对象,则一旦某一个线程先拿到对象的锁,另一个线程就必须等待该线程执行结束,释放对象的锁后,才能重新获得对象的锁执行自己的代码
Result:
Main Thread loop sequence: 0
Main Thread loop sequence: 1
Main Thread loop sequence: 2
Main Thread loop sequence: 3
Main Thread loop sequence: 4
Main Thread loop sequence: 5
Main Thread loop sequence: 6
Main Thread loop sequence: 7
Main Thread loop sequence: 8
Main Thread loop sequence: 9
Sub Thread loop sequence: 0
Sub Thread loop sequence: 1
Sub Thread loop sequence: 2
Sub Thread loop sequence: 3
Sub Thread loop sequence: 4
Sub Thread loop sequence: 5
Sub Thread loop sequence: 6
Sub Thread loop sequence: 7
Sub Thread loop sequence: 8
Sub Thread loop sequence: 9
Tips:
- 如果某一个线程正在执行对象的Synchronized方法,那么该对象的所有Synchronized方法都不能被其他线程所访问,因为每一个对象只有一把锁,当一个线程运行synchronized方法时,就获得了该对象的锁,其他线程自然也就无法获取到对象的锁(除非得到该线程运行结束或出现异常释放锁,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象
- 由于synchronized维护的是对象的锁,因此不同对象的synchronized方法可以被多个线程同时访问
- 当一个线程访问对象的synchronized方法,此时允许其他线程访问那些非Synchronized方法,因为运行那些非Synchronized方法并不需要获取对象的锁
类锁
每个类也会有一个锁,它可以用来控制对static数据成员的并发访问。
并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象
public class MySynchronized {
public static void main(String[] args) {
final Business business = new Business();
new Thread(new Runnable() {
@Override
public void run() {
business.outStaticSub();
}
}).start();
business.outMain();
}
}
class Business {
public static synchronized void outStaticSub() {
for(int i=0;i<10;i++) {
System.out.println("static Sub Thread loop sequence: " + i);
}
}
public synchronized void outMain() {
for(int i=0;i<10;i++) {
System.out.println("Main Thread loop sequence: " + i);
}
}
}
//由于此时static synchronized方法占用了类锁,和普通的Synchronized方法占用的对象的锁并不冲突,所以结果为可能存在交替输出
Result:
Main Thread loop sequence: 0
Main Thread loop sequence: 1
Main Thread loop sequence: 2
Main Thread loop sequence: 3
Main Thread loop sequence: 4
static Sub Thread loop sequence: 0
Main Thread loop sequence: 5
Main Thread loop sequence: 6
static Sub Thread loop sequence: 1
Main Thread loop sequence: 7
static Sub Thread loop sequence: 2
Main Thread loop sequence: 8
static Sub Thread loop sequence: 3
Main Thread loop sequence: 9
static Sub Thread loop sequence: 4
static Sub Thread loop sequence: 5
static Sub Thread loop sequence: 6
static Sub Thread loop sequence: 7
static Sub Thread loop sequence: 8
static Sub Thread loop sequence: 9
Lock
After Java5
既然自Java5推出并发concurrent包,自然是Synchronized存在诸多的缺陷,我们先来讨论下这些缺陷,就更加能体会到Lock的好了
1.假如某一线程在执行synchronized过程中调用sleep()或者陷入死循环导致该线程一直占用锁,而其他线程也一直获取不到锁,此时程序效率将大大降低,因此我们需要实现超时即释放锁,而synchronized作为Java内置特性此时就无能为力了
2.假如我们的程序操作某一份文件,读写方法我们都加上了synchronized关键字,导致线程间写方法互斥,写与读互斥,读与读互斥。而实际情况中线程间读方法与读方法不应该互斥,多个线程之间允许同时读取共享资源的内容
Lock
public interface Lock {
void lock(); //获取锁
void lockInterruptibly() throws InterruptedException; // 使用及原理参考下方的Example
boolean tryLock(); //获取锁,如果得到锁返回true,反之返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //获取锁的过程中允许等待一段时间,如果得到锁返回true,反之返回false
void unlock(); //释放锁
Condition newCondition();
}
lockInterruptibly:当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException
注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为本身在前面的文章中讲过单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去
ReentrantLock
AbstractOwnableSynchronizer源码
public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
protected AbstractOwnableSynchronizer() { }
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
AbstractOwnableSynchronizer源码
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
......
}
ReentrantLock源码
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync(); //默认为非公平锁
}
public ReentrantLock(boolean fair) { //也可以传入参数创建公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
......
}
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
}
}
Example:
public class MyReentrantLock {
public static void main(String[] args) {
Business business =new Business();
new Thread(new Runnable() {
@Override
public void run() {
business.output2();
}
}).start();
business.output2();
}
}
class Business {
Lock lock = new ReentrantLock();
public void output() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " obtain the lock...");
Thread.sleep(3000);
}catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(Thread.currentThread().getName() + " release the lock...");
lock.unlock();
}
}
public void output2() {
if(lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " obtain the lock...");
Thread.sleep(3000);
}catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(Thread.currentThread().getName() + " release the lock...");
lock.unlock();
}
}
}
}
Example:lock.lockInterruptibly()
public class InterruptTest {
public static void main(String[] args) throws InterruptedException{
//only defined one ReentrantLock
MyBusiness2 myBusiness2 = new MyBusiness2();
Thread subThread = new Thread(new Runnable() {
@Override
public void run() {
myBusiness2.output();
}
});
Thread subThread2 = new Thread(new Runnable() {
@Override
public void run() {
myBusiness2.output();
}
});
subThread.start();
// let subThread obtain the lock, we test for subThread2 waiting for the lock
Thread.sleep(100);
subThread2.start();
subThread2.interrupt();
}
}
class MyBusiness2 {
// object lock
private Lock lock = new ReentrantLock();
public void output(){
String currThreadName = Thread.currentThread().getName();
try {
System.out.println(currThreadName+" preparing to obtain the lock");
lock.lockInterruptibly();
System.out.println(currThreadName+" obtain the lock, then sleep... ");
for (int i=0; i<5; i++) {
Thread.sleep(1000);
System.out.println(currThreadName + " : " + i);
}
}catch (InterruptedException e) {
System.out.println(currThreadName+" interrupted" );
}finally {
try {
lock.unlock();
System.out.println(currThreadName+" running over... then release the lock..." );
}catch (Exception e) {
System.out.println(currThreadName+" before obtain the lock alr interrupted..." );
}
}
}
}
Result:
Thread-0 preparing to obtain the lock
Thread-0 obtain the lock, then sleep...
Thread-1 preparing to obtain the lock
Thread-1 interrupted
Thread-1 before obtain the lock alr interrupted...
Thread-0 : 0
Thread-0 : 1
Thread-0 : 2
Thread-0 : 3
Thread-0 : 4
Thread-0 running over... then release the lock...
lockInterruptibly作用:方法能够中断等待获取锁的线程,例如上方的例子,subThread先一步subThread2获取到锁,此时subThread2等待subThread执行结束释放锁,但是在subThread运行过程中,subThread2调用了线程的中断方法,那么此时subThread2将退出锁等待,并会抛出InterruptedException,并且假如后续存在中断锁操作的话,subThread2也会抛出异常吗,因为他自始至终根本没有获取到锁。
ReadWriteLock
ReadWriteLock 顾名思义,将读写操作分开加锁的操作(多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥)
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
ReentrantReadWriteLock
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
private static final sun.misc.Unsafe UNSAFE;
private static final long TID_OFFSET;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
TID_OFFSET = UNSAFE.objectFieldOffset
(tk.getDeclaredField("tid"));
} catch (Exception e) {
throw new Error(e);
}
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
abstract static class Sync extends AbstractQueuedSynchronizer {
private transient ThreadLocalHoldCounter readHolds;
private transient HoldCounter cachedHoldCounter;
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // ensures visibility of readHolds
}
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
}
/**
* Nonfair version of Sync
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
/**
* Fair version of Sync
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquireShared(1);
}
......
}
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
......
}
}
Example:
public class MyReadWriteLock {
public static void main(String[] args) {
MyBusiness myBusiness =new MyBusiness();
ExecutorService threadPool = Executors.newCachedThreadPool();
for(int i=0;i<5;i++) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
myBusiness.readData();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
myBusiness.writeData();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
threadPool.execute(t1);
threadPool.execute(t2);
}
threadPool.shutdown();
}
}
class MyBusiness{
ReadWriteLock readWriteLock =new ReentrantReadWriteLock();
private int sharedData;
public void readData() {
String currThreadName = Thread.currentThread().getName();
readWriteLock.readLock().lock();
try {
System.out.println(currThreadName + " ready --- read data: ");
} catch (Exception e) {
}finally {
System.out.println(currThreadName + " read data: " + sharedData);
readWriteLock.readLock().unlock();
}
}
public void writeData() {
String currThreadName = Thread.currentThread().getName();
readWriteLock.writeLock().lock();
try {
System.out.println(currThreadName + " ready --- write data: ");
sharedData = new Random().nextInt(10000);
} catch (Exception e) {
}finally {
System.out.println(currThreadName + " write data: " + sharedData);
readWriteLock.writeLock().unlock();
}
}
}
Result:
Thread-0 ready --- read data:
Thread-0 read data: 0
Thread-1 ready --- write data:
Thread-1 write data: 147
Thread-7 ready --- write data:
Thread-7 write data: 753
Thread-2 ready --- read data:
Thread-2 read data: 753
Thread-3 ready --- write data:
Thread-3 write data: 4545
Thread-4 ready --- read data:
Thread-4 read data: 4545
Thread-5 ready --- write data:
Thread-5 write data: 2928
Thread-6 ready --- read data:
Thread-6 read data: 2928
Thread-9 ready --- write data:
Thread-9 write data: 9244
Thread-8 ready --- read data:
Thread-8 read data: 9244
ReadWriteLock总结:如果不使用锁,多个线程访问共享数据,必然出现多线程安全问题,但是如果我们使用普通的ReentrantLock存在多线程所有操作(读-读,读-写,写-写)都互斥,导致程序执行效率不高,显示情况中多线程(读-读)操作不应该互斥,因此ReadWriteLock将共享数据的读写操作分离,实现多线程(读-读)操作不互斥,(读-写,写-写)操作依旧互斥,保证线程共享数据安全的同时还提高了程序效率。