浅谈Java并发编程基础知识

进程和线程

在并行程序中进程和线程是两个基本的运行单元,在Java并发编程中,并发主要核心在于线程

1. 进程

一个进程有其专属的运行环境,一个进程通常有一套完整、私有的运行时资源;尤其是每个进程都有其专属的内存空间。
通常情况下,进程等同于运行的程序或者应用,然而很多情况下用户看到的一个应用实际上可能是多个进程协作的。为了达到进程通信的目的,主要的操作系统都实现了Inter Process Communication(IPC)资源,例如pipe和sockets,IPC不仅能支持同一个系统中的进程通信,还能支持跨系统进程通信。

2. 线程

线程通常也被叫做轻量级进程,进程线程都提供执行环境,但是创建一个线程需要的资源更少,线程在进程中,每个进程至少有一条线程,线程共享进程的资源,包括内存空间和文件资源,这种机制会使得处理更高效但是也存在很多问题。
多线程运行是Java的一个主要特性,每个应用至少包含一个线程或者更多。从应用程序角度来讲,我们从一条叫做主线程的线程开始,主线程可以创建别的其他的线程。

线程生命周期

一个线程的生命周期包含了一下几种状态

1、新建状态

该状态线程已经被创建,但未进入运行状态,我们可以通过start()方法来调用线程使其进入可执行状态。

2、可执行状态/就绪状态

在该状态下,线程在排队等待任务调度器对其进行调度执行。

3、运行状态

在该状态下,线程获得了CPU的使用权并在CPU中运行,在这种状态下我们可以通过yield()方法来使得该线程让出时间片给自己或者其他线程执行,若让出了时间片,则进入就绪队列等待调度。

4、阻塞状态

在阻塞状态下,线程不可运行,并且被异除出等待队列,没有机会进行CPU执行,在以下情况出现时线程会进入阻塞状态

  • 调用suspend()方法
  • 调用sleep()方法
  • 调用wait()方法
  • 等待IO操作

线程可以从阻塞状态重回就绪状态等待调度,如IO操作完毕后。

5、终止状态

当线程执行完毕或被终止执行后便会进入终止状态,进入终止状态后线程将无法再被调度执行,彻底丧失被调度的机会。

线程对象

每一条线程都有一个关联的Thread对象,在并发编程中Java提供了两个基本策略来使用线程对象

  • 直接控制线程的创建和管理,在需要创建异步任务时直接通过实例化Thread来创建和使用线程。
  • 或者将抽象好的任务传递给一个任务执行器 executor

1. 定义和开始一条线程

在创建一个线程实例时需要提供在线程中执行的代码,有两种方式可以实现。

提供一个Runnable对象,Runnable接口定义了一个run方法,我们将要在线程中执行的方法放到run方法内部,再将Runnable对象传递给一个Thread构造器,代码如下。

public class ThreadObject {
  public static void main(String args[]) {
    new Thread(new HelloRunnable()).start();
  }
}
// 实现Runnable接口
class HelloRunnable implements Runnable {
  @Override
  public void run() {
    System.out.println("Say hello to world!!!");
  }
}

继承Thread,Thread类自身实现了Runnable接口,但是其run方法什么都没做,由我们自己根据需求去扩展。

public class ThreadObject {
  public static void main(String args[]) {
    new HelloThread().start();
  }
}
// 继承Thread,扩展run方法
class HelloThread extends Thread {
  public void run() {
    System.out.println("Say hello to world!!!");
  }
}

两种实现方式的选取根据业务场景和Java中单继承,多实现的特性来综合考量。

2. 利用Sleep暂停线程执行

sleep()方法会使线程进入阻塞队列,进入阻塞队列后,线程会将CPU时间片让给其他线程执行,sleep()有两个重载方法sleep(long millis)和sleep(long millis, int nanos)当到了指定的休眠时间后,线程将会重新进入就绪队列等待调度管理器进行调度

public static void main(String args[]) throws InterruptedException {
  for (int i = 0; i < 4; i++) {
    System.out.println("print number "+ i);
    // 将主线程暂停4秒后执行,4秒后重新获得调度执行的机会
    Thread.sleep(4*1000);
  }
}

3. 中断

当一个线程被中断后就代表这个线程再无法继续执行,将放弃所有在执行的任务,程序可以自己决定如何处理中断请求,但通常都是终止执行。

在Java中与中断相关的有Thread.interrupt()、Thread.isInterrupted()、Thread.interrupted()三个方法

