ThreadLocal не прост в использовании? Ты бесполезен! | Заметки об отладке Java

Java задняя часть
ThreadLocal не прост в использовании? Ты бесполезен! | Заметки об отладке Java

Эта статья участвует в "Месяце тем Java - Заметки по отладке Java", подробности см.Ссылка на мероприятие

В Java, если вы хотите спросить, какой класс является самым простым в использовании, но наименее простым в использовании? Я думаю, слово «ThreadLocal» однажды придет вам на ум. ​

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

Сценарий использования 1: локальные переменные

Давайте возьмем время многопоточного формата в качестве примера, чтобы продемонстрироватьThreadLocalЗначение и эффект , что часто делается, когда мы форматируем время в нескольких потоках.

① Форматирование 2 потоков

Когда есть 2 потока для форматирования времени, мы можем написать:

import java.text.SimpleDateFormat;
import java.util.Date;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        // 创建并启动线程1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 得到时间对象
                Date date = new Date(1 * 1000);
                // 执行时间格式化
                formatAndPrint(date);
            }
        });
        t1.start();
        // 创建并启动线程2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 得到时间对象
                Date date = new Date(2 * 1000);
                // 执行时间格式化
                formatAndPrint(date);
            }
        });
        t2.start();
    }

    /**
     * 格式化并打印结果
     * @param date 时间对象
     */
    private static void formatAndPrint(Date date) {
        // 格式化时间对象
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
        // 执行格式化
        String result = simpleDateFormat.format(date);
        // 打印最终结果
        System.out.println("时间:" + result);
    }
}

Результат выполнения вышеуказанной программы:image.pngПриведенный выше код не создает много потоков, поэтому мы можем создать частный объект для каждого потока.SimpleDateFormatдля форматирования времени. ​

② Форматирование 10 потоков

Когда количество потоков увеличивается с 2 до 10, мы можем использоватьforЦикл для создания форматирования времени выполнения нескольких потоков, конкретный код реализации выглядит следующим образом:

import java.text.SimpleDateFormat;
import java.util.Date;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            // 创建线程
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    // 得到时间对象
                    Date date = new Date(finalI * 1000);
                    // 执行时间格式化
                    formatAndPrint(date);
                }
            });
            // 启动线程
            thread.start();
        }
    }
    /**
     * 格式化并打印时间
     * @param date 时间对象
     */
    private static void formatAndPrint(Date date) {
        // 格式化时间对象
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
        // 执行格式化
        String result = simpleDateFormat.format(date);
        // 打印最终结果
        System.out.println("时间:" + result);
    }
}

Результат выполнения вышеуказанной программы:image.pngИз приведенных выше результатов видно, что хотя количество потоков, созданных в это время иSimpleDateFormatКоличество не маленькое, но программа все равно может нормально работать. ​

③ Форматирование 1000 потоков

Однако когда мы меняем количество потоков с 10 на 1000, мы не можем просто использоватьforПроблема решается путем создания 1000 потоков в цикле, потому что такое частое создание и уничтожение потоков вызовет много системных накладных расходов и чрезмерную конкуренцию потоков.CPUпроблемы с ресурсами. ​

Итак, немного подумав,Мы решили использовать пул потоков для выполнения этих 1000 задач, потому что пул потоков может повторно использовать ресурсы потоков без необходимости часто создавать и уничтожать потоки, а также путем управления количеством потоков в пуле потоков, чтобы избежать чрезмерного количества потоков.**CPU**Проблемы с производительностью, вызванные чрезмерной конкуренцией за ресурсы и частым переключением потоков, и мы можемSimpleDateFormatПерейдите к глобальной переменной, чтобы не создавать новую для каждого выполнения.SimpleDateFormatпроблема, поэтому мы написали этот код:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class App {
    // 时间格式化对象
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) throws InterruptedException {
        // 创建线程池执行任务
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            // 执行任务
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    // 得到时间对象
                    Date date = new Date(finalI * 1000);
                    // 执行时间格式化
                    formatAndPrint(date);
                }
            });
        }
        // 线程池执行完任务之后关闭
        threadPool.shutdown();
    }

    /**
     * 格式化并打印时间
     * @param date 时间对象
     */
    private static void formatAndPrint(Date date) {
        // 执行格式化
        String result = simpleDateFormat.format(date);
        // 打印最终结果
        System.out.println("时间:" + result);
    }
}

