技术/杨33

Java并发编程系列:

(一)Java并发编程,线程的使用

(二)Java并发编程之关键字synchronized

(三)Java并发编程之wait和notify、线程的设计模式使用

一、park和unpark方法

这两个方法是工具类LockSupport中的方法。

用法如下:

暂停当前线程:LockSupport.park();

恢复当前线程的运行:LockSupport.unpark(该参数为被暂停的线程对象);

二、线程的6种状态切换

线程死锁_线程死锁的原因_线程死锁的原因及解决方法

Java线程的6种状态:

1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

3.阻塞(BLOCKED):表示线程阻塞于锁。

4.等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

5.超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

6. 终止(TERMINATED):表示该线程已经执行完毕。

视频教程:详细说明Java线程的状态是如何切换

三、线程的活跃性问题:死锁

锁的粒度没细分之前,并发度太低,影响代码效率。

但是锁的粒度细分后,也有好处和坏处:

死锁就是:t1线程获得A对象的锁,接下来想获取B对象的锁;t2线程获得B对象的锁,接下来想获取A对象的锁,如果t1、t2线程分别永远不释放持有的A对象、B对象锁,接下来,t1、t2线程分别想获取B对象、A对象的锁,这两个t1、t2线程将永远的等待下去,永远被阻塞,这就是死锁的现象。

一段死锁的代码如下:

    public static void main(String[] args) {
        //对象锁A
        Object objectA = new Object();
        //对象锁B
        Object objectB = new Object();
        //线程t1
        Thread t1 = new Thread(() -> {
            synchronized (objectA) {
                System.out.println("t1线程取得A对象的锁");
                synchronized (objectB) {
                    System.out.println("t1线程取得B对象的锁");
                }
            }
        }, "t1");
        //线程t2
        Thread t2 = new Thread(() -> {
            synchronized (objectB) {
                System.out.println("t2线程取得B对象的锁");
                synchronized (objectA) {
                    System.out.println("t2线程取得A对象的锁");
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }

运行结果如图:

线程死锁的原因_线程死锁_线程死锁的原因及解决方法

在编码过程中,发现使用Junit单元测试不支持多线程测试,比如下面代码:

    @org.junit.Test
    public void threadTest(){
        //对象锁A
        Object objectA = new Object();
        //对象锁B
        Object objectB = new Object();
        //线程t1
        Thread t1 = new Thread(() -> {
                synchronized (objectA) {
                    System.out.println("t1线程取得A对象的锁");
                    synchronized (objectB) {
                        System.out.println("t1线程取得B对象的锁");
                    }
                }
        }, "t1");
        //线程t2
        Thread t2 = new Thread(() -> {
                synchronized (objectB) {
                    System.out.println("t2线程取得B对象的锁");
                    synchronized (objectA) {
                        System.out.println("t2线程取得A对象的锁");
                    }
                }
        }, "t2");
        t1.start();
        t2.start();
    }

结果控制台打印的结果如下图:

线程死锁的原因_线程死锁的原因及解决方法_线程死锁

为什么线程没结束,就系统退出呢?

那是因为单元测试的底层逻辑是使用了TestRunner类的main方法,即使还有其他的线程在运行,main也会调用System.exit(0),System.exit()是系统调用,通知系统立即结束jvm的运行,即使jvm中有线程在运行,jvm也会停止的。

线程死锁的原因_线程死锁的原因及解决方法_线程死锁

四、定位死锁的方式

两种方式分别是:

1、使用jconsole工具

线程死锁的原因_线程死锁_线程死锁的原因及解决方法

线程死锁的原因及解决方法_线程死锁_线程死锁的原因

2、使用jps和jstack

五、Java对synchronized的优化

在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。

到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比ReentrantLock差。官方也表示,他们也更支持synchronized,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

六、ReentrantLock

它是在内置锁synchronized无法满足需求的情况下,ReentrantLock可作为一种高级的工具来使用,否则还是优先使用synchronized。

相对于synchronized,它具备以下特点:

与synchronized一样,都支持可重入。

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果不支持可重入,那么第二次获取锁时,自己也会被锁挡住。

ReentrantLock语法:

        //ReentrantLock对象
        ReentrantLock reentrantLock = new ReentrantLock();
        //获取锁
        reentrantLock.lock();
        try {
            //临界区
        } finally {
            //释放锁
            reentrantLock.unlock();
        }

ReentrantLock的特点使用代码操作:

1、线程可中断,说明线程在等待获取锁的过程中,可以使用方法interrupt()方法中止等待

代码如下:lockInterruptibly()方法

    //ReentrantLock对象
    static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                System.out.println("尝试获取锁");
                //这儿使用的方法是lockInterruptibly()
                reentrantLock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("被打断了,中断阻塞线程的运行");
                //被中断,就结束该方法
                return;
            }
            try {
                System.out.println("获取到锁");
            }finally {
                //释放锁
                reentrantLock.unlock();
            }
        });
        //主线程获取到锁,让线程t1阻塞
        reentrantLock.lock();
        t1.start();
        //中断正在阻塞的线程t1
        System.out.println("打断阻塞的线程t1");
        t1.interrupt();
    }