Thread.interrupt()为设置中断的方法,该方法会将线程状态设置为确认中断状态,但程序并不会立马中断执行只是设置了状态,而Thread.isInterrupted()、Thread.interrupted()这两个方法可以用于捕获中断状态,区别在于Thread.interrupted()会重置中断状态。

4. Join

join方法允许一条线程等待另一条线程执行完毕,例如t是一条线程,若调用t.join()方法,则当前线程会等待t线程执行完毕后再执行。

线程同步 Synchronization

各线通信方式

  • 共享对象的访问权限 如. A和B线程都有访问和操作某一个对象的权限
  • 共享 对象的引用对象的访问权限 如. A和B线程都能访问C对象,C对象引用了D对象,则A和B能通过C访问D对象

这种通信方式使得线程通讯变得高效,但是也带来一些列的问题例如线程干扰和内存一致性错误。那些用于防止出现这些类型的错误出现的工具或者策略就叫做同步。

1. 线程干扰 Thread Interference

线程干扰是指多条线同时操作某一个引用对象时造成计算结果与预期不符,彼此之间相互干扰。如例

public class ThreadInterference{
  public static void main(String args[]) throws InterruptedException {
    Counter ctr = new Counter();
    // 累加线程
    Thread incrementThread = new Thread(()->{
      for(int i = 0; i<10000;i++) {
        ctr.increment();
      }
    });
    // 累减线程
    Thread decrementThread = new Thread(()->{
      for(int i = 0; i<10000;i++) {
        ctr.decrement();
      }
    });
    incrementThread.start();
    decrementThread.start();
    incrementThread.join();
    decrementThread.join();
    System.out.println(String.format("最终执行结果:%d", ctr.get()));
  }
}
class Counter{
  private int count = 0;
  // 自增
  public void increment() {
    ++this.count;
  }
  // 自减
  public void decrement() {
    --this.count;
  }
  public int get() {
    return this.count;
  }
}

理论上来讲,如果按照正常的思路理解,一个累加10000次一个累减10000次最终结果应该是0 ,但实际结果却是每次运行结果都不一致,产生这个结果的原因便是线程之间相互干扰。

我们可以把自增和自减操作拆解为以下几个步骤

  • 获取count变量当前值
  • 自增/自减 获取到的值
  • 将结果保存回count变量

当多个线程同时对count进行操作时,便可能产生如下这一种状态

  • 线程A : 获取count
  • 线程B : 获取count
  • 线程A: 自增,结果 为 1
  • 线程B: 自减,结果为 -1
  • 线程A: 将结果1 保存到count; 当前count = 1
  • 线程B: 将结果-1 保存到count; 当前count = -1

当线程以上面所示的顺序执行时,线程B就会覆盖掉线程A的结果,当然这只是其中一种情况。

2. 内存一致性错误 Memory Consistency Errors

当不同的线程对应相同数据具有不一致的视图时,会发生内存一致性错误,详细信息参见 JVM内存模型

3. 同步方法

Java提供了两种同步的惯用方法:同步方法 synchronized methods 、同步语句 synchronized statements 。要使方法变成同步方法只需要在方法声明时加入synchronized关键字,如

class Counter{
  private int count = 0;
  // 自增
  public synchronized void increment() {
    ++this.count;
  }
  // 自减
  public synchronized void decrement() {
    --this.count;
  }
  public synchronized int get() {
    return this.count;
  }
}

声明为同步方法之后将会使得对象产生如下所述的影响

  • 首先,不可以在同一对象上多次调用同步方法来交错执行,同步声明使得同一个时间只能有一条线程调用该对象的同步方法,当一条线程已经在调用同步方法时,其他线程会被阻塞block,无法调用该对象的所有同步方法。
  • 其次,当同步方法调用结束时,会自动与同一对象的任何后续调用方法建立一个happens-before关联,这保证对对象状态的更改对所有线程可见。

4. 内部锁和同步

同步是围绕对象内部实体构建的,API规范通常将此类实体称之为监视器,内部锁有两个至关重要的作用

  • 强制对对象状态的独占访问
  • 建立至关重要的happens-before关系

每个对象都有与其关联的固有锁,通常,需要对对象的字段进行独占且一致的访问前需要获取对象的内部锁,然后再使用完成时释放内部锁,线程在获取后释放前拥有该对象的内部锁。只要线程拥有了内部锁其他任何线程都无法获取相同的锁,其他线程在尝试获取锁时将被阻塞。在线程释放内部锁时,该操作将会在该对象的任何后续操作间建立happens-before关系。

