java并发

作者: 拔刀装酷 | 来源:发表于2014-07-20 14:46 被阅读644次

    java并发

    1. 线程的通信

    wait() 使线程进入睡眠状态
    notify() 随机唤醒一个等待的线程,它将获得一次抢夺锁的机会
    notifyAll() 唤醒所有等待的线程
    临界区: 需要线程同步的代码区
    $$
    运行状态
    \dfrac{ wait() }{}
    睡眠状态
    \dfrac{ notify() }{}
    等锁状态
    \dfrac{ 获得锁 }{}
    运行状态
    $$

    注意

    • 3个方法只能在同步块里面被调用
    • 使用while避免被假唤醒
    • 不要使用全局对象,字符串常量作为锁
        public class MywaitNotify{
            MonitorObject monitor = new MonitorObject();
            boolean wasSignalled = false;
            
            public void doWait() {
                synchronized(monitor){
                    while (!wasSignalled){  //用while代替if防止假唤醒
                        monitor.wait();  //wait(),notify(),notifyAll()必须位于同步块内
                    }
                    wasSignalled = false;
                }
            }
            
            public void doNotify() {
                synchronized(monitor){
                    wasSignalled = true;
                    monitor.notify();  //notity后并不意味着等待线程立即可以继续执行,而只是可以有机会再次抢夺锁
                }
            }
        }
    

    2. 饥饿与公平

    • 若一个线程因为cpu时间全部被其他线程抢走而得不到cpu运行时间,这种状态称之为饥饿
    • 公平锁的实现
    public class FairLock {
        private boolean locked; //锁区是否正处于锁定状态(锁区是否已有其他线程存在尚未离开)
        private Thread lockingThread; //当前在锁区的那个线程
        private List<LockObject> waittingThreads = new ArrayList<LockObject>();
        
        
        public void lock() throws InterruptedException{
            LockObject lockObject = new LockObject();
            boolean isAllowedEnterLockedArea = false;
            
            //线程开始排队,谁先进来,谁先被add进List,也就是排在最前面
            synchronized (this) {
                waittingThreads.add(lockObject);
            }
            
            //若不允许进入锁区,则自旋等待
            while (!isAllowedEnterLockedArea) {
                synchronized (this) {
                    /* 当前线程是否允许进入锁区的线程(isAllowedEnterLockedArea)?
                     * 如果锁区没有其他线程(locked=false) 且 排队排在最前面 
                     * 则当前线程是被允许进入锁区的线程
                     */
                    isAllowedEnterLockedArea = !locked && waittingThreads.get(0) == lockObject;
                    
                    /* 若允许进入锁区,则在进入前将"锁区已有线程"的标志置为true,
                     * 并从排队队列中将自己移除 */
                    if (isAllowedEnterLockedArea) {
                        locked = true;
                        waittingThreads.remove(lockObject);
                        lockingThread = Thread.currentThread();
                        return;
                    }
                }
                
                try {
                    /* 不被允许进入锁区的线程(isAllowedEnterLockedArea=false)
                     * 使用他排队时使用的lockObject对象作为锁标志的锁睡眠了,
                     * 直到有其他线程调用这个lockObject对象的notify()叫醒他
                     * -----------------------
                     * 这里为什么不直接调用lockObject.wait()方法?
                     * --为了防止信号丢失!
                     * 因为自带的wait()或notify()方法是没有状态的, 
                     * 有可能当前线程(线程A)执行到此处,在还没有进入睡眠时, 
                     * 其他线程unlock,就已经notify了线程A, 
                     * 这时由于线程A不是出于睡眠状态而忽略了notify的信号,
                     * 接着线程A进入睡眠, 因为唤醒它的信号已被忽略,
                     * 那么线程A将永远不会醒来
                     */
                    lockObject.doWait();  
                } catch (InterruptedException e) {
                    /* 若因出现异常,在等待进入锁区的睡眠中被异常打断,
                     * 则将其从排队队列中踢出 */
                    synchronized (this) {
                        waittingThreads.remove(lockObject);
                    }
                    throw e;
                }
            }
        }
        
        
        public synchronized void unlock(){
            if (this.lockingThread != Thread.currentThread()){
                throw new IllegalMonitorStateException("当前线程并不是加锁者,解锁失败");
            }
            locked = false;
            lockingThread = null;
            //在离开锁区时,若还有其他线程在排队等待进入锁区,则叫醒排在最前面的那个线程
            if (waittingThreads.size() > 0){
                waittingThreads.get(0).doNotify();
            }
        }
    }
    
    public class LockObject {
        private boolean isNotified;  //使用成员变量记住信号,防止信号丢失(带状态的锁)
        
        public synchronized void doWait() throws InterruptedException{
            while(!isNotified){
                this.wait();
            }
            isNotified = false;
        }
        
        public synchronized void doNotify(){
            this.isNotified = true;
            this.notify();
        }
        
        @Override
        public boolean equals(Object o){
            return this == o;
        }
    }
    

    3. 嵌套管程锁死

    有嵌套同步块A和B,锁分别是LockA,LockB,线程1在持有外层同步块A的锁LockA的情况下,进入同步块B,调用LockB.wait()进入睡眠,等待其他线程调用LockB.notify()将其唤醒, 至此,线程1就释放了LockB, 但仍持有LockA. 这时, 其他线程想进入锁同样为LockA的同步块去唤醒线程A(调用LockB.notify),却发现因线程1持有锁LockA睡眠了,无法获得该锁,从而导致死锁状态
    简要的归纳就是:

        线程带着其它锁没有释放的情况下进入了休眠(因同步块嵌套导致),导致其他线程无法获得这个锁去叫醒他
    

    嵌套管程锁死的例子:

    public class Lock{
        private Monitor monitor = new Monitor();
        private boolean locked;
        
        public void lock(){
            synchronized (this){
                while (locked){
                    synchronized(monitor){
                        //进入休眠时,只释放了monitor锁,没有释放this锁
                        monitor.wait(); 
                    }
                }
                locked = true;
            }
        }
        
        public void unlock(){
            //因为this被占用, 无法进入同步块唤醒已睡眠的线程
            synchronized(this){
                locked = false;
                synchronized(monitor){
                    monitor.notify();
                }
            }
        }
    }
    

    死锁嵌套管程锁死 的区别:

      死锁中,两个线程都在等待对方释放锁. 
      嵌套管程锁死中,线程1持有锁A, 同事等待从线程2发来的信号,线程2需要锁A来发信号给线程1.
    

    4. Slipped Conditions

    从一个线程检查某一特定条件, 到操作此条件期间,这个条件已被其他线程所改变

    public class Lock {
        private boolean locked;
        
        public void lock() {
            synchronized (this){  //同步块1
                while (locked){
                    this.wait();
                }
            }
            
            /* 这里同步块1与同步块2间可能出现slipped conditions:
             * 一个线程(线程A)检查到locked=false跳出同步块1后,即将准备进入同步块2,此时:
             * this锁已被释放. 这时新的线程B调用lock()进入同步块1,检查到locked=false,
             * 因此线程B马上跳出同步块1,进入同步块2,将locked置为true,
             * 接下来线程A才进入同步块2,而此时发现:期间locked已被别人(线程B)修改过了
             */
            synchronized(this){  //同步块2
                locked = true;
            }
        }
        
        public void unlocked() {
            locked = false;
            this.notify();
        }
    }
    

    为避免slipped conditions, 条件的检查和设置必须是原子的, 也就是在第一个线程检查和设置条件期间,不会有其他线程检查这个条件

    相关文章

      网友评论

        本文标题:java并发

        本文链接:https://www.haomeiwen.com/subject/ouestttx.html