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);
        }
    });
}        
时间: 2024-08-10 17:13:48

Java并发编程的一些基本概念的相关文章

Java并发编程(三)概念介绍

在构建稳健的并发程序时,必须正确使用线程和锁.但是这终归只是一些机制.要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问. 对象的状态是指存储在状态变量(例如实例或静态域)中的数据. 对象的状态可能包括其他依赖对象的域.比如某个HashMap的状态不仅是HashMap对象本身,还存储在许多Map.Entry对象中. "共享"意味着变量可以由多个线程同时访问,而"可变"则意味着变量的值在其生命周

【java并发编程实战】-----线程基本概念

学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习Java并发编程,共同进步,互相指导. 在学习Java并发之前我们需要先理解一些基本的概念:共享.可变.线程安全性.线程同步.原子性.可见性.有序性. 共享和可变 要编写线程安全的代码,其核心在于对共享的和可变的状态进行访问. "共享"就意味着变量可以被多个线程同时访问.我们知道系统中的资

java并发编程基础概念

1.什么是进程和线程 1.1 进程是程序运行资源分配的最小单位 进程是操作系统进行资源分配的最小单位,其中资源包括:CPU.内存空间.磁盘IO等,同一进程中的多个线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 进程是程序在计算机上的一次执行活动.当你运行一个程序,你就启动了一个进程.显然,程序是死的.静态的,进程是活的.动态的.进程可以分为系统进程和用户进程.凡是用于完成操作

6、Java并发编程:volatile关键字解析

Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatil

Java并发编程:进程和线程

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

读《Java并发编程的艺术》(一)

离开博客园很久了,自从找到工作,到现在基本没有再写过博客了.在大学培养起来的写博客的习惯在慢慢的消失殆尽,感觉汗颜.所以现在要开始重新培养起这个习惯,定期写博客不仅是对自己学习知识的一种沉淀,更是在督促自己要不断的学习,不断的进步. 最近在进一步学习Java并发编程,不言而喻,这部分内容是很重要的.现在就以<并发编程的艺术>一书为主导线,开始新一轮的学习. 进程和线程 进程是一个应用程序在处理机上的一次执行过程,线程是进程的最小基本单位(个人理解).一个进程可以包含多个线程. 上下文切换 我们

Java并发编程学习路线

一年前由于工作需要从微软技术栈入坑Java,并陆陆续续做了一个Java后台项目,目前在搞Scala+Java混合的后台开发,一直觉得并发编程是所有后台工程师的基本功,所以也学习了小一年Java的并发工具,对整体的并发理解乃至分布式都有一定的提高,所以想和大家分享一下. 我的学习路线 首先说说学习路线,我一开始是直接上手JCIP(Java Concurrency in Practice),发现不是很好懂,把握不了那本书的主线,所以思索着从国内的作者开始先,所以便读了下方腾飞的<Java并发编程的艺

5、Java并发编程:Lock

Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 也许有朋友会问,既然都可以通过synchronized来实现同步访问了,那么为什么还需要提供Lock?这个问题将在下面进行阐述.本文先从synchronized的缺陷讲起,然后再讲述java.util.concurrent.locks包

Java并发编程(一)

Java并发编程(一) 之前看<Thinking In Java>时,并发讲解的挺多的,自己算是初步了解了并发.但是其讲解的不深入,自己感觉其讲解的不够好.后来自己想再学一学并发,买了<Java并发编程实战>,看了一下讲的好基础.好多的理论,而且自我感觉讲的逻辑性不强.最后,买了本<Java并发编程的艺术>看,这本书挺好的,逻辑性非常强. 1. 概述 本篇文章主要内容来自<Java并发编程的艺术>,其讲解的比较深入,自己也有许多不懂的地方,然后自己主要把它讲