4.1 同步方法中的锁

当线程调用同步方法时,线程会自动获得该方法所属对象得内部锁,并且在方法返回时自动释放,即使返回是由未捕获异常导致。静态同步方法的锁不同于实例方法的锁,静态方法是围绕该类进行控制而非该类的某一个实例。

4.2 同步语句

另外一个提供同步的方法是同步代语句,与同步方法不同的是,同步语句必须指定一个对象来提供内部锁。

public class IntrinsicLock {
  private List<String> nameList = new LinkedList<String>();
  private String lastName;
  private int nameCount;

  public void addName(String name) {
    // 当多条线程对同一个实例对象的addName()方法操作时将会是同步的,提供锁的对象为该实例对象本身
    synchronized(this) {
      lastName = name;
      nameCount++;
    }
    nameList.add(name);
  }
}

同步语句对细粒度同步提高并发性也很有用,比如我们需要对同一个对象的不同属性进行同步修改我们可以通过如下代码来提高细粒度同步控制下的并发。

public class IntrinsicLock {
  // 1. 该属性需要基于同步的修改
  private String lastName;
  // 1. 该属性也需要基于同步的修改
  private int count;

  // 该对象用于对lastName提供内部锁
  private Object nameLock = new Object();
  // 该对象用于对nameCount提供内部锁
  private Object countLock = new Object();

  public void addName(String name) {
    synchronized(nameLock) {
      lastName = name;
    }
  }
  public void increment() {
    synchronized(countLock) {
      count++;
    }
  }
}

这样,对lastName的操作不会阻塞count属性的自增操作,因为他们分别使用了不同的对象来提供锁。若像上一个例子中使用this来提供锁的话,则在调用addName()方法时increment()也被阻塞,反之亦然,这样将会增加不必要的阻塞。

4.3 可重入同步

线程无法获取另外一个线程已经拥有的锁,但是线程可以多次获取它已经拥有的锁,允许线程多次获取同一锁可以实现可重入的同步,即同步方法或者同步代码块中又调用了由同一个对象提供锁的其他同步方法时,该锁可以多次被获取

public class IntrinsicLock {
  private int count;
  public void decrement(String name) {
    synchronized(this) {
      count--;
      // 调用其他由同一个对象提供锁的同步方法时,锁可以重复获取
      // 但只能由当前有用锁的线程重复获取
      increment();
    }
  }
  public void increment() {
    synchronized(this) {
      count++;
    }
  }
}

4.4 原子访问

在编程中,原子操作指的是指所有操作一行性完成,原子操作不可能执行一半,要么全都执行,要么都不执行。在原子操作完成之前,其修改都是不可见的。在Java中以下操作是原子性的。

  • 读写大部分原始变量(除了long和double)
  • 读写所有使用volatile声明的变量

原子操作的特性使得我们不必担心线程干扰带来的同步问题,但是原子操作依然会发生内存一致性错误。需要使用volatile声明变量以有效防止内存一致性错误,因为写volatile标记的变量时会与读取该变量的后续操作建立happens-before关系,所以改变使用volatile标记变量时对其他线程总是可见的。也就是它不仅可以观测最新的改变,也能观测到尚未使其改变的操作。

5. 死锁

死锁是描述一种两条或多条线程相互等待(阻塞)的场景,如下例子所示

public class DeadLock {
  static class Friend {
    String name;
    public Friend(String name) {
      super();
      this.name = name;
    }
    public String getName() {
      return name;
    }
    public synchronized void call(Friend friend) {
      System.out.println(String.format("%s被%s呼叫...", name,friend.getName()));
      friend.callBack(this);
    }
    public synchronized void callBack(Friend friend) {
      System.out.println(String.format("%s呼叫%s...", friend.getName(),name));
    }
  }

  public static void main(String args[]) {
    final Friend zhangSan = new Friend("张三");
    final Friend liSi = new Friend("李四");
    new Thread(new Runnable() {
      public void run() { zhangSan.call(liSi); }
    }).start();
    new Thread(new Runnable() {
      public void run() { liSi.call(zhangSan); }
    }).start();
  }
}

如果张三呼叫李四的同时,李四呼叫张三,那么他们会永远等待对方,线程永远阻塞。

6. 饥饿和活锁

相对死锁而言,饥饿和活锁问题要少得多,但是也应注意。

6.1 饥饿