Результат выполнения вышеуказанной программы:image.pngКогда мы запускаем программу с большой радостью, мы обнаруживаем, что произошел несчастный случай, и при написании кода таким образом будут проблемы с безопасностью потоков. Из вышеприведенных результатов видно, что результат печати программы имеет повторяющееся содержимое, и правильная ситуация должна состоять в том, что нет повторяющегося времени. ​

P.S. Так называемыйПотокобезопасность относится к ситуации, в которой результат выполнения программы не соответствует ожидаемому результату при выполнении нескольких потоков..

а) Анализ вопросов безопасности потоков

Чтобы найти проблему, мы пытаемся посмотреть наSimpleDateFormatсерединаformatИсходный код метода устранения проблемы,formatИсходный код выглядит следующим образом:

private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
    // 注意此行代码
    calendar.setTime(date);

    boolean useDateFormatSymbols = useDateFormatSymbols();

    for (int i = 0; i < compiledPattern.length; ) {
        int tag = compiledPattern[i] >>> 8;
        int count = compiledPattern[i++] & 0xff;
        if (count == 255) {
            count = compiledPattern[i++] << 16;
            count |= compiledPattern[i++];
        }

        switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;

            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;

            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
        }
    }
    return toAppendTo;
}

Как видно из приведенного выше исходного кода, при выполненииSimpleDateFormat.formatметод, будем использоватьcalendar.setTimeМетод преобразует время ввода, поэтому давайте представим такой сценарий:

  1. Поток 1 выполняетсяcalendar.setTime(date)метод преобразования времени, введенного пользователем, во время, необходимое для последующего форматирования;
  2. Поток 1 приостанавливает выполнение, поток 2 получаетCPUВременной интервал начинает выполняться;
  3. Поток 2 выполняетсяcalendar.setTime(date)метод, время было изменено;
  4. Поток 2 приостанавливает выполнение, поток 1 получаетCPUКвант времени продолжает выполняться, поскольку поток 1 и поток 2 используют один и тот же объект, а время было изменено потоком 2, поэтому при продолжении выполнения потока 1 возникнут проблемы с безопасностью потока.

В обычных условиях выполнение программы выглядит следующим образом:image.png

Поток выполнения, не являющийся потокобезопасным, выглядит следующим образом:image.png

б) Решить проблему безопасности потоков: блокировка

Когда возникает проблема безопасности потоков, первое решение, о котором мы думаем, это блокировка, конкретный код реализации выглядит следующим образом:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class App {
    // 时间格式化对象
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) throws InterruptedException {
        // 创建线程池执行任务
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            // 执行任务
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    // 得到时间对象
                    Date date = new Date(finalI * 1000);
                    // 执行时间格式化
                    formatAndPrint(date);
                }
            });
        }
        // 线程池执行完任务之后关闭
        threadPool.shutdown();
    }

    /**
     * 格式化并打印时间
     * @param date 时间对象
     */
    private static void formatAndPrint(Date date) {
        // 执行格式化
        String result = null;
        // 加锁
        synchronized (App.class) {
            result = simpleDateFormat.format(date);
        }
        // 打印最终结果
        System.out.println("时间:" + result);
    }
}

Результат выполнения вышеуказанной программы:image.pngИз вышеприведенных результатов видно, что использованиеsynchronizedПосле блокировки программа может выполняться в обычном режиме. ​

Недостатки блокировки

Хотя метод блокировки может решить проблему безопасности потоков, он также приносит новые проблемы: когда программа заблокирована, все потоки должны стоять в очереди для выполнения определенных служб, что незаметно снижает эффективность работы программы.. ​

Есть ли решение, которое не только решает проблему потокобезопасности, но и повышает скорость выполнения программы? ​

Ответ: да, в настоящее времяThreadLocalсобирается играть.

c) Решить проблемы безопасности потоков: ThreadLocal

1. Введение в ThreadLocal

ThreadLocalВ буквальном смысле это означает локальную переменную потока, то есть это частная переменная в потоке, и каждый поток может использовать только свою собственную переменную. ​

Взяв в качестве примера указанное выше время форматирования пула потоков, когда в пуле потоков 10 потоков,SimpleDateFormatбудет депонированThreadLocal, он создаст только 10 объектов, даже если вы хотите выполнить 1000 задач форматирования времени, он создаст только 10 новых объектовSimpleDateFormatобъект, каждый поток вызывает свой собственныйThreadLocalПеременная. ​

2. Базовое использование ThreadLocal

