zoukankan      html  css  js  c++  java
  • Java并发编程(一) 基础知识

    1. 原子性

      操作的原子性是即不能被分割的操作,和数据库中事务的原子性概念一致,即要么不执行,要么全部完成。有很多看起来具有原子性操作的实际上并不具有“原子性”,例如:

      “读取-修改-写入”操作:形式上像 i++ 一样简单的语句实际上并不是一个具有“原子性”的操作,它实际上包含三步:

      读取原来的值

      修改值

      写入新的值

     并且这三步之间是可以被分割执行的。

    2. 竞态条件

      在多线程环境下,遇到不恰当的执行时序会出现不正确结果的情况称为竞态条件,竞态条件并不一定会导致错误,还需要某种不恰当的执行时序。常见的会导致竞态条件的语句结构有:

      “读取-修改-写入”结构:即更新变量成为一个新值,并且该新值依赖于旧值。

      “先检查后执行”结构:即执行操作之前要先验证一个条件,例如:if-else语句。

    3.加锁

      对于需要保证原子性的操作,我们可以通过加锁来实现多个线程之间的互斥访问。Java提供了内置锁机制来方便地给某个操作加锁,Java中每个对象都有一个内置的锁,我们可以通过synchronized关键字来使用内置锁机制,通常包括两种形式:synchronized块和synchronized方法。

    //synchronized块
    synchronized(obj)   //使用对象obj的内置锁
    {
      // .......
    }
    
    //synchronized方法
    public  synchronized method() 
    {
      // .......
    }
    //就相当于
    public method()
    { 
      synchronized(this)
      {
        // ....... 
      }
    }

       Java的内置锁是可重入的,即持有锁的单位是线程,而不是调用。因此,对于某个已经持有锁X的线程,再次请求锁X会成功,这种情况常见于递归调用:

    public void method()
    {
       synchronized(obj)
      {
         //.....
         method();
      }
    }

       一种常见的错误是认为只有在对共享变量进行写入时才需要加锁,事实上只要访问共享变量,无论是读操作还是写操作,都需要加锁。

    4. 可见性

      通常情况下,我们需要实现只有一个线程能执行一些互斥的操作,我们还可能需要在执行完这个操作后使其他线程知道发生的改变,这就需要同步机制来保证操作执行之后的内存可见性。

      Java中的内置锁和显示锁都支持同步机制,对于显示锁可以使用常用的notify方法和wait方法等。Java中还可以通过volatile关键字来实现轻量级的同步,通常用来实现变量的内存可见性。

      volatile修饰的变量上的操作不会被重排序,访问volatile变量时不会使用缓存中的值,总是会得到最新写入的值。需要注意的是和加锁不一样的是,volatile关键字只能确保内存可见性,但不能确保原子性。使用volatile变量需要满足以下条件:

      (1)对该变量的访问可以是非互斥的

      (2)更新该变量的值不依赖于旧值(如:volatile不能保证自增操作的原子性),如果依赖于旧值则需要确保只有一个线程更新该变量的值

      volatile变量通常用作状态变量,例如,常用作判断某个循环是否应该退出的状态标记:

    volatile  boolean  running = true;
    
    public void run()
    {
      while(running)
      {
        //.....
      }
    }
    
    public void stop()
    {
       running = false;
    }

    5.发布与逸出

      发布一个对象是指使对象能够在当前作用域之外的代码中使用,例如:将该对象的引用传递给其他方法,或者在public方法中返回对象的引用。这样,这个对象就可以在其他代码中被修改。当某个对象在不该被发布的时候发布了,就称为逸出。

      常见的逸出包括:

     (1)在public方法中返回私有成员(可变)的引用

    class  Test
    {
       private Date  birthday;
     
       public void getBirthday()
      {
         return birthday;
      }
    }

      Date类对象不是不可变对象,因此返回Date类对象的引用后,就可以在外部代码中修改它的值,说明这个类的封装性不够好。

    (2)在对象构造完成之前就传递this引用到其他地方

    class  A
    {
       private int num;   
    
       public A()
       {
            new B(this);                 //发布this引用到B类的对象中
            num++;                       //本意是希望num变成1,结果实际上是2
       }
    
       public void addNum()
      {
         num++;
      }
       
    }
    
    class  B
    {
       public B(A a)
      {
          a.addNum();
      }
    }

      上面的错误比较容易看出来,一种更加隐晦的错误情况是在构造函数中启动一个线程:

    class A
    {
        public A()
       {
           new Task(this).start();
    //..... } }
    class Task implements Runnable { A a; Task(A a) { this.a = a; } public void run() { //操作a } }

      上述情况可能会导致A类对象尚未创建完成时便在另一个线程中被修改。

    6. 线程封闭

      仅在单线程内访问数据,就不需要同步以及互斥,这种技术称为线程封闭。Swing中大量使用了线程封闭技术,在Swing技术中,对界面组件的操作都要在EDT(event dispath thread)线程中执行。如果需要在其他线程中更新Swing界面组件,Swing提供了invokeLater方法和invokeAndWait方法来提交更新Swing界面组件的请求。因此,使用Swing编写的程序的main函数形式应该如下:

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable(){
            public void run()
            {
                JFrame mainFrame = new JFrame();
                mainFrame.setVisible(true);
            }
        });
    }        

    参考资料 《Java并发编程实战》

  • 相关阅读:
    git创建远程分支
    npm 设置淘宝镜像的两个方法
    webpack + ts 项目的初始化
    react组件间事件触发的两种方式
    PhpStorm 使用 xdebug 进行调试(三)
    PhpStorm 使用 Apache 运行 php 代码(二)
    搭建 PHP 开发环境(Apache + PHP)(一)
    Centos7 安装 MongoDB
    Vue 中使用 vuex(十三)
    Vue 中使用 vue-router(十二)
  • 原文地址:https://www.cnblogs.com/jqctop1/p/4771894.html
Copyright © 2011-2022 走看看