JAVA并发编程基础概念知识点(二)

人生之路不会是一帆风顺的,我们会遇上顺境,也会遇上逆境,在所有成功路上折磨你的,背后都隐藏着激励你奋发向上的动机,人生没有如果,只有后果与结果,成熟,就是用微笑来面对一切小事。

导读:本篇文章讲解 JAVA并发编程基础概念知识点(二),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

5、synchronized

synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized 块。

Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。

6、Volatile关键字

总结:volatile关键字是用于保证有序性和可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

可见性: 指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

有序性: 即程序执行的顺序按照代码的先后顺序执行

不保证原子性

我们所写的代码,不一定是按照我们自己书写的顺序来执行的,编译器会做重排序,CPU也会做重排序的,这样的重排序是为了减少流水线的阻塞的,引起流水阻塞,比如数据相关性,提高CPU的执行效率。
有序性通过插入内存屏障来保证。

可见性:首先Java内存模型分为,主内存,工作内存。比如线程A从主内存把变量从主内存读到了自己的工作内存中,做了加1的操作,但是此时没有将i的最新值刷新会主内存中,线程B此时读到的还是i的旧值。而加了volatile关键字的代码的汇编代码,会多出一个lock前缀指令。Lock指令对Intel平台的CPU,早期是锁总线,这样代价太高了,后面提出了缓存一致性协议,MESI,来解决了多核之间数据不一致性问题。一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。禁止进行指令重排序。

7、Lock锁

Lock 是 java.util.concurrent.locks 包 下的接口,Lock 实现提供了比 synchronized 关键字 更广泛的锁操作,它能以更优雅的方式处理线程同步问题。Lock提供了比synchronized更多的功能。

   1.Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是ReentrantLock(可重入锁),ReadWriteLock(读写锁)的代表实现类是ReentrantReadWriteLock。

  2.Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。
   
   3.ReadWriteLock 接口以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现 ReentrantReadWriteLock。

   4 .Lock是可重入锁,可中断锁,可以实现公平锁和读写锁,写锁为排它锁,读锁为共享锁。ReentrantLock也是一种排他锁 
 

8、死锁

定义:在两个或多个并发进程中,如果每个进程持有某种资源而又都等待着别的进程释放它或它们现在保持着的资源,否则就不能向前推进,此时每个进程都占用了一定的资源但又都不能向前推进,称这一组进程产生了死锁。

JAVA并发编程基础概念知识点(二)

产生原因:1、系统资源不足;2、进程推进顺序非法。

产生死锁的四个必要条件:

互斥条件:涉及的资源是非共享的。

不剥夺条件:进程所获得的资源在未使用完毕之前不能被其它进程强行夺走。

部分分配:进程每次申请它所需要的一部分资源,在等待新资源的同时继续占用已分配到的资源。

环路条件:存在着一种进程的循环链,链中的每一个进程已获得的资源同时被链中的下一个进程请求。

死锁的解决方法:

简单描述(四种方法):

        预防:通过设置某些限制条件,以破坏产生死锁的四个条件中的一个或者几个,来防止发生死锁。

        避免:系统在分配资源时根据资源的使用情况提前作出预测,从而避免死锁的发生。

        检测:允许系统在运行的过程中产生死锁,但是,系统中有相应的管理模块可以及时检测出已经产生的死锁,并且精确地确定与死锁有关的进程和资源,然后采取适当措施,清除系统中已经产生的死锁。

        解除:与检测死锁相配套的一种措施,用于将进程从死锁状态下解脱出来。

具体描述(三种方法):

        预防:根据生产死锁的四个必要条件,只要使用其中之一不能成立,死锁就不会出现。但必要条件①是由设备的固有特性所决定的,不仅不能改变,相反还应加以保证,因此实际上只有三种方法。

        预防死锁是一种较易实现的方法,已被广泛使用,但由于所施加的相知条件往往太严格,可能导致系统资源利用率和系统吞吐量降低。即具体可通过以下3种方法实现:

        1、防止部分分配(摒弃请求和保持条件)

        系统要求任一进程必须预先申请它所需要的全部资源,而且仅当该进程的全部资源要求能得到满足时,系统才能给予一次性分配,然后启动该进程运行,但是在分配时只要有一种资源不满足,系统就不会给进程分配资源。进程运行期间,不会再请求新的资源,所以,再分配就不会发生(摒弃了部分分配)。其特点为:资源严重浪费,进程延迟进行。

        2、防止“不剥夺”条件的出现

        采用的策略:一个已经保持了某些资源的进程,当它再提出新的资源要求而不能立即得到满足时,必须释放它已经保持的所有资源,待以后需要时再重新申请。此方法实现比较复杂,且要付出很大代价;此外,还因为反复地申请和释放资源,而使进程的执行无限地推迟,延长了周转时间,增加了系统的开销,降低了系统吞吐量。(例如打印机打印的结果不连续)

        3、防止“环路等待”条件的出现

        采用资源顺序使用法,基本思想是:把系统中所有资源类型线性排队,并按递增规则赋予每类资源以唯一的编号,例如输入机=1,打印机=2,磁带机=3,硬盘=4等等。进程申请资源时,必须严格按资源编号的递增顺序进行,否则系统不予分配。

        优点:资源利用率和系统吞吐量与另两种方法相比有较明显的改善。

        缺点:1、为系统中各种类型的资源所分配的序号必须相对稳定,这就限制了新设备类型的增加。2、作业实际使用资源的顺序与系统规定的顺序不同而造成资源的浪费。

        避免:避免死锁与预防死锁的区别在于:预防死锁是设法至少破坏产生死锁的必要条件之一,严格地防止死锁的出现。

        避免死锁,它是在进程请求分配资源时,采用某种算法(银行家算法)来预防可能发生的死锁,从而拒绝可能引起死锁的某个资源请求。

        在避免死锁(或银行家算法)中必须谈到两种系统状态:①安全状态,指系统能按照某种顺序,为每个进程分配所需的资源,直至最大需求,使得每个进程都能顺利完成。②非安全状态:即在某个时刻系统中不存在一个安全序列,则称系统处于不安全状态或非安全状态。

        虽然并非所有不安全状态都是死锁状态,但当系统进入不安全状态后,便有可能进入死锁状态;反之只要系统处于安全状态,系统便可避免进入死锁状态。因此,避免死锁的实质是如何使系统不进入不安全状态。