ThreadLocalСуществует три широко используемых основных метода:

  1. метод set: используется для установки независимой от потока копии переменной.ThreadLocal без операций set подвержен грязным данным.
  2. метод get: используется для получения копии независимой от потока переменной.Объект ThreadLocal без операции получения не имеет смысла.
  3. Метод удаления: используется для удаления копий переменных, не зависящих от потока.Без операции удаления легко вызвать утечку памяти.

Все методы ThreadLocal показаны на следующем рисунке:image.pngОфициальная документация:docs.Oracle.com/java-color/8/do…

Основное использование ThreadLocal выглядит следующим образом:

/**
 * @公众号:Java中文社群
 */
public class ThreadLocalExample {
    // 创建一个 ThreadLocal 对象
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // 线程执行任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName + " 存入值:" + threadName);
                // 在 ThreadLocal 中设置值
                threadLocal.set(threadName);
                // 执行方法,打印线程中设置的值
                print(threadName);
            }
        };
        // 创建并启动线程 1
        new Thread(runnable, "MyThread-1").start();
        // 创建并启动线程 2
        new Thread(runnable, "MyThread-2").start();
    }

    /**
     * 打印线程中的 ThreadLocal 值
     * @param threadName 线程名称
     */
    private static void print(String threadName) {
        try {
            // 得到 ThreadLocal 中的值
            String result = threadLocal.get();
            // 打印结果
            System.out.println(threadName + " 取出值:" + result);
        } finally {
            // 移除 ThreadLocal 中的值(防止内存溢出)
            threadLocal.remove();
        }
    }
}

Результат выполнения вышеуказанной программы:image.pngИз приведенных выше результатов видно, что каждый поток будет читать только свой собственный поток.ThreadLocalценность.

3. Расширенное использование ThreadLocal

① Инициализация: начальное значение
public class ThreadLocalByInitExample {
    // 定义 ThreadLocal
    private static ThreadLocal<String> threadLocal = new ThreadLocal(){
        @Override
        protected String initialValue() {
            System.out.println("执行 initialValue() 方法");
            return "默认值";
        }
    };

    public static void main(String[] args) {
        // 线程执行任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 执行方法,打印线程中数据(未设置值打印)
                print(threadName);
            }
        };
        // 创建并启动线程 1
        new Thread(runnable, "MyThread-1").start();
        // 创建并启动线程 2
        new Thread(runnable, "MyThread-2").start();
    }

    /**
     * 打印线程中的 ThreadLocal 值
     * @param threadName 线程名称
     */
    private static void print(String threadName) {
        // 得到 ThreadLocal 中的值
        String result = threadLocal.get();
        // 打印结果
        System.out.println(threadName + " 得到值:" + result);
    }
}

Результат выполнения вышеуказанной программы:image.pngпри использовании#threadLocal.setПосле метода,initialValueМетод не будет выполнен, как показано в следующем коде:

public class ThreadLocalByInitExample {
    // 定义 ThreadLocal
    private static ThreadLocal<String> threadLocal = new ThreadLocal() {
        @Override
        protected String initialValue() {
            System.out.println("执行 initialValue() 方法");
            return "默认值";
        }
    };

    public static void main(String[] args) {
        // 线程执行任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName + " 存入值:" + threadName);
                // 在 ThreadLocal 中设置值
                threadLocal.set(threadName);
                // 执行方法,打印线程中设置的值
                print(threadName);
            }
        };
        // 创建并启动线程 1
        new Thread(runnable, "MyThread-1").start();
        // 创建并启动线程 2
        new Thread(runnable, "MyThread-2").start();
    }

    /**
     * 打印线程中的 ThreadLocal 值
     * @param threadName 线程名称
     */
    private static void print(String threadName) {
        try {
            // 得到 ThreadLocal 中的值
            String result = threadLocal.get();
            // 打印结果
            System.out.println(threadName + "取出值:" + result);
        } finally {
            // 移除 ThreadLocal 中的值(防止内存溢出)
            threadLocal.remove();
        }
    }
}

Результат выполнения вышеуказанной программы:image.png

Почему код инициализации не выполняется после метода set?

Чтобы разобраться в этой проблеме, необходимо начать сThreadLocal.get()Ответ получен в исходном коде метода, потому что метод инициализацииinitialValueсуществуетThreadLocalВыполняется не сразу при создании, а при вызовеgetМетод будет только выполняться, тестовый код выглядит следующим образом:

import java.util.Date;

