Статьи

Java 8 Friday Goodies: простое как пирог локальное кэширование

В Data Geekery мы любим Java. И так как мы действительно входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему. Мы пару раз писали о приятных вкусностях Java 8 , и теперь мы чувствуем, что пришло время начать новую серию блогов,…

Ява 8 Пятница

Каждую пятницу мы показываем вам пару замечательных новых функций Java 8 в виде учебника, в которых используются лямбда-выражения, методы расширения и другие замечательные вещи. Вы найдете исходный код на GitHub .

Java 8 Goodie: простое как пирог локальное кэширование

Теперь приготовьтесь к одному из самых удивительных открытий в этой серии. Мы покажем вам простой способ реализовать локальный кеш, используя старые добрые выражения ConcurrentHashMap и лямбда-выражения. Потому что в Map теперь есть новый способ атомарного вычисления нового значения в случае отсутствия ключа . Идеально подходит для кэшей. Давайте углубимся в код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public static void main(String[] args) {
    for (int i = 0; i < 10; i++)
        System.out.println(
            "f(" + i + ") = " + fibonacci(i));
}
 
static int fibonacci(int i) {
    if (i == 0)
        return i;
 
    if (i == 1)
        return 1;
 
    System.out.println("Calculating f(" + i + ")");
    return fibonacci(i - 2) + fibonacci(i - 1);
}

Да. Это наивный способ делать вещи. Даже для небольших чисел, таких как fibonacci(5) , вышеприведенный алгоритм выведет огромное количество строк, поскольку мы повторяем те же вычисления в геометрической прогрессии:

01
02
03
04
05
06
07
08
09
10
11
12
13
Calculating f(6)
Calculating f(4)
Calculating f(2)
Calculating f(3)
Calculating f(2)
Calculating f(5)
Calculating f(3)
Calculating f(2)
Calculating f(4)
Calculating f(2)
Calculating f(3)
Calculating f(2)
f(6) = 8

То, что мы хотим сделать, это создать кеш из предварительно рассчитанных чисел Фибоначчи. Самый простой способ — запоминать все значения в кэше . Вот как мы строим кеш:

1
2
static Map<Integer, Integer> cache
    = new ConcurrentHashMap<>();

Готово! Как упоминалось ранее, мы используем недавно добавленный Map.computeIfAbsent() для вычисления нового значения из source функции, только если у нас еще нет значения для данного ключа. Кэширование! И поскольку этот метод гарантированно выполняется атомарно, и поскольку мы используем ConcurrentHashMap , этот кеш даже является поточно-ориентированным, не прибегая к ручному применению synchronized любом месте. И это может быть использовано для других целей, кроме вычисления числа Фибоначчи. Но давайте сначала применим этот кеш к нашей функции fibonacci() .

01
02
03
04
05
06
07
08
09
10
11
static int fibonacci(int i) {
    if (i == 0)
        return i;
 
    if (i == 1)
        return 1;
 
    return cache.computeIfAbsent(i, (key) ->
                 fibonacci(i - 2)
               + fibonacci(i - 1));
}

Вот и все. Это не может быть проще, чем это! Хотите доказательства? Мы будем регистрировать сообщение на консоли каждый раз, когда мы на самом деле оцениваем новое значение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
static int fibonacci(int i) {
    if (i == 0)
        return i;
 
    if (i == 1)
        return 1;
 
    return cache.computeIfAbsent(i, (key) -> {
        System.out.println(
            "Slow calculation of " + key);
 
        return fibonacci(i - 2) + fibonacci(i - 1);
    });
}

Вышеуказанная программа напечатает

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
f(0) = 0
f(1) = 1
Slow calculation of 2
f(2) = 1
Slow calculation of 3
f(3) = 2
Slow calculation of 4
f(4) = 3
Slow calculation of 5
f(5) = 5
Slow calculation of 6
f(6) = 8
Slow calculation of 7
f(7) = 13
Slow calculation of 8
f(8) = 21
Slow calculation of 9
f(9) = 34

Как бы мы сделали это в Java 7?

Хороший вопрос. С большим количеством кода. Мы, вероятно, напишем что-то подобное, используя двойную проверку блокировки :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int fibonacciJava7(int i) {
    if (i == 0)
        return i;
 
    if (i == 1)
        return 1;
 
    Integer result = cache.get(i);
    if (result == null) {
        synchronized (cache) {
            result = cache.get(i);
 
            if (result == null) {
                System.out.println(
                    "Slow calculation of " + i);
 
                result = fibonacci(i - 2)
                       + fibonacci(i - 1);
                cache.put(i, result);
            }
        }
    }
 
    return result;
}

Будучи убеждена?

Обратите внимание, что ваше реальное решение, вероятно, будет использовать кэши Guava .

Вывод

Лямбды — это только одна часть Java 8. Важная часть, но давайте не будем забывать обо всех новых функциях, которые были добавлены в библиотеки и которые теперь можно использовать с лямбдами!

Это действительно захватывающе и …

Мы можем значительно улучшить наши кодовые базы, не прибегая к новым библиотекам. Все вышеперечисленное может быть запущено только с библиотеками JDK.

На следующей неделе в этой серии блогов мы рассмотрим, как Java 8 улучшит существующие и новые API параллелизма, так что следите за обновлениями!

Подробнее о Java 8

А пока взгляните на удивительную страницу ресурсов Java 8 от Eugen Paraschiv.