论坛首页 编程语言技术论坛

高手问答:Java多线程编程实战指南(设计模式篇)--获奖名单公布

浏览 15513 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2015-11-28  
wulunbi 写道
viscent 写道
string2020 写道
一直看不懂volatile这个关键字是干啥的,楼主能否深入讲解一下。


  • 保证赋值操作的原子性。

我们知道对Java中的64位数据类型(long和double)进行赋值的时候,JVM是不保证原子性的。例如:

private long count=0;

void someUpdate(){
   count=1;
}

上述代码中,一个线程调用 someUpdate更新count值的时候,另外一个线程读取到的该变量的值可能是0、也可能是1,甚至可能是其它数值。如果对上述变量采用voalitle进行修饰,那么上述代码对long型变量的赋值就具有了原子性。因此,其它线程读取到的该变量的值只可能是0或者1。
  • 保证共享可变变量的可见性。

简单来说,就是一个线程对一个共享变量的更新对于另外一个线程而言不一定是可见的。比如,

private boolean isDone=false;
如果有个线程将上面的变量修改为true,那么其它线程可能读取到的值一直是false。如果将上述变量采用volatile修饰,那么一个线程将其值修改后,之后有其它线程来读取该变量的值,后面这个线程总是可以读取到跟新后的变量值。
  • 禁止重排序。

比如下面的代码:
private int nonVoaltileVar=1;
private boolean isVarSet=false;

private void update(){
  nonVoaltileVar=2;
isVarSet=true;
}

上述代码执行时,由于重排序的结果,一个线程执行update方法后(假设此时再也没有其它线程会去更新上述两个变量的值),其它线程读取到isVarSet的值为true的情况下,它所读取到nonVoaltileVar的值可能仍然是1。这是由于update方法中的两个语句的执行顺序可能被对调(重排序)。而如果我们用voalitle去修饰isVarSet,那么voaltile会禁止其修饰的变量的赋值操作前的操作被重排序到其之后。这样,就保证了其它线程读取到isVarSet的值为true的情况下,nonVoaltileVar的值总是为2。

本书第3章的实战案例代码中有使用volatile关键字,可以参考下。如果要进一步或者更加详细的解释,那要不小的篇幅。深入的理解voaltile关键字涉及到CPU访问内存的机制以及JMM。