public class ThreadLocalByInitExample {
    // 定义 ThreadLocal
    private static ThreadLocal<String> threadLocal = new ThreadLocal() {
        @Override
        protected String initialValue() {
            System.out.println("执行 initialValue() 方法 " + new Date());
            return "默认值";
        }
    };
    public static void main(String[] args) {
        // 线程执行任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 得到当前线程名称
                String threadName = Thread.currentThread().getName();
                // 执行方法,打印线程中设置的值
                print(threadName);
            }
        };
        // 创建并启动线程 1
        new Thread(runnable, "MyThread-1").start();
        // 创建并启动线程 2
        new Thread(runnable, "MyThread-2").start();
    }

    /**
     * 打印线程中的 ThreadLocal 值
     * @param threadName 线程名称
     */
    private static void print(String threadName) {
        System.out.println("进入 print() 方法 " + new Date());
        try {
            // 休眠 1s
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 得到 ThreadLocal 中的值
        String result = threadLocal.get();
        // 打印结果
        System.out.println(String.format("%s 取得值:%s %s",
                threadName, result, new Date()));
    }
}

Результат выполнения вышеуказанной программы:image.pngКак видно из времени, напечатанного выше:initialValueметод неThreadLocalВыполняется при создании, но при вызовеThread.getметод выполняется.

СледующийThreadlocal.getРеализация исходного кода:

public T get() {
    // 得到当前的线程
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 判断 ThreadLocal 中是否有数据
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            // 有 set 值,直接返回数据
            return result;
        }
    }
    // 执行初始化方法【重点关注】
    return setInitialValue();
}
private T setInitialValue() {
    // 执行初始化方法【重点关注】
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

Как видно из приведенного выше исходного кода, когдаThreadLocalКогда есть значение, он вернет значение напрямуюe.value,ТолькоThreadlocalМетод инициализации будет выполняться только тогда, когда вinitialValue.

Примечания. Типы должны быть согласованы.

Обратите внимание на использованиеinitialValue, тип возвращаемого значения должен быть таким же, какThreadLocaОпределенные типы данных согласованы, как показано на следующем рисунке:image.pngЕсли данные несовместимы, это вызоветClassCaseExceptionИсключение преобразования типа, как показано на следующем рисунке:image.png

② Инициализация 2: withInitial
import java.util.function.Supplier;

public class ThreadLocalByInitExample {
    // 定义 ThreadLocal
    private static ThreadLocal<String> threadLocal =
            ThreadLocal.withInitial(new Supplier<String>() {
                @Override
                public String get() {
                    System.out.println("执行 withInitial() 方法");
                    return "默认值";
                }
            });
    public static void main(String[] args) {
        // 线程执行任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                // 执行方法,打印线程中设置的值
                print(threadName);
            }
        };
        // 创建并启动线程 1
        new Thread(runnable, "MyThread-1").start();
        // 创建并启动线程 2
        new Thread(runnable, "MyThread-2").start();
    }

    /**
     * 打印线程中的 ThreadLocal 值
     * @param threadName 线程名称
     */
    private static void print(String threadName) {
        // 得到 ThreadLocal 中的值
        String result = threadLocal.get();
        // 打印结果
        System.out.println(threadName + " 得到值:" + result);
    }
}

Результат выполнения вышеуказанной программы:image.pngЧерез найденный выше код,withInitialправильное использование метода иinitialValueРазницы вроде бы нет, так зачем создавать два одинаковых метода? Не волнуйтесь, приглашенный офицер продолжал смотреть вниз.

③ Более краткое использование withInitial

withInitialПреимущество метода в том, что можно проще реализовать инициализацию переменных, как показано в следующем коде:

public class ThreadLocalByInitExample {
    // 定义 ThreadLocal
    private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "默认值");
    public static void main(String[] args) {
        // 线程执行任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                // 执行方法,打印线程中设置的值
                print(threadName);
            }
        };
        // 创建并启动线程 1
        new Thread(runnable, "MyThread-1").start();
        // 创建并启动线程 2
        new Thread(runnable, "MyThread-2").start();
    }

    /**
     * 打印线程中的 ThreadLocal 值
     * @param threadName 线程名称
     */
    private static void print(String threadName) {
        // 得到 ThreadLocal 中的值
        String result = threadLocal.get();
        // 打印结果
        System.out.println(threadName + " 得到值:" + result);
    }
}

Результат выполнения вышеуказанной программы:image.png

4. Форматирование времени версии ThreadLocal