控制台打印结果:

打断阻塞的线程t1
尝试获取锁
被打断了,中断阻塞线程的运行
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at Test.lambda$main$0(Test.java:45)
	at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0

2、设置超时时间:tryLock()方法

该方法还有一种有入参的方式:reentrantLock.tryLock(2, TimeUnit.SECONDS),表示,等待2秒钟。

    //ReentrantLock对象
    static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("尝试获取锁");
            //这儿使用的方法是tryLock()
            if (!reentrantLock.tryLock()) {
                System.out.println("获取不到锁");
                return;
            }
            try {
                System.out.println("获取到锁");
            }finally {
                //释放锁
                reentrantLock.unlock();
            }
        });
        //主线程先获取到锁,让线程t1阻塞,获取不到锁
        reentrantLock.lock();
        t1.start();
    }

控制台打印结果:

尝试获取锁
获取不到锁
Process finished with exit code 0

3、多个条件变量,来存放处于等待状态的线程队列

使用流程:

    //ReentrantLock对象
    static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        //多个条件condition1、condition2
        Condition condition1 = reentrantLock.newCondition();
        Condition condition2 = reentrantLock.newCondition();
        reentrantLock.lock();
        //进入休息室等待锁
        condition1.await();
        condition2.await();
        //唤醒其中一个等待的线程
        condition1.signal();
        //唤醒所有等待的线程
        condition1.signalAll();
    }

七、使用ReentrantLock的tryLock()来解决上面标题三的死锁问题

代码如下:

    //ReentrantLock对象A
    static ReentrantLock reentrantLockA = new ReentrantLock();
    //ReentrantLock对象B
    static ReentrantLock reentrantLockB = new ReentrantLock();
    public static void main(String[] args) {
        //线程t1
        Thread t1 = new Thread(() -> {
            if (reentrantLockA.tryLock()) {
                try {
                    System.out.println("t1线程取得A对象的锁");
                    if (reentrantLockB.tryLock()) {
                        try {
                            System.out.println("t1线程取得B对象的锁");
                        } finally {
                            reentrantLockB.unlock();
                        }
                    }
                } finally {
                    reentrantLockA.unlock();
                }
            }
        }, "t1");
        //线程t2
        Thread t2 = new Thread(() -> {
            if (reentrantLockB.tryLock()) {
                try {
                    System.out.println("t2线程取得B对象的锁");
                    if (reentrantLockA.tryLock()) {
                        try {
                            System.out.println("t2线程取得A对象的锁");
                        } finally {
                            reentrantLockA.unlock();
                        }
                    }
                } finally {
                    reentrantLockB.unlock();
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }

控制台打印结果,没有再出现因为线程死锁,而导致程序无法结束运行

线程死锁_线程死锁的原因_线程死锁的原因及解决方法

八、总结

使用synchronized或者ReentrantLock,可以达到共享资源的互斥效果,保证临界区中代码的原子性。

限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: lzxmw777

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注