Java Synchronized & Lock

目的

解决在多线程访问同一资源时,线程之间的同步互斥问题

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:

  1. 如果某一个线程正在执行对象的Synchronized方法,那么该对象的所有Synchronized方法都不能被其他线程所访问,因为每一个对象只有一把锁,当一个线程运行synchronized方法时,就获得了该对象的锁,其他线程自然也就无法获取到对象的锁(除非得到该线程运行结束或出现异常释放锁,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象
  2. 由于synchronized维护的是对象的锁,因此不同对象的synchronized方法可以被多个线程同时访问
  3. 当一个线程访问对象的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将共享数据的读写操作分离,实现多线程(读-读)操作不互斥,(读-写,写-写)操作依旧互斥,保证线程共享数据安全的同时还提高了程序效率

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值