viscent老师,您解释的volatile关键字的中的第二点:
  • 保证共享可变变量的可见性。我有一个疑问点。
  • 假如说一个线程A改变了一个共享的volatile变量X,接着另外两个线程B,C都去读取这个变量X,那么B,C线程读取的变量X的值是A线程刚刚改变的最新值。
    接着,如果B线程比C线程先改变volatile变量X,
    问题1:那么对于线程C来说,线程C知道volatile变量X的改变吗?
    问题2:线程C会更新读取的volatile的X的旧值(线程A改变的)吗?

    希望你能解答一下,感激呀!

    线程C会读取到线程B更新之后的变量值,而不是一个旧值。理由同线程B和线程C能够读取到线程A更新之后的变量值。
    你可以去理解下Write Buffer/CPU Cache和Memory Barrier这几个硬相关的概念,对volatile关键字就会有跟进一步的认识了。
    0 请登录后投票
       发表时间:2015-11-29  
    viscent 写道
    wulunbi 写道
    viscent 写道
    string2020 写道
    一直看不懂volatile这个关键字是干啥的,楼主能否深入讲解一下。


    • 保证赋值操作的原子性。

    我们知道对Java中的64位数据类型(long和double)进行赋值的时候,JVM是不保证原子性的。例如:

    private long count=0;

    void someUpdate(){
       count=1;
    }

    上述代码中,一个线程调用 someUpdate更新count值的时候,另外一个线程读取到的该变量的值可能是0、也可能是1,甚至可能是其它数值。如果对上述变量采用voalitle进行修饰,那么上述代码对long型变量的赋值就具有了原子性。因此,其它线程读取到的该变量的值只可能是0或者1。
    • 保证共享可变变量的可见性。

    简单来说,就是一个线程对一个共享变量的更新对于另外一个线程而言不一定是可见的。比如,

    private boolean isDone=false;
    如果有个线程将上面的变量修改为true,那么其它线程可能读取到的值一直是false。如果将上述变量采用volatile修饰,那么一个线程将其值修改后,之后有其它线程来读取该变量的值,后面这个线程总是可以读取到跟新后的变量值。
    • 禁止重排序。

    比如下面的代码:
    private int nonVoaltileVar=1;
    private boolean isVarSet=false;

    private void update(){
      nonVoaltileVar=2;
    isVarSet=true;
    }

    上述代码执行时,由于重排序的结果,一个线程执行update方法后(假设此时再也没有其它线程会去更新上述两个变量的值),其它线程读取到isVarSet的值为true的情况下,它所读取到nonVoaltileVar的值可能仍然是1。这是由于update方法中的两个语句的执行顺序可能被对调(重排序)。而如果我们用voalitle去修饰isVarSet,那么voaltile会禁止其修饰的变量的赋值操作前的操作被重排序到其之后。这样,就保证了其它线程读取到isVarSet的值为true的情况下,nonVoaltileVar的值总是为2。

    本书第3章的实战案例代码中有使用volatile关键字,可以参考下。如果要进一步或者更加详细的解释,那要不小的篇幅。深入的理解voaltile关键字涉及到CPU访问内存的机制以及JMM。




    viscent老师,您解释的volatile关键字的中的第二点:
  • 保证共享可变变量的可见性。我有一个疑问点。
  • 假如说一个线程A改变了一个共享的volatile变量X,接着另外两个线程B,C都去读取这个变量X,那么B,C线程读取的变量X的值是A线程刚刚改变的最新值。
    接着,如果B线程比C线程先改变volatile变量X,
    问题1:那么对于线程C来说,线程C知道volatile变量X的改变吗?
    问题2:线程C会更新读取的volatile的X的旧值(线程A改变的)吗?

    希望你能解答一下,感激呀!

    线程C会读取到线程B更新之后的变量值,而不是一个旧值。理由同线程B和线程C能够读取到线程A更新之后的变量值。
    你可以去理解下Write Buffer/CPU Cache和Memory Barrier这几个硬相关的概念,对volatile关键字就会有跟进一步的认识了。



    谢谢viscent老师解答的volatile的问题!
    另外我觉得一个比较难理解的问题是happen-before,这个概念一直没有弄懂,您能给一些大概的提示,怎么才能比较好的理解?
    0 请登录后投票
       发表时间:2015-11-29  
    viscent 写道
    seandeng888 写道
    viscent老师:
        您好!
        关注您很久,看您出书了,很是期待啊。

    感谢你的支持!你是在InfoQ上看到我的文章么?

    是的。
    0 请登录后投票
       发表时间:2015-11-29  
    wulunbi 写道

    另外我觉得一个比较难理解的问题是happen-before,这个概念一直没有弄懂,您能给一些大概的提示,怎么才能比较好的理解?

    我的博客上已添加回复:

    Happens-before关系是JMM中的一个比较容易误解的概念。我的理解是它其实是一个形式化(或者模型化)的概念,所以理解起来有些困难。但是,这种比较抽象的概念我们可以对其具体化,通过一些具体的例子来更好的理解它。

    Happens-before的提出是为了解决多线程共享变量的可见性问题。我们知道,这个问题编译器要关心、我们作为应用开发人员也要关心。理解这点很重要,因为如果你从编译器的角度出发去理解Happens-before的概念,就会涉及一些Memory Barrier等与硬件相关的概念。所以,我建议先从应用开发人员的角度出发去理解这个概念,这样会比较容易。

    下面,我们对几个具体的Happens-before规则从应用开发人员的角度进行“解读”,通过这个解读相信大家都能明白Happens-before是个什么东西,至少明白它对我们(应用开发人员)意味着什么。

    线程启动规则。对一个线程进行的Thread.start调用happens‐before被启动的线程中的每个动作(Action)。
    Thread start rule. A call to Thread.start on a thread happens‐before every action in the started thread.

    所谓动作(Action),包括读变量、写变量、启动线程、等待线程停止(join)和锁的获取与释放。

    上面的描述乍看起来很抽象,也显得像废话——因为线程只有在启动以后,相应线程的run方法中的代码才会被执行。所以,线程肯定是启动在先,运行在后。这大家都知道啊!不过,这里happens‐before要说明并不是我们刚才将的时间上先后关系。它要描述的是某种可见性的保证。以上面的规则为例,这个规则意味着父线程在启动一个子线程之前对任何一个变量的变更对于这个子线程而言都是可见的(这才是我们关心的话题!)。例如:

    public class HappensBefore {
    static int a;
    static long b;

    public static void main(String[] args) throws InterruptedException {
    Thread childThread = new Thread() {
    @Override
    public void run() {

    if (a == 1) {
    System.out.println(b);

    b = 900L;
    a = 3;
    }
    }
    };

    a = 1;
    b = 10000L;
    a = 2;
    b = 1L;

    childThread.join();

    System.out.println("a=" + a + ",b=" + b);
    }

    }

    上述代码的输出为:

    10000
    a=3,b=900

    这是因为,根据上面的对“线程启动规则”的解读,子线程childThread始终是可以看到其父线程(main线程)在启动其前对变量的写操作的结果(即a==1,b == 10000)。因此,childThread的run方法运行的时候看到a的值为1以及b的值为10000是有保证的。

    再看另外一个具体的Happens-before的规则:

    线程终止规则。一个线程中的任何一个动作都 happens‐before检测该线程终止的线程中的任何一个动作。这包括检测线程调用被检测线程的Thread.join或者Thread.isAlive。
    Thread  termination  rule.  Any  action  in  a  thread  happens‐before  any  other  thread  detects  that  thread  has
    terminated, either by successfully return from Thread.join or by Thread.isAlive returning false.

    同样,这个规则的理解关键还在于可见性方面它对我们(应用开发人员)意味着什么。这条规则说明,当一个线程终止的时候,该线程所做的所有变量更新动作的结果对于等待其停止的线程而言都是可见的(当然,要等Thread.join/Thread.isAlive调用返回)。

    还是以上面的代码为例,根据上面对“线程终止规则”的解读,当子线程终止的时候,调用其join方法的父线程(main线程)看到该线程更新过的变量值,即变量a的值为3,变量b的值为900,是有保证的。

    此时,我们对Happens-before有了一定的认识。这时,可以考虑从编译器的角度(假设我们是编译器开发人员),去理解Happens-before这个概念了。

    我们知道Thread.start这个方法是一个synchronized修饰的方法。上述“线程启动规则”的实现就是通过编译器对synchronized关键字的实现而实现的。编译器会在synchronized块进入和退出的时候分别插入恰当的Memory Barrier(指令)。这些指令的作用是保证一个线程对变量的更新得以刷新到主内存(而不是寄存器、写缓冲器等“工作内存”)中,并防止一些指令重排序。

    接着,我们可以循着上述方法再去解读其它Happens-before规则,使得我们对Happens-before的理解更加深刻。
    0 请登录后投票
       发表时间:2015-11-30  
    viscent老师,您解释的volatile关键字的中的第二点:
  • 保证共享可变变量的可见性。我有一个疑问点。
  • 假如说一个线程A改变了一个共享的volatile变量X,接着另外两个线程B,C都去读取这个变量X,那么B,C线程读取的变量X的值是A线程刚刚改变的最新值。
    接着,如果B线程比C线程先改变volatile变量X,
    问题1:那么对于线程C来说,线程C知道volatile变量X的改变吗?
    问题2:线程C会更新读取的volatile的X的旧值(线程A改变的)吗?

    希望你能解答一下,感激呀!

    线程C会读取到线程B更新之后的变量值,而不是一个旧值。理由同线程B和线程C能够读取到线程A更新之后的变量值。
    你可以去理解下Write Buffer/CPU Cache和Memory Barrier这几个硬相关的概念,对volatile关键字就会有跟进一步的认识了。

    线程C可是已经读了变量X的值的呀,为什么还要去读一次呢,难道是cpu通知线程C再去读一次吗?
    0 请登录后投票
       发表时间:2015-12-01  
    我想问下: 书本里描述的promise模式和JS中异步编程模式promise有什么区别? 使用该模式的目的是一样的么?
    0 请登录后投票
       发表时间:2015-12-01  
    wangmuming1122 写道
    什么是ThreadLocal类,怎么使用它? 一般在什么样的场景下可以使用ThreadLocal类?
    多线程情况下,怎么结合当前资源(硬件资源,如cpu,内存,IO等,缓层服务、软件负载等)进行有效控制,一般如何考虑和规划这方面的设计?


    第1个问题我的博客上回复了,第2个问题能否说下具体是控制什么?

    ThreadLocal类是个什么东西的确不容易解释。要深入理解ThreadLocal类,还是得从为什么有这个类说起。

    打个比方说。两个小孩玩一台遥控小汽车玩具,一个时刻只能有一个小孩操控遥控器,另外一个小孩只能等待,弄不好两个小孩还会为抢遥控器的控制权而打架!因此,共享是好的,但是有时也会产生一些问题。于是,我们容易想到一个解决由共享导致的麻烦,那就是不共享——给两个小孩给咱买一台同一型号的遥控小汽车,让它们各自玩各自的!

    回到多线程编程领域,多线程编程中共享变量(数据)往往导致要加锁,而锁又会导致等待以及上下文切换、死锁等开销和问题。因此,有时候不共享是最好的。这就是引入ThreadLocal的原因。

    多数情况下,我们访问一个变量值是通过使用相应的变量名进行的。我们可以把ThreadLocal类的一个实例看做变量名,通过这个变量名我们可以获得一个变量值,这个变量值同时还与具体的线程相关联。也就是说,特定线程与特定这样的变量名的组合决定了一个特定的变量值。也就是说,假设Java中有这样一个关键字 thread_specific,它可以用来修饰某个变量。这样的变量一旦被多个线程访问,各个线程所得到的变量值总是属于该线程所特有的那一份,彼此之间互不干扰。这个假设的关键字所起到的作用正是ThreadLocal类所要实现的效果。

    private thread_specific SimpleDateFormat threadSpecificSdf=new SimpleDateFormat("MMddHHmmss");

    形象地说ThreadLocal类可以这样理解:每个线程都持有一个其特有(私有)的一个储物柜。一个储物柜可以有多个储物箱,每个储物箱中存放的东西就是变量值。每个线程只能访问自己的储物柜而不能访问别的线程的拥有储物柜。并且,每个储物箱都有一把钥匙(Key),一把钥匙只能开一个储物箱。一把钥匙就是一个ThreadLocal实例。因此,我们就可以看到下面的这种决定关系:

    {线程对象(储物柜),ThreadLocal实例(储物箱钥匙)}→变量值(储物箱中存放的东西)

    例如,
    {thread1,threadLocalA}→String1

    {thread1,threadLocalB}→String2

    {thread2,threadLocalC}→String3

    {thread2,threadLocalD}→String4


    《Java多线程编程实战指南(设计模式篇)》第10章讲解的设计模式的实现使用了ThreadLocal类。这一章的“模式评价与实现考量”一节总结了ThreadLocal类的4种典型应用场景。书中有详细的结束和示例代码,这里我简单列举下。
    场景一:需要使用非线程安全对象,但是又不希望引入锁。     

    这个典型的例子就是在多线程环境中在不加锁的情况下保证对SimpleDateFormat类使用的线程安全。如下代码所示:

    public class SimpleDateFormatExample {
    // 注意这里!SimpleDateFormat是非线程安全,这意味着直接在多个线程间共享它是有问题的。
    private static ThreadLocal<SimpleDateFormat> tlSdf = new ThreadLocal<SimpleDateFormat>() {
    protected SimpleDateFormat initialValue() {
    return new SimpleDateFormat("MMddHHmmss");
    };
    };

    public void someOper(Date date) {
    String ts = tlSdf.get().format(date);
    System.out.println(ts);
    }

    }
    场景二:需要使用线程安全对象,但是希望避免其使用的锁的开销和相关问题。     

    比如,随机数生成器类Random是个线程安全的对象,这是因为它内部使用锁。虽然我们可以在多个线程间共享Random实例而不会导致线程安全问题,但是这涉及锁的开销。如果想避免这种开销,那么一个好的方法是每个线程只使用一个Random实例来生成随机数。在JDK7中引入的类java.util.concurrent.ThreadLocalRandom体现的正是这种思想。《Java多线程编程实战指南(设计模式篇)》第10章所举的实战案例就是这种应用场景,大家可以参考下。

    场景三:隐式参数传递。     

    一个ThreadLocal类的实例可以被同一个线程内的不同方法(可以跨类)使用。具体实现通常借助单例(Singleton)模式。场景四:特定于线程的单例(Singleton)模式。     

    传统的单例模式实际上是保证对于某个类,一个JVM下的一个ClassLoader下最多只有一个实例。而借助ThreadLocal我们可以实现对于某个类,每个线程可以拥有该类最多一个实例。

    ThreadLocal类使用时需要特别注意以下两点:

    1、ThreadLocal类的实例通常设置为某个类的静态变量。 

    即通常的使用格式是:
      private static ThreadLocal<XXX> tlVar=new ThreadLocal<XXX>() {
    protected XXX initialValue() {
    return new XXX();
    };
    };


    这是因为:一个ThreadLocal实例就对应一个线程特有的变量值,如果把ThreadLocal作为某个类的实例变量,由于一个类可以有多个实例,那么就会有多个ThreadLocal实例被创建。即便是对于一个线程而言,这多个ThreadLocal实例就对应了多个该线程特有的变量值。而这通常不是我们所需要。如果我们需要为同一线程创建不同的该线程特有的变量值,那应该创建不同名字的ThreadLocal实例。例如:
        private static ThreadLocal<XXX> tlVarA=new ThreadLocal<XXX>() {
    protected XXX initialValue() {
    return new XXX();
    };
    };

    private static ThreadLocal<YYY> tlVarB=new ThreadLocal<YYY>() {
    protected YYY initialValue() {
    return new YYY();
    };
    };
    2、在线程池环境下(如Web应用服务器下),使用ThreadLocal可能导致内存泄漏 

    这种内存泄漏的原因分析可以从Class(也是一个对象)及负责加载其的ClassLoader之间的关系、JDK对ThreadLocal的具体实现以及Web应用服务器加载Web应用程序的原理入手。分析起来需要花费不是篇幅,《Java多线程编程实战指南(设计模式篇)》10章有详细的分析和配图。
    在此基础上,我们可以给出相应的解决方案。详情参考《Java多线程编程实战指南(设计模式篇)》10章。

     

    0 请登录后投票
       发表时间:2015-12-01  
    wulunbi 写道
    viscent老师,您解释的volatile关键字的中的第二点:
  • 保证共享可变变量的可见性。我有一个疑问点。
  • 假如说一个线程A改变了一个共享的volatile变量X,接着另外两个线程B,C都去读取这个变量X,那么B,C线程读取的变量X的值是A线程刚刚改变的最新值。
    接着,如果B线程比C线程先改变volatile变量X,
    问题1:那么对于线程C来说,线程C知道volatile变量X的改变吗?
    问题2:线程C会更新读取的volatile的X的旧值(线程A改变的)吗?

    希望你能解答一下,感激呀!

    线程C会读取到线程B更新之后的变量值,而不是一个旧值。理由同线程B和线程C能够读取到线程A更新之后的变量值。
    你可以去理解下Write Buffer/CPU Cache和Memory Barrier这几个硬相关的概念,对volatile关键字就会有跟进一步的认识了。

    线程C可是已经读了变量X的值的呀,为什么还要去读一次呢,难道是cpu通知线程C再去读一次吗?

    上次回复是我理解错的你的意思了。我想你的问题其实是,举个例子来说,
    int x=0;
    C线程读取x的值:它读到的值是0;
    接着,
    B线程执行: x=1;
    此时,如果C线程没有再次去读取变量x的值,那么它读取的值当然还是0。若此时,C线程再此读取变量x的值,那么
    它能够读取到x的值为1(假设中途没有其它线程再去修改变量x的值)。
    简单来说,voaltile修饰的变量被一个线程修改之后(注意这个时间先后顺序),其它线程来读取这个变量的值总是(注意这个词)可以读取到其它线程修改之后的值,而不是一个已经过时的值。相反,没有使用voaltile修饰或者变量的赋值与读取也没有使用锁等同步机制的,就没有这种保证。

    0 请登录后投票
       发表时间:2015-12-01  
    viscent 写道
    wulunbi 写道
    viscent老师,您解释的volatile关键字的中的第二点:
  • 保证共享可变变量的可见性。我有一个疑问点。
  • 假如说一个线程A改变了一个共享的volatile变量X,接着另外两个线程B,C都去读取这个变量X,那么B,C线程读取的变量X的值是A线程刚刚改变的最新值。
    接着,如果B线程比C线程先改变volatile变量X,
    问题1:那么对于线程C来说,线程C知道volatile变量X的改变吗?
    问题2:线程C会更新读取的volatile的X的旧值(线程A改变的)吗?

    希望你能解答一下,感激呀!

    线程C会读取到线程B更新之后的变量值,而不是一个旧值。理由同线程B和线程C能够读取到线程A更新之后的变量值。
    你可以去理解下Write Buffer/CPU Cache和Memory Barrier这几个硬相关的概念,对volatile关键字就会有跟进一步的认识了。

    线程C可是已经读了变量X的值的呀,为什么还要去读一次呢,难道是cpu通知线程C再去读一次吗?

    上次回复是我理解错的你的意思了。我想你的问题其实是,举个例子来说,
    int x=0;
    C线程读取x的值:它读到的值是0;
    接着,
    B线程执行: x=1;
    此时,如果C线程没有再次去读取变量x的值,那么它读取的值当然还是0。若此时,C线程再此读取变量x的值,那么
    它能够读取到x的值为1(假设中途没有其它线程再去修改变量x的值)。
    简单来说,voaltile修饰的变量被一个线程修改之后(注意这个时间先后顺序),其它线程来读取这个变量的值总是(注意这个词)可以读取到其它线程修改之后的值,而不是一个已经过时的值。相反,没有使用voaltile修饰或者变量的赋值与读取也没有使用锁等同步机制的,就没有这种保证。




    那就是说,在我举得的这个例子中:一个线程A改变了一个共享的volatile变量X,接着另外两个线程B,C都去读取这个变量X,那么B,C线程读取的变量X的值是A线程刚刚改变的最新值。

    B和C线程读取的肯定是A线程改变的值,但是接着B线程改变了X的值,因为C线程已经读过了A线程改变的X的值,所以他不会去读B线程的值。
    0 请登录后投票
       发表时间:2015-12-01  
    sxlkk 写道
    我有个问题想咨询一下,简单说多线程的使用是有条件的,如果上下文切换开销非常大的时候,多线程就不如单线程,我主要想了解这个多线程的上下文切换都包括什么操作(我知道的皮毛可能是缓存切换),还有上下文切换的耗时一般什么地方耗时最长容易导致上下文切换慢,是不是解决了上下文切换快慢问题,多线程就可以放心使用呢,感谢博主


    本书第1章打了个比方:比如我们用手机与他人通话的时候,聊着聊着的时候由于第3个人拨打了你的电话。那么,这个时候你可能会做的一个动作就是先记下刚才的通话聊到哪里的(即进度),接着和对方说“我先接个电话,你别挂断“,然后和第3个人说”稍后给您回电“。这时,继续和第1个人通话的时候,我们就要回想刚才我们聊到哪里了,否则你必然要问对方这个问题。

    上面的比方中,从和一个人通话到和另外一个人通话的这种来回切换就是上下文切换,通话中聊到哪里的这个信息就是”上下文”。

    从Java语言的角度来说,上下文切换就是Java线程的状态在Runnable和非Runnable(Blocked、WAITED或者TIMED_WAITED)之间来回变动时CPU所做的一个动作。线程的状态从Runnable到非Runnable的变化时CPU所在的动作叫切出(Switch out),线程的状态从非Runnable到Runnable的变化时CPU所在的动作叫切入(Switch in)。

    上下文切换的开销包括直接开销和间接开销。直接开销就是保存或者恢复被切出或者切入的线程的上下文信息的CPU开销。上下文信息包括寄存器、程序计数器、栈指针等信息。

    间接的开销包括线程重新调度所需的开销、以及被切出的线程可能会被切入到另外一个CPU上运行而导致CPU Cache Miss导致的开销(即Cache Miss导致读内存这个慢的操作的时间开销)。

    根据上述描述,我们可以发现凡是可以导致线程状态从Runnable变为非Runnable的因素,都会导致上下文切换。这些常见因素包括:
    I/O操作、
    锁(非争用的锁由于Java做过优化,并不会导致上下文切换)、
    GC(GC的时候所谓的stop-the-world会导致所有应用线程被暂停,即切出)。

    如何减少上下文切换的开销问题实际上就是如何减少上下文切换的问题。常见的方法包括:
    减少锁持有的时间:锁持有的时间越短,它被争用的几率越低,因此它导致上下文切换的几率也越低。这与Java1.6 开始对synchronized做的一些优化有关。因为Java会对持有时间较短的锁做这样一种优化(可以理解为spin-wait),使得这种锁的获得不会导致上下文切换。
    避免在临界区中执行I/O操作:因为I/O操作会引起上下文切换,而I/O操作通常又是比较慢的操作,若将这种操作放在临界区中会使得锁的持有时间变长,从而使得锁的获得也会上下文切换。于是就产生了雪上加霜的效果。
    尽量减少GC的频率,有其是Full GC的频率:GC过程中的compact阶段由于要移动对象的内存区域导致它必须停止应用线程,从而导致上下文切换。

    在Linux平台下,我们可以使用perf命令来监控发生在指定进程内的上下文切换。
    对于Java8,我们可以使用JMC这个JDK工具来查看上下文切换的频率。
    0 请登录后投票
    论坛首页 编程语言技术版

    跳转论坛:
    Global site tag (gtag.js) - Google Analytics