JAVA并发编程基础概念知识点(二)

        检测和恢复:死锁的检测和恢复技术是指定期启动一个软件检测系统的状态。若发现有死锁存在,则采取措施恢复之。

        死锁的检测:检查死锁的办法就是由软件检查系统中由进程和资源构成的有向图是否构成一个或多个环路,若是,则存在死锁,否则不存在。其实质是确定是否存在环路等待现象,一但发现这种环路便认定死锁存在,并识别出该环路涉及的有关进程,以供系统采取适当的措施来解除死锁。

        死锁的解除:1、将陷入死锁的进程全部撤销。2、逐个作废死锁进程,直至死锁不再存在。3、从死锁进程中逐个地强迫抢占其所占资源,直至死锁不存在。
 

9、可重入锁

可重入性:就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。(简单来说:A线程在某上下文中或得了某锁,当A线程想要在次获取该锁时,不会应为锁已经被自己占用,而需要先等到锁的释放)假使A线程即获得了锁,又在等待锁的释放,就会造成死锁。

注意:synchronized和reentrantlock都是可重入锁

synchronized:

无需释放锁,synchronized会自动释放锁

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    System.out.println(“第1次获取锁,这个锁是:” + this);
                    int index = 1;
                    while (true) {
                        synchronized (this) {
                            System.out.println(“第” + (++index) + “次获取锁,这个锁是:” + this);
                        }
                        if (index == 10) {
                            break;
                        }
                    }
                }
            }
        }).start();
    }
reentrantlock:

上几次锁,就需要手动释放几次。

  public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        new Thread(() -> {
            try {
                reentrantLock.lock();
                System.out.println(“第1次获取这个锁,这个锁是:”+reentrantLock);
 
                int index = 1;
                while (true) {
                    try {
                        reentrantLock.lock();
                        System.out.println(“第”+(++index)+”次获取这个锁,这个锁是:”+reentrantLock);
 
                        if (index == 10 ) {
                            break;
                        }
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            } finally {
                reentrantLock.unlock();
            }
        }).start();
    }
测试结果:

注意:如果此时少释放一次锁会出现什么情况:

正常情况(加锁和释放锁次数相同):

 public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        new Thread(() -> {
            try {
                reentrantLock.lock();
                System.out.println(“第1次获取这个锁,这个锁是:”+reentrantLock);
 
                int index = 1;
                while (true) {
                    try {
                        reentrantLock.lock();
                        System.out.println(“第”+(++index)+”次获取这个锁,这个锁是:”+reentrantLock);
 
                        if (index == 10 ) {
                            break;
                        }
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            } finally {
                reentrantLock.unlock();
            }
        }).start();
 
        /**
         * 构建第二个线程 看是否可以获取锁
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    reentrantLock.lock();
                    for (int i = 0; i < 5; i++) {
                        System.out.println(“threadName:” + Thread.currentThread().getName());
                        try {
                            Thread.sleep(new Random().nextInt(200));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } finally {
                    reentrantLock.unlock();
                }
            }
        }).start();
    }
正常测试结果:

 异常情况(少释放一次锁):

 public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        new Thread(() -> {
            try {
                reentrantLock.lock();
                System.out.println(“第1次获取这个锁,这个锁是:”+reentrantLock);
 
                int index = 1;
                while (true) {
                    try {
                        reentrantLock.lock();
                        System.out.println(“第”+(++index)+”次获取这个锁,这个锁是:”+reentrantLock);
 
                        if (index == 10 ) {
                            break;
                        }
                    } finally {
//                        reentrantLock.unlock();  //锁少释放一次
                    }
                }
            } finally {
                reentrantLock.unlock();
            }
        }).start();
 
        /**
         * 构建第二个线程 看是否可以获取锁
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    reentrantLock.lock();
                    for (int i = 0; i < 5; i++) {
                        System.out.println(“threadName:” + Thread.currentThread().getName());
                        try {
                            Thread.sleep(new Random().nextInt(200));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } finally {
                    reentrantLock.unlock();
                }
            }
        }).start();
    }
异常结果(第二个线程一直没有获取到锁):

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/125143.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!