设为首页 加入收藏

TOP

Java 并发基础教程——线程安全性(一)
2018-04-08 08:51:33 】 浏览:377
Tags:Java 并发 基础 教程 线程 安全性

当线程安全:多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类都能表现出正确的行为,那么久称这个类是线程安全的。


在线程安全类中封装了必要的同步机制,因此客户端无需采取进一步的同步措施。


要么不执行,要么执行到底。原子性就是当某一个线程修改i的值的时候,从取出i到将新的i的值写给i之间不能有其他线程对i进行任何操作。也就是说保证某个线程对i的操作是原子性的,这样就可以避免数据脏读。 通过锁机制或者CAS(Compare And Set 需要硬件CPU的支持)操作可以保证操作的原子性。


当多个线程访问某个状态变量,并且其中有一个线程执行写入操作时,必须采用同步机制来协调这些线程对变量的访问。无状态对象一定是线程安全的。


  如果我们在无状态的对象中增加一个状态时,会出现什么情况呢?假设我们按照以下方式在servlet中增加一个"命中计数器"来管理请求数量:在servlet中增加一个long类型的域,每处理一个请求就在这个值上加1。


public class UnsafeCountingFactorizer implements Servlet {
    private long count = 0;


    public long getCount() {
            return count ;
    }


    @Override
    public void service(ServletRequest arg0, ServletResponse arg1)
                throws ServletException, IOException {
            // do something
          count++;
    }
}


不幸的是,以上代码不是线程安全的,因为count++并非是原子操作,实际上,它包含了三个独立的操作:读取count的值,将值加1,然后将计算结果写入count。如果线程A读到count为10,马上线程B读到count也为10,线程A加1写入后为11,线程B由于已经读过count值为10,执行加1写入后依然为11,这样就丢失了一次计数。


        在 count++例子中线程不安全是因为 count++并非原子操作,我们可以使用原子类,确保确保操作是原子,这样这个类就是线程安全的了。


public class CountingFactorizer implements Servlet {
    private final AtomicLong count = new AtomicLong(0);


    public long getCount() {
          return count .get() ;
  }


    @Override
    public void service(ServletRequest arg0, ServletResponse arg1)
              throws ServletException, IOException {
          // do something
          count.incrementAndGet();
  }
}


      AtomicLong是java.util.concurrent.atomic包中的原子变量类,它能够实现原子的自增操作,这样就是线程安全的了。  同样,上述情况还会出现在 单例模式的懒加载过程中,当多个线程同时访问 getInstance()函数时。


      线程在执行被synchronized修饰的代码块时,首先检查是否有其他线程持有该锁,如果有则阻塞等待,如果没有则持有该锁,并在执行完之后释放该锁。


      除了使用原子变量的方式外,我们也可以通过加锁的方式实现线程安全性。还是UnsafeCountingFactorizer,我们只要在它的service方法上增加synchronized关键字,那么它就是线程安全的了。当然在整个方法中加锁在这里是效率很低的,因为我们只需要保证count++操作的原子性,所以这里只对count++进行了加锁,代码如下:


public class UnsafeCountingFactorizer implements Servlet {
    private long count = 0;


    public long getCount() {
            return count ;
    }


    @Override
    public void service(ServletRequest arg0, ServletResponse arg1)
                throws ServletException, IOException {
            // do something
          synchronized(this){
              count++;
          }
    }
}


Synchronized代码块使得一段程序的执行具有 原子性,即每个时刻只能有一个线程持有这个代码块,多个线程执行在执行时会互不干扰。


    的内存模型没有上面这么简单,在Java Memory Model中,Memory分为两类,main memory和working memory,main memory为所有线程共享,working memory中存放的是线程所需要的变量的拷贝(线程要对main memory中的内容进行操作的话,首先需要拷贝到自己的working memory,一般为了速度,working memory一般是在cpu的cache中的)。被volatile修饰的变量在被操作的时候不会产生working memory的拷贝,而是直接操作main memory,当然volatile虽然解决了变量的可见性问题,但没有解决变量操作的原子性的问题,这个还需要sy

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Protocol Buffers数据描述语言 下一篇Java中hashCode问题详细解析

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目