关于java:并发编程中是如何降低锁粒度的怎么做到性能优化

6次阅读

共计 2762 个字符,预计需要花费 7 分钟才能阅读完成。

在高负载多线程利用中性能是十分重要的。为了达到更好的性能,开发者必须意识到并发的重要性。当咱们须要应用并发时,经常有一个资源必须被两个或多个线程共享。

在这种状况下,就存在一个竞争条件,也就是其中一个线程能够失去锁(锁与特定资源绑定),其余想要失去锁的线程会被阻塞。这个同步机制的实现是有代价的,为了向你提供一个好用的同步模型,JVM 和操作系统都要耗费资源。有三个最重要的因素使并发的实现会耗费大量资源,它们是:

  • 上下文切换
  • 内存同步
  • 阻塞

为了写出针对同步的优化代码,你必须意识到这三个因素以及如何缩小它们。在写这样的代码时你须要留神很多货色。在本文中,我会向你介绍一种通过升高锁粒度的技术来缩小这些因素。

让咱们从一个根本准则开始:不要长时间持有不必要的锁。

在取得锁之前做完所有须要做的事,只把锁用在须要同步的资源上,用完之后立刻开释它。咱们来看一个简略的例子:

public class HelloSync {private Map dictionary = new HashMap();
    public synchronized void borringDeveloper(String key, String value) {long startTime = (new java.util.Date()).getTime();
        value = value + "_"+startTime;
        dictionary.put(key, value);
        System.out.println("I did this in"+
     ((new java.util.Date()).getTime() - startTime)+"miliseconds");
    }
}

在这个例子中,咱们违反了根本准则,因为咱们创立了两个 Date 对象,调用了 System.out.println(),还做了很屡次 String 连贯操作,但惟一须要做同步的操作是“dictionary.put(key, value);”。让咱们来批改代码,把同步办法变成只蕴含这句的同步块,失去上面更优化的代码:

public class HelloSync {private Map dictionary = new HashMap();
    public void borringDeveloper(String key, String value) {long startTime = (new java.util.Date()).getTime();
        value = value + "_"+startTime;
        synchronized (dictionary) {dictionary.put(key, value);
        }
        System.out.println("I did this in"+
 ((new java.util.Date()).getTime() - startTime)+"miliseconds");
    }
}

下面的代码能够进一步优化,但这里只想传播出这种想法。如果你对如何进一步优化感兴趣,请参考 java.util.concurrent.ConcurrentHashMap.

那么,咱们怎么升高锁粒度呢?简略来说,就是通过尽可能少的申请锁。根本的想法是,别离用不同的锁来爱护同一个类中多个独立的状态变量,而不是对整个类域只应用一个锁。咱们来看上面这个我在很多利用中见到过的简略例子:

public class Grocery {private final ArrayList fruits = new ArrayList();
    private final ArrayList vegetables = new ArrayList();
    public synchronized void addFruit(int index, String fruit) {fruits.add(index, fruit);
    }
    public synchronized void removeFruit(int index) {fruits.remove(index);
    }
    public synchronized void addVegetable(int index, String vegetable) {vegetables.add(index, vegetable);
    }
    public synchronized void removeVegetable(int index) {vegetables.remove(index);
    }
}

杂货店主能够对他的杂货铺中的蔬菜和水果进行增加 / 删除操作。上面对杂货铺的实现,通过根本的 Grocery 锁来爱护 fruits 和 vegetables,因为同步是在办法域实现的。事实上,咱们能够不应用这个大范畴的锁,而是针对每个资源(fruits 和 vegetables)别离应用一个锁。来看一下改良后的代码:

public class Grocery {private final ArrayList fruits = new ArrayList();
    private final ArrayList vegetables = new ArrayList();
    public void addFruit(int index, String fruit) {synchronized(fruits) fruits.add(index, fruit);
    }
    public void removeFruit(int index) {synchronized(fruits) {fruits.remove(index);}
    }
    public void addVegetable(int index, String vegetable) {synchronized(vegetables) vegetables.add(index, vegetable);
    }
    public void removeVegetable(int index) {synchronized(vegetables) vegetables.remove(index);
    }
}

在应用了两个锁后(把锁拆散),咱们会发现比起之前用一个整体锁,锁阻塞的状况更少了。当咱们把这个技术用在有中度锁争抢的锁上时,优化晋升会更显著。如果把该办法利用到轻微锁争抢的锁上,改良尽管比拟小,但还是有成果的。然而如果把它用在有重度锁争抢的锁上时,你必须意识到后果并非总是更好。

请有选择性的应用这个技术。如果你狐疑一个锁是重度争抢锁请按上面的办法来确认是否应用下面的技术:

  • 确认你的产品会有多少争抢度,将这个争抢度乘以三倍或五倍(甚至 10 倍,如果你想筹备的十拿九稳)
  • 基于这个争抢度做适当的测试
  • 比拟两种计划的测试后果,而后挑选出最合适的.

用于改良同步性能的技术还有很多,但对所有的技术来说最根本的准则只有一个:不要长时间持有不必要的锁。

这条根本准则能够如我之前向你们解释的那样了解成“尽可能少的申请锁”,也能够有其余解释(实现办法),我将在之后的文章中进一步介绍。

正文完
 0