欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JDK同步控制工具,JAVA高并发程序设计

程序员文章站 2022-05-22 12:46:42
...

同步控制是并发程序的重要手段之一,我们平常用过最多的Synchronized就是其中一种简单的方法,此外还有Object.wait(),Object.notify()等方法。在JDK之中,还有其他好用的工具。

重入锁_ReentrantLock:

一个可重入的互斥锁Lock,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。ReentrantLock将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用lock的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用isHeldByCurrentThread()getHoldCount()方法来检查此情况是否发生。举个栗子:

public class Demo_1 implements Runnable{

    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            lock.lock();
            try {
                i++;
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Demo_1 A = new Demo_1();
        Thread t1 = new Thread(A);
        Thread t2 = new Thread(A);
        t1.start();t2.start();
        t1.join();t2.join();
    }
}

使用重入锁,在并发时,保护i的安全性。重入锁还有一个特点,跟它的名字一样,可以反复进如入:

for (int j = 0; j < 1000000; j++) {
            lock.lock();
            lock.lock();
            try {
                i++;
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
                lock.unlock();
            }
        }

跟synchronized相比,重入锁有显示操作,必须手动指定何时加锁和释放锁,对逻辑控制的灵活性要好于synchronized,唯一注意的就是必须释放锁。另外,重入锁还可以提供中断功能。使用synchronized时,一个线程在等待锁,最后结果就两种,要么获得锁,要么一直等待下去,这种情况对于实际业务需求来讲,并不友好。比如你朝思暮想的女朋友来看你了,在这种干柴烈火,风花雪月,月黑风高的时候,你正准备干点什么,女朋友亲戚来了,这时候你难道要一直等下去吗,那肯定不行,当然是停下来,然后开始学习JDK同步控制工具,JAVA高并发程序设计

public class Demo_2 implements Runnable {

    public static ReentrantLock rLockA = new ReentrantLock();
    public static ReentrantLock rLockB = new ReentrantLock();
    int lock;

    public Demo_2(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            if (lock == 1) {
                rLockA.lockInterruptibly();
                System.out.println("占用lock="+lock+":"+System.currentTimeMillis());
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                rLockB.lockInterruptibly();
            } else {
                System.out.println("占用lock="+lock+":"+System.currentTimeMillis());
                rLockB.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                rLockA.lockInterruptibly();
            }
        } catch (Exception e) {
            // TODO: handle exception
        } finally {
            if (rLockA.isHeldByCurrentThread())
                rLockA.unlock();
            if (rLockB.isHeldByCurrentThread())
                rLockB.unlock();
            System.out.println(Thread.currentThread().getId() + " :线程退出 \b lock="+lock);
        }
    }

    public static void main(String[] args) throws Exception {
        Demo_2 demoA = new Demo_2(1);
        Demo_2 demoB = new Demo_2(2);
        Thread rA = new Thread(demoA);
        Thread rB = new Thread(demoB);
        rA.start();rB.start();
        Thread.sleep(1000);
        rB.interrupt();
    }

}

当rA和rB同时执行,rA先占用lockA,再占用lockB,而rB先占用lock2,再占用lockA,这样就形成了相互等待。在这里,我们使用了 lockInterruptibly() 这个方法,这是一个可以对中断,休眠的线程进行响应的锁申请操作。

JDK同步控制工具,JAVA高并发程序设计

在上面的列子中,lockInterruptibly() 的优先作用是响应中断,就好比有个特工深入敌后,随时执行外面给的命令,因为外面的人进不来。最后我们调用了interrupt 方法,来中断rB线程,那么rB就会放弃lockA锁的申请,同时释放lockB的锁,这样rA就可以继续执行下去了。

除了这种等待外部通知外,还提供了一种其他方法:限时等待。顾名思义,就是在指定时间内,线程会一直等待锁的获取,超出时间就不再等待了。这里我们用 tryLock() 来实现:

public class Demo_3 implements Runnable {

    public static ReentrantLock rLock = new ReentrantLock();

    public Demo_3() {
        System.out.println("初始化:"+Thread.currentThread().getId());
    }

    @Override
    public void run() {
        try {
            if (rLock.tryLock(5, TimeUnit.SECONDS)) {
                Thread.sleep(6000);
                System.out.println("执行完成:"+Thread.currentThread().getId());
            } else {
                System.out.println("获取锁失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        }

    }

    public static void main(String[] args) {
        Demo_3 demo = new Demo_3();
        Thread t1 = new Thread(demo);
        Thread t2 = new Thread(demo);
        t1.start();
        t2.start();
    }

}

tryLock() 的两个参数也很好理解,一个是等待时长,另一个就是时间单位。如果不带参数,就表示不等待,如果当前锁没有被其他线程占用,就直接返回true,反之直接返回false。

Condition 条件:

使用Synchronized的时候,我们可以用wait()和notify()方法来达到控制线程的目的。同样的在重入锁中,也有类似的方法,await(),awaitUninterruptibly()和singal()。

  • await():当前线程等待,同时释放锁,其他线程使用singal()或者singalAll()方法时,重新获得锁并继续执行。当线程中断时,能跳出等待。
  • awaitUninterruptibly():和await()功能差不多,但是它无法等待过程中响应中断。
  • singal():唤醒另一个在等待中的线程。
相关标签: 线程 线程锁