понялThreadLocalПосле использования мы вернемся к теме этой статьи, и далее мы будем использоватьThreadLocalЧтобы реализовать форматирование 1000 раз, конкретный код реализации выглядит следующим образом:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadLocalByDateFormat {
    // 创建 ThreadLocal 并设置默认值
    private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));

    public static void main(String[] args) {
        // 创建线程池执行任务
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
        // 执行任务
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            // 执行任务
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    // 得到时间对象
                    Date date = new Date(finalI * 1000);
                    // 执行时间格式化
                    formatAndPrint(date);
                }
            });
        }
        // 线程池执行完任务之后关闭
        threadPool.shutdown();
        // 线程池执行完任务之后关闭
        threadPool.shutdown();
    }
    /**
     * 格式化并打印时间
     * @param date 时间对象
     */
    private static void formatAndPrint(Date date) {
        // 执行格式化
        String result = dateFormatThreadLocal.get().format(date);
        // 打印最终结果
        System.out.println("时间:" + result);
    }
}

Результат выполнения вышеуказанной программы:image.pngИз вышеприведенных результатов видно, что использованиеThreadLocalЭто также может решить проблему параллелизма потоков и избежать проблемы выполнения очереди кодовой блокировки.

Сценарий использования 2: передача данных между классами

В дополнение к приведенным выше сценариям использования мы также можемиспользовать**ThreadLocal**Для достижения передачи данных между классами и методами в потоках. например, зарегистрированный пользовательUserИнформация об объекте, нам нужно использовать ее несколько раз в разных подсистемах, если мы используем традиционный метод, нам нужно использовать метод для передачи параметров и возвращаемых значений для передачиUserОбъекты, однако, это создает незримую взаимную связь между классами и даже системами и системами, поэтому в настоящее время мы можем использоватьThreadLocalреализоватьUserпередача объекта. ​

После определения плана давайте реализуем конкретный бизнес-код. Сначала мы можем построить и инициализироватьUserобъект, и этоUserОбъекты хранятся вThreadLocal, после завершения хранения мы можем напрямую получить и использовать его в других классах в том же потоке, таких как класс складирования или класс заказаUserобъект, конкретный код реализации выглядит следующим образом. ​

Бизнес-код в основном потоке:

public class ThreadLocalByUser {
    public static void main(String[] args) {
        // 初始化用户信息
        User user = new User("Java");
        // 将 User 对象存储在 ThreadLocal 中
        UserStorage.setUser(user);
        // 调用订单系统
        OrderSystem orderSystem = new OrderSystem();
        // 添加订单(方法内获取用户信息)
        orderSystem.add();
        // 调用仓储系统
        RepertorySystem repertory = new RepertorySystem();
        // 减库存(方法内获取用户信息)
        repertory.decrement();
    }
}

UserКласс сущности:

/**
 * 用户实体类
 */
class User {
    public User(String name) {
        this.name = name;
    }
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

ThreadLocalКласс действия:

/**
 * 用户信息存储类
 */
class UserStorage {
    // 用户信息
    public static ThreadLocal<User> USER = new ThreadLocal();

    /**
     * 存储用户信息
     * @param user 用户数据
     */
    public static void setUser(User user) {
        USER.set(user);
    }
}

Класс заказа:

/**
 * 订单类
 */
class OrderSystem {
    /**
     * 订单添加方法
     */
    public void add() {
        // 得到用户信息
        User user = UserStorage.USER.get();
        // 业务处理代码(忽略)...
        System.out.println(String.format("订单系统收到用户:%s 的请求。",
                user.getName()));
    }
}

Класс хранения:

/**
 * 仓储类
 */
class RepertorySystem {
    /**
     * 减库存方法
     */
    public void decrement() {
        // 得到用户信息
        User user = UserStorage.USER.get();
        // 业务处理代码(忽略)...
        System.out.println(String.format("仓储系统收到用户:%s 的请求。",
                user.getName()));
    }
}

Окончательный результат выполнения вышеуказанной программы:image.pngИз приведенных выше результатов видно, что когда мы сначала инициализируем в основном потокеUserПосле объекта класс заказа и класс хранения можно получить нормально без передачи каких-либо параметров.Userобъект, поэтомуРеализована передача данных между классами и методами в потоке..

Суммировать

использоватьThreadLocalМожно создавать частные переменные потока, поэтому это не вызовет проблем с потокобезопасностью при использованииThreadLocalЭто также позволяет избежать потребления производительности, вызванного очередями потоков из-за введения блокировок;ThreadLocalВы также можете реализовать передачу данных между классами и методами в потоке.

Ссылки и благодарности

«Эффективный код: руководство по разработке на Java»

"Параллельное программирование на Java 78 лекций" ​

Подпишитесь на официальный аккаунт «Сообщество китайского языка Java», чтобы увидеть больше интересных и полезных статей о параллельном программировании.