• Java并发之线程间的同步协作与通信协作


    1,Monitor监视器与syncrhoized实现原理

    1.1:Monitor

          Monitor是一个同步工具,相当于操作系统中的互斥量(mutex),即值为1的信号量。

          它内置与每一个Object对象中,相当于一个许可证。拿到许可证即可以进行操作,没有拿到则需要阻塞等待。

    1.2:syncrhoized实现原理

           syncrhoized又叫做内置锁,为什么呢?因为使用syncrhoized加锁的同步代码块在字节码引擎中执行时,其实是通过锁对象的monitor的取用与释放来实现的。由上面我们直到Monitor是内置于任何一个对象中的,syncrhoized利用monitor来实现加锁解锁,故syncrhoized又叫做内置锁。

           现在我们知道为什么用syncrhoized(lock)来加锁时,锁对象可以是任意对象了:

           1:syncrhoized(lock)加锁时,用到的其实只是lock对象内置的monitor而已;

           2:一个对象的monitor是唯一的,相当于一个唯一的许可证。拿到许可证的线程才可以执行,执行完后释放对象的monitor才可以被其他线程获取。

    我们来讲解一下syncrhoized加锁的同步块的执行过程:

           现在假设有代码块:  

          syncrhoized(Object lock){

                                        同步代码...;

                           }

            它在字节码文件中被编译为:

          monitorenter;//获取monitor许可证,进入同步块

                                                 同步代码...

                          monitorexit;//离开同步块后,释放monitor许可证

    2,线程间的同步协作与通信协作

    2.1:线程的状态以及变化图

         Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态)。

      New:新建状态,当线程创建完成时为新建状态,即new Thread(...),还没有调用start方法时,线程处于新建状态。

      Runnable:就绪状态,当调用线程的的start方法后,线程进入就绪状态,等待CPU资源。处于就绪状态的线程由Java运行时系统的线程调度程序(thread scheduler)来调度。

      Running:运行状态,就绪状态的线程获取到CPU执行权以后进入运行状态,开始执行run方法。

      Blocked:阻塞状态,线程没有执行完,由于某种原因(如,I/O操作等)让出CPU执行权,自身进入阻塞状态。

      Dead:死亡状态,线程执行完成或者执行过程中出现异常,线程就会进入死亡状态。

    2.2 线程间的同步协作

             由syncrhoized同步锁、ReentrantLock(可重入锁)、ReadWriteLock(读写锁)等待同步机制,实现线程之间的同步。

    说明:

    1)syncrhoized也是可重入锁,基本用法

    syncrhoized(Object lock){
         需要同步的代码...;
     }

    2)ReentrantLock类(不是关键字)可以实现syncrhoized同样的效果并且有扩展功能

    Lock lock = new ReentranLock();
    lock.lock();   需要同步的代码; lock.unlock();

    3)ReentrantReadWriteLock类有两个锁,读相关的锁(也叫共享锁),写相关的锁(也叫排他锁)。

    ReentranReadWriteLock lock = new ReentranReadWriteLock();
    
    lock.readLock().lock();
       需要读锁同步的代码;
    lock.readLock().unlock();
    
    lock.writeLock().lock();
       需要写锁同步的代码;
    lock.writeLock().unlock();

             线程获得锁则进入就绪态,等待CPU调度进入运行态;

             线程申请被占用的锁,则进入阻塞态,让出CPU使用权。直到获得该锁后,重新进入就绪态,等待CPU调度进入运行态。

     2.3 线程间的通信协作

             在获得锁而执行的线程执行时,执行到某处时需要申请同一把锁的其他线程先执行,此时就需要让出同步锁以及CPU(进入阻塞态),让其他线程先获取同步锁以及CPU而执行。直到其他线程执行完并释放同步锁后通知它唤醒(就绪态),才接着申请同步锁以及CPU而继续执行下去(运行态)。

             这个线程之间 让出资源、挂起、唤醒 就是通过线程的通信来实现的。

    两种方式:         

    1)syncrhoized加锁的线程的wait()/notify()/notifyAll()

    2)ReentrantLock类加锁的线程的Condition类调用await()/signal()/signalAll()

    Object类中的wait()/notify()/notifyAll()方法依次与Condition类中的await()/signal()/signalAll()一一对应

    2.4  线程自身的动作

    1) 线程自身可以通过调用 sleep() 方法进入阻塞态暂时让出CPU资源(但是不释放锁休眠时间过后自动恢复就绪态等待CPU调度执行;

    2)线程自身可以通过调用 yield() 方法由运行态变为就绪态;这个过程称为“让步”,即正在运行的线程让出CPU给就绪态中的线程先执行一下,自己则回到就绪态中等待CPU再次调度自己执行;

    3)线程可以在自身执行过程中,通过其他线程对象.join() 方已经启动的其他线程先执行完 ,再继续执行自身的余下操作。可以通过这个方法来实现线程之间顺序执行。

     join()方法解释:

    为什么要使用join:

    package joinTest1;
    
    public class MyThread extends Thread {
    
        @Override
        public void run() {
            try {
                int secondValue = (int) (Math.random() * 10000);
                System.out.println(secondValue);
                Thread.sleep(secondValue);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
    }
    package joinTest1;
    
    public class Test {
    
        public static void main(String[] args) {
    
            MyThread threadTest = new MyThread();
            threadTest.start();
    
            // Thread.sleep(?)
            System.out.println("我想当threadTest对象执行完毕后我再执行");
            System.out.println("但上面代码中的sleep()中的值应该写多少呢?");
            System.out.println("答案是:根据不能确定:)");
        }
    
    }

    运行结果:

    问题就是,我们当前线程想在某个线程(其实就是当前线程的子线程)执行完成后执行,要等多长时间,其实还有一个问题是,join内部是wait实现的会释放锁,而sleep不会释放锁。

    package joinTest2;
    
    public class MyThread extends Thread {
    
        @Override
        public void run() {
            try {
                int secondValue = (int) (Math.random() * 10000);
                System.out.println(secondValue);
                Thread.sleep(secondValue);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
    }
    package joinTest2;
    
    public class Test {
    
        public static void main(String[] args) {
            try {
                MyThread threadTest = new MyThread();
                threadTest.start(); //启动子线程
                threadTest.join();  //当时间片又来到当前线程(父线程)时,运行了子线程类的join方法,把当前线程(父线程)无限期阻塞,直到子线程销毁再执行父线程余下的代码。
    
                System.out.println("我想当threadTest对象执行完毕后我再执行,我做到了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }

    运行结果

    方法join具有使线程排队运行的作用。

    《Java多线程编程核心技术》

    http://www.cnblogs.com/ygj0930/p/6561589.html

    https://www.cnblogs.com/ygj0930/p/6561667.html

  • 相关阅读:
    word批量打印工具,c#写的
    word添加页眉脚和设置各页不同的页眉页脚.
    打印机双面打印
    ORACLE OCP认证
    基于.net程序,使用cefsharp开发的打开网页工具,如何不加载图片
    在iis上运行的服务器端程序,运行一段时间后,访问都只出现一行乱码,回收进程池后又好了,求大神回复
    ArcGis API for JavaScript 开发笔记一 加载地图
    修改现有消息类让.net core项目支持Protobuf
    结合现有分布式系统的数据一致性思考
    让现有vue前端项目快速支持多语言
  • 原文地址:https://www.cnblogs.com/xdyixia/p/9387758.html
Copyright © 2020-2023  润新知