饥饿是一种描述线程无法定期访问共享资源,程序无法取得正常执行的一种场景,比如一个同步方法执行时间很长,但是多条线程争抢且频繁的执行,那么将会有大量线程无法在正常的情况下获得使用权,造成大量阻塞和积压,我们使用饥饿来描述这种并发场景。

6.2 活锁

活锁是一种描述线程在执行同步方法的过程中依赖其他外部资源,而该部分获取缓慢而无保障造成无法进一步执行的的场景,相对于死锁,活锁是有机会进一步执行的,只是执行过程缓慢,造成部分资源被 正在等待其他资源的线程占用。

7. 保护块/守护块

通常,线程会根据其需要来协调其操作。最常用的协调方式便是通过守护块的方式,用一个代码块来轮询一个一条件,只有到该条件满足时,程序才继续执行。要实现这个功能通常有几个要遵循的步骤,先给出一个并不是那么好的例子请勿在生产代码使用以下示例

public void guardedJoy() {
  // 这是一个简单的轮询守护块,但是极其消耗资源
  // 请勿在生产环境中使用此类代码,这是一个不好的示例
  while(!joy) {}
  System.out.println("Joy has been achieved!");
}

这个例子中,只有当别的线程讲joy变量设置为true时,程序才会继续往下执行,在理论上该方法确实能实现守护的功能,利用简单的轮询,一直等待条件满足后,才继续往下执行,这是这种轮询方式是极其消耗资源的,因为轮询会一直占用CPU资源。别的线程便无法获得CPU进行处理。

一个更为有效的守护方式是调用Object.wait方法来暂停线程执行,暂停后线程会被阻塞,让出CPU时间片给其他线程使用,直到其他线程发出一个某些条件已经满足的通知事件后,该线程会被唤醒重新执行,即使其他线程完成的条件并非它等的哪一个条件。更改上面的代码

public synchronized void guardedJoy() {
  // 正确的例子,该守护快每次被其他线程唤醒之后只会轮询一次,
  while(!joy) {
    try{
      wait();
    }catch(Exception e) {}
  }
  System.out.println("Joy has been achieved!");
}

为什么这个版本的守护块需要同步的?假设d是一个我们调用wait方法的对象,当线程调用d.wait()方法时线程必须拥有对象d的内部锁,否则将会抛出异常。在一个同步方法内部调用wait()方法是一个简单的获取对象内部锁的方式。当wait()方法被调用后,当前线程会释放内部锁并暂停执行,在将来的某一刻,其他线程将会获得d的内部锁,并调用d.notifyAll()方法,来唤醒由对象d.wait()方法暂停执行的线程。

public synchronized notifyJoy() {
  joy = true;
  // 唤醒所有被wait()方法暂停的线程
  notifyAll();
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java并发编程示例(九):本地线程变量的使用

    共享数据是并发程序最关键的特性之一.对于无论是继承Thread类的对象,还是实现Runnable接口的对象,这都是一个非常周重要的方面. 如果创建了一个实现Runnable接口的类的对象,并使用该对象启动了一系列的线程,则所有这些线程共享相同的属性.换句话说,如果一个线程修改了一个属性,则其余所有线程都会受此改变的影响. 有时,我们更希望能在线程内单独使用,而不和其他使用同一对象启动的线程共享.Java并发接口提供了一种很清晰的机制来满足此需求,该机制称为本地线程变量.该机制的性能也非常可观.

  • Java并发编程示例(七):守护线程的创建和运行

    Java有一种特殊线程,守护线程,这种线程优先级特别低,只有在同一程序中的其他线程不执行时才会执行. 由于守护线程拥有这些特性,所以,一般用为为程序中的普通线程(也称为用户线程)提供服务.它们一般会有一个无限循环,或用于等待请求服务,或用于执行任务等.它们不可以做任何重要的工作,因为我们不确定他们什么时才能分配到CPU运行时间,而且当没有其他线程执行时,它们就会自动终止.这类线程的一个典型应用就是Java的垃圾回收. 在本节示例中,我们将创建两个线程,一个是普通线程,向队列中写入事件:另外一个是

  • Java并发编程示例(六):等待线程执行终止

    在某些场景下,我们必须等待线程执行完成才能进行下一步工作.例如,某些程序在开始执行之前,需要先初始化一些资源.这时,我们可以启动一个线程专门来做初始化任务,等到线程任务完成后,再去执行其他部分. 为此,Thread类为我们提供了join()方法.当我们使用线程对象调用此方法时,正在掉调用的线程对象将被推迟到被调用对象执行完成后再开始执行. 在本节,示例程序演示等待初始化方法完成后,再去执行其他任务. 知其然 按照下面所示步骤,完成示例程序. 1.创建一个名为DataSourcesLoader的类

  • Java并发控制机制详解

    在一般性开发中,笔者经常看到很多同学在对待java并发开发模型中只会使用一些基础的方法.比如Volatile,synchronized.像Lock和atomic这类高级并发包很多人并不经常使用.我想大部分原因都是来之于对原理的不属性导致的.在繁忙的开发工作中,又有谁会很准确的把握和使用正确的并发模型呢? 所以最近基于这个思想,本人打算把并发控制机制这部分整理成一篇文章.既是对自己掌握知识的一个回忆,也是希望这篇讲到的类容能帮助到大部分开发者. 并行程序开发不可避免地要涉及多线程.多任务的协作和数

  • Java并发编程示例(五):线程休眠与恢复

    有时,我们需要在指定的时间点中断正在执行的线程.比如,每分钟检查一次传感器状态的线程,其余时间,线程不需要做任何事情.在此期间,线程不需要使用计算机的任何资源.过了这段时间之后,并且当Java虚拟机调度了该线程,则该线程继续执行.为此,你可以使用Thread类的sleeep()方法.该方法以休眠的方式来推迟线程的执行,而且整数类型的参数则指明休眠的毫秒数.当调用sleep()方法,休眠时间结束后,Java虚拟机分配给线程CPU运行时间,线程就会继续执行. 另一种是用sleep()方法的方式是通过

  • 深入解析Java并发程序中线程的同步与线程锁的使用

    synchronized关键字 synchronized,我们谓之锁,主要用来给方法.代码块加锁.当某个方法或者代码块使用synchronized时,那么在同一时刻至多仅有有一个线程在执行该段代码.当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段.但是,其余线程是可以访问该对象中的非加锁代码块的. synchronized主要包括两种方法:synchronized 方法.synchronized 块. synchron

  • Java并发编程总结——慎用CAS详解

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能. 2.对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized.以java.util.concurrent.atomic包中AtomicInteger类为例,其getAn

  • Java并发编程示例(十):线程组

    对线程分组是Java并发API提供的一个有趣功能.我们可以将一组线程看成一个独立单元,并且可以随意操纵线程组中的线程对象.比如,可以控制一组线程来运行同样的任务,无需关心有多少线程还在运行,还可以使用一次中断调用中断所有线程的执行. Java提供了ThreadGroup类来控制一个线程组.一个线程组可以通过线程对象来创建,也可以由其他线程组来创建,生成一个树形结构的线程. 根据<Effective Java>的说明,不再建议使用ThreadGroup.建议使用Executor. --D瓜哥特此

  • Java并发编程示例(一):线程的创建和执行

    开门见山 在IT圈里,每当我们谈论并发时,必定会说起在一台计算机上同时运行的一系列线程.如果这台电脑上有多个处理器或者是一个多核处理器,那么这时是实实在在的"同时运行":但是,如果计算机只有一个单核处理器,那么这时的"同时运行"只是表象而已. 所有的现代操作系统全部支持任务的并发执行.你可以边听音乐,边上网看新闻,还不耽误首发电子邮件.我们可以说,这种并发是 进程级并发 .在进程内部,我也可以看到有许许多多的并发任务.我们把运行在一个进程里面的并发任务称 线程. 和

  • 浅谈Java并发编程基础知识

    进程和线程 在并行程序中进程和线程是两个基本的运行单元,在Java并发编程中,并发主要核心在于线程 1. 进程 一个进程有其专属的运行环境,一个进程通常有一套完整.私有的运行时资源:尤其是每个进程都有其专属的内存空间. 通常情况下,进程等同于运行的程序或者应用,然而很多情况下用户看到的一个应用实际上可能是多个进程协作的.为了达到进程通信的目的,主要的操作系统都实现了Inter Process Communication(IPC)资源,例如pipe和sockets,IPC不仅能支持同一个系统中的进

  • 浅谈Java并发编程之Lock锁和条件变量

    简单使用Lock锁 Java 5中引入了新的锁机制--java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接口有3个实现它的类:ReentrantLock.ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁.读锁和写锁.lock必须被显式地创建.锁定和释放,为了可以使用更多的功能,一般用ReentrantLock为其实例

  • java网络编程基础知识介绍

    网络基础知识 1.OSI分层模型和TCP/IP分层模型的对应关系 这里对于7层模型不展开来讲,只选择跟这次系列主题相关的知识点介绍. 2.七层模型与协议的对应关系 网络层 ------------ IP(网络之间的互联协议) 传输层 ------------ TCP(传输控制协议).UDP(用户数据报协议) 应用层 ------------ Telnet(Internet远程登录服务的标准协议和主要方式).FTP(文本传输协议).HTTP(超文本传送协议) 3.IP地址和端口号 1.ip地址用于

  • 浅谈java多线程编程

    一.多线程的优缺点 多线程的优点: 1)资源利用率更好 2)程序设计在某些情况下更简单 3)程序响应更快 多线程的代价: 1)设计更复杂 虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂.在多线程访问共享数据的时候,这部分代码需要特别的注意.线程之间的交互往往非常复杂.不正确的线程同步产生的错误非常难以被发现,并且重现以修复. 2)上下文切换的开销 当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据

  • 浅谈Java自定义注解相关知识

    一.自定义注解格式 分析 Java 中自带的 @Override 注解 , 源码如下 : @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } 注解分为两部分 : ① 元注解 ; ② public @interface 注解名称 ; 二.注解本质分析 按照 public @interface 注解名称 格式 , 写出一个注解 , 编译该注解代码生成 Annotat

  • 浅谈Swoole并发编程的魅力

    场景介绍 假设我们要做一个石头剪刀布的Web游戏,3个玩家同时提交竞猜后显示胜者.在传统串行化Web编程中,我们一般思路是这样: 设置form表单,用户提交竞猜后保存到MySQL/Redis存储 添加一个查看结果按钮,如果未全部完成,显示正在等待其他人提交.当3个人全部提交时,查询存储,并显示最终结果 并发编程 这个场景就可以使用Swoole实现并发编程,无需依赖MySQL/Redis存储,在内存中可以完成竞猜. 当有用户提交竞猜时,hold住请求,不返回结果,用户进入等待状态.当前请求和连接保

  • 详解Java并发编程基础之volatile

    目录 一.volatile的定义和实现原理 1.Java并发模型采用的方式 2.volatile的定义 3.volatile的底层实现原理 二.volatile的内存语义 1.volatile的特性 2.volatile写-读建立的happens-before关系 3.volatile的写/读内存语义 三.volatile内存语义的实现 1.volatile重排序规则 2.内存屏障 3.内存屏障示例 四.volatile与死循环问题 五.volatile对于复合操作非原子性问题 一.volati

  • 浅谈Java并发中ReentrantLock锁应该怎么用

    目录 1.重入锁 说明 2.中断响应 说明 3.锁申请等待限时 tryLock(long, TimeUnit) tryLock() 4.公平锁 说明 源码(JDK8) 重入锁可以替代关键字 synchronized . 在 JDK5.0 的早期版本中,重入锁的性能远远优于关键字 synchronized , 但从 JDK6.0 开始, JDK 在关键字 synchronized 上做了大量的优化,使得两者的性能差距并不大. 重入锁使用 ReentrantLock 实现 1.重入锁 package

  • 浅谈Java 并发的底层实现

    并发编程的目的是让程序运行更快,但是使用并发并不定会使得程序运行更快,只有当程序的并发数量达到一定的量级的时候才能体现并发编程的优势.所以谈并发编程在高并发量的时候才有意义.虽然目前还没有开发过高并发量的程序,但是学习并发是为了更好理解一些分布式架构.那么当程序的并发量不高,比如是单线程的程序,单线程的执行效率反而比多线程更高.这又是为什么呢?熟悉操作系统的应该知道,CPU是通过给每个线程分配时间片的方式实现多线程的.这样,当CPU从一个任务切换到另一个任务的时候,会保存上一个任务的状态,当执行

  • 浅谈Java多线程编程中Boolean常量的同步问题

    在JAVA中通过synchronized语句可以实现多线程并发.使用同步代码块,JVM保证同一时间只有一个线程可以拥有某一对象的锁.锁机制实现了多个线程安全地对临界资源进行访问.   同步代码写法如下:   代码1: Object obj = new Object(); ... synchronized(obj) { //TODO: 访问临界资源 } JAVA的多线程总是充满陷阱,如果我们用Boolean作为被同步的对象,可能会出现以下两种情况:   一. 以为对一个对象加锁,实际同步的是不同对

随机推荐