Ответ 1
Это пример руководства " блокировать укрупнение" и, возможно, это было сделано для повышения производительности.
Рассмотрим эти два фрагмента:
StringBuffer b = new StringBuffer();
for(int i = 0 ; i < 100; i++){
b.append(i);
}
против
StringBuffer b = new StringBuffer();
synchronized(b){
for(int i = 0 ; i < 100; i++){
b.append(i);
}
}
В первом случае StringBuffer должен получить и освободить блокировку 100 раз (потому что append
- это синхронизированный метод), тогда как во втором случае блокировка приобретается и освобождается только один раз. Это может дать вам повышение производительности и, вероятно, поэтому автор сделал это. В некоторых случаях компилятор может выполнить эту блокировку укрупнения для вас (но не вокруг циклов конструкций, потому что вы можете в конечном итоге удерживать блокировку в течение длительных периодов времени время).
Кстати, компилятор может обнаружить, что объект не "ускользает" от метода, и поэтому удаляет процесс эквайринга и освобождения блокировок объекта (блокировка elision), так как ни один другой поток не может получить доступ к объекту в любом случае. Эта работа была выполнена в JDK7.
Update:
Я провел два быстрых теста:
1) БЕЗ ПРЕДОХРАНИТЕЛЯ:
В этом тесте я несколько раз не запускал методы для "разминки" JVM. Это означает, что компилятор Java Hotspot Server Compiler не получил возможности оптимизировать код, например. путем исключения блокировок для экранирующих объектов.
JDK 1.4.2_19 1.5.0_21 1.6.0_21 1.7.0_06
WITH-SYNC (ms) 3172 1108 3822 2786
WITHOUT-SYNC (ms) 3660 801 509 763
STRINGBUILDER (ms) N/A 450 434 475
С JDK 1.4 код с внешним синхронизированным блоком выполняется быстрее. Однако с JDK 5 и выше выигрывает код без внешней синхронизации.
2) С WARM-UP:
В этом тесте методы выполнялись несколько раз до того, как были рассчитаны тайминги. Это было сделано для того, чтобы JVM мог оптимизировать код, выполнив анализ escape-кода.
JDK 1.4.2_19 1.5.0_21 1.6.0_21 1.7.0_06
WITH-SYNC (ms) 3190 614 565 587
WITHOUT-SYNC (ms) 3593 779 563 610
STRINGBUILDER (ms) N/A 450 434 475
Еще раз, с JDK 1.4, код с внешним синхронизированным блоком выполняется быстрее. Однако с JDK 5 и выше оба метода работают одинаково хорошо.
Вот мой тестовый класс (не стесняйтесь улучшаться):
public class StringBufferTest {
public static void unsync() {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 9999999; i++) {
buffer.append(i);
buffer.delete(0, buffer.length() - 1);
}
}
public static void sync() {
StringBuffer buffer = new StringBuffer();
synchronized (buffer) {
for (int i = 0; i < 9999999; i++) {
buffer.append(i);
buffer.delete(0, buffer.length() - 1);
}
}
}
public static void sb() {
StringBuilder buffer = new StringBuilder();
synchronized (buffer) {
for (int i = 0; i < 9999999; i++) {
buffer.append(i);
buffer.delete(0, buffer.length() - 1);
}
}
}
public static void main(String[] args) {
System.out.println(System.getProperty("java.version"));
// warm up
for(int i = 0 ; i < 10 ; i++){
unsync();
sync();
sb();
}
long start = System.currentTimeMillis();
unsync();
long end = System.currentTimeMillis();
long duration = end - start;
System.out.println("Unsync: " + duration);
start = System.currentTimeMillis();
sync();
end = System.currentTimeMillis();
duration = end - start;
System.out.println("sync: " + duration);
start = System.currentTimeMillis();
sb();
end = System.currentTimeMillis();
duration = end - start;
System.out.println("sb: " + duration);
}
}