Совместное использование данных — одна из наиболее важных особенностей параллельных программ. Это очень важный аспект, независимо от того, является ли это объектом, наследующим класс Thread, или объектом, реализующим интерфейс Runnable.
Если вы создаете объект класса, реализующего интерфейс Runnable, и используете этот объект для запуска серии потоков, все эти потоки будут иметь одни и те же свойства. Другими словами, если один поток изменяет свойство, это изменение затрагивает все остальные потоки.
Иногда мы предпочитаем использовать его отдельно в потоке, а не делиться им с другими потоками, начатыми с того же объекта. Интерфейс параллелизма Java предоставляет очень четкий механизм для удовлетворения этого требования, называемый локальными переменными потока. Производительность этого механизма также очень впечатляет.
знай это
Выполните шаги, показанные ниже, чтобы завершить пример программы.
1. Сначала реализуем программу с указанными выше проблемами. Создайте класс с именем UnsafeTask и реализуйте интерфейс Runnable. Объявите в классе частное свойство типа java.util.Date. Код выглядит следующим образом:
Скопируйте код кода следующим образом:
публичный класс UnsafeTask реализует Runnable {
частная дата startDate;
2. Реализуйте метод run() класса UnsafeTask, который создает экземпляр атрибута startDate и выводит его значение на консоль. Спите в течение произвольного периода времени, а затем снова выведите значение атрибута startDate на консоль. Код выглядит следующим образом:
Скопируйте код кода следующим образом:
@Override
общественный недействительный запуск () {
startDate = новая дата();
System.out.printf("Начальный поток: %s : %s/n",
Thread.currentThread().getId(), startDate);
пытаться {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
е.printStackTrace();
}
System.out.printf("Поток завершен: %s: %s/n",
Thread.currentThread().getId(), startDate);
}
3. Реализовать основной класс проблемной программы. Создайте класс с методом main() UnsafeMain. В методе main() создайте объект UnsafeTask и используйте этот объект для создания 10 объектов Thread для запуска 10 потоков. В середине каждой нити задержитесь на 2 секунды. Код выглядит следующим образом:
Скопируйте код кода следующим образом:
общественный класс UnsafeMain {
public static void main(String[] args) {
Задача UnsafeTask = новая UnsafeTask();
для (int я = 0; я <10; я++) {
Поток потока = новый поток (задача);
поток.start();
пытаться {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
е.printStackTrace();
}
}
}
}
4. Согласно приведенной выше логике, каждый поток имеет разное время запуска. Однако, согласно выходному журналу ниже, существует много одинаковых значений времени. следующее:
Скопируйте код кода следующим образом:
Начальная тема: 9: Вс, 29 сентября, 23:31:08 CST 2013 г.
Начальная тема: 10 : Вс, 29 сентября, 23:31:10 CST 2013 г.
Начальная тема: 11: Вс, 29 сентября, 23:31:12 CST 2013 г.
Начальная тема: 12: Вс, 29 сентября, 23:31:14 CST 2013 г.
Тема завершена: 9 : Вс, 29 сентября 23:31:14 CST 2013
Начальная тема: 13 : Вс, 29 сентября, 23:31:16 CST 2013 г.
Тема завершена: 10: Вс, 29 сентября, 23:31:16 CST 2013 г.
Начальная тема: 14: Вс, 29 сентября, 23:31:18 CST 2013 г.
Тема завершена: 11: Вс, 29 сентября, 23:31:18 CST 2013 г.
Начальная тема: 15 : Вс, 29 сентября, 23:31:20 CST 2013 г.
Тема завершена: 12: Вс, 29 сентября, 23:31:20 CST 2013 г.
Начальная тема: 16: Вс, 29 сентября, 23:31:22 CST 2013 г.
Начальная тема: 17: Вс, 29 сентября, 23:31:24 CST 2013 г.
Тема завершена: 17: Вс, 29 сентября 23:31:24 CST 2013
Тема завершена: 15: Вс, 29 сентября, 23:31:24 CST 2013 г.
Тема завершена: 13: Вс, 29 сентября, 23:31:24 CST 2013 г.
Начальная тема: 18 : Вс, 29 сентября, 23:31:26 CST 2013 г.
Тема завершена: 14: Вс, 29 сентября, 23:31:26 CST 2013 г.
Тема завершена: 18: Вс, 29 сентября, 23:31:26 CST 2013 г.
Тема завершена: 16: Вс, 29 сентября, 23:31:26 CST 2013 г.
5. Как показано выше, для решения этой проблемы мы собираемся использовать механизм локальных переменных потока.
6. Создайте класс с именем SafeTask и реализуйте интерфейс Runnable. Код выглядит следующим образом:
Скопируйте код кода следующим образом:
публичный класс SafeTask реализует Runnable {
7. Объявите объект типа ThreadLocal<Date>. При создании экземпляра объекта метод InitialValue() переопределяется, и в этом методе возвращается фактическое значение даты. Код выглядит следующим образом:
Скопируйте код кода следующим образом:
частный статический ThreadLocal<Date> startDate = новый
ThreadLocal<Дата>() {
@Override
защищенная дата InitialValue() {
вернуть новую дату();
}
};
8. Реализуйте метод run() класса SafeTask. Этот метод аналогичен методу run() класса UnsafeTask, за исключением того, что метод атрибута startDate немного изменен. Код выглядит следующим образом:
Скопируйте код кода следующим образом:
@Override
общественный недействительный запуск () {
System.out.printf("Начальный поток: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
пытаться {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
е.printStackTrace();
}
System.out.printf("Поток завершен: %s: %s/n",
Thread.currentThread().getId(), startDate.get());
}
9. Основной класс этого безопасного примера в основном такой же, как и основной класс небезопасной программы, за исключением того, что UnsafeTask необходимо изменить на SafeTask. Конкретный код выглядит следующим образом:
Скопируйте код кода следующим образом:
общественный класс SafeMain {
public static void main(String[] args) {
Задача SafeTask = новая SafeTask();
для (int я = 0; я <10; я++) {
Поток потока = новый поток (задача);
поток.start();
пытаться {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
е.printStackTrace();
}
}
}
}
10. Запустите программу и проанализируйте разницу между двумя входными данными.
Чтобы стандартизировать именование классов, название основного класса в этой статье немного отличается от исходного текста. Кроме того, оригинальная программа и текстовое описание не соответствуют друг другу. Должно быть, это техническая ошибка.
знаю почему
Ниже приведен результат выполнения примера безопасности. Из результатов легко увидеть, что каждый поток имеет значение атрибута startDate, принадлежащее соответствующему потоку. Ввод программы следующий:
Скопируйте код кода следующим образом:
Начальная тема: 9: Вс, 29 сентября, 23:52:17 CST 2013 г.
Начальная тема: 10 : Вс, 29 сентября, 23:52:19 CST 2013 г.
Начальная тема: 11: Вс, 29 сентября, 23:52:21 CST 2013 г.
Тема завершена: 10: вс, 29 сентября, 23:52:19 CST 2013 г.
Начальная тема: 12 : Вс, 29 сентября, 23:52:23 CST 2013 г.
Тема завершена: 11: Вс, 29 сентября, 23:52:21 CST 2013 г.
Начальная тема: 13 : Вс, 29 сентября, 23:52:25 CST 2013 г.
Тема завершена: 9 : Вс, 29 сентября 23:52:17 CST 2013
Начальная тема: 14 : Вс, 29 сентября, 23:52:27 CST 2013 г.
Начальная тема: 15 : Вс, 29 сентября, 23:52:29 CST 2013 г.
Тема завершена: 13: Вс, 29 сентября, 23:52:25 CST 2013 г.
Начальная тема: 16 : Вс, 29 сентября, 23:52:31 CST 2013 г.
Тема завершена: 14: Вс, 29 сентября, 23:52:27 CST 2013 г.
Начальная тема: 17: Вс, 29 сентября, 23:52:33 CST 2013 г.
Тема завершена: 12: Вс, 29 сентября, 23:52:23 CST 2013 г.
Тема завершена: 16: Вс, 29 сентября, 23:52:31 CST 2013 г.
Тема завершена: 15: Вс, 29 сентября, 23:52:29 CST 2013 г.
Начальная тема: 18 : Вс, 29 сентября, 23:52:35 CST 2013 г.
Тема завершена: 17 : Вс, 29 сентября, 23:52:33 CST 2013 г.
Тема завершена: 18 : Вс, 29 сентября, 23:52:35 CST 2013 г.
Локальные переменные потока хранят копию свойства для каждого потока. Вы можете использовать метод get() ThreadLocal для получения значения переменной и использовать метод set() для установки значения переменной. Если доступ к локальной переменной потока осуществляется впервые и переменной еще не присвоено значение, вызывается метод InitialValue() для инициализации значения для каждого потока.
бесконечный
Класс ThreadLocal также предоставляет метод remove() для удаления значения локальной переменной, хранящейся в потоке, вызывающем этот метод.
Кроме того, API параллелизма Java также предоставляет класс InheritableThreadLocal, который позволяет дочернему потоку получать начальные значения всех наследуемых локальных переменных потока, чтобы получить значение, принадлежащее родительскому потоку. Если поток A имеет локальную переменную потока, то когда поток A создает поток B, поток B будет иметь ту же локальную переменную потока, что и поток A. Вы также можете переопределить childValue() для инициализации локальных переменных дочернего потока. Этот метод принимает значение локальной переменной потока, переданной в качестве параметра из родительского потока.
Используйте доктрину
Эта статья переведена из «Книги по параллелизму Java 7» (Д Гуа Ге украл ее как «Коллекция примеров параллелизма Java7») и используется только в качестве учебных материалов. Его нельзя использовать в коммерческих целях без разрешения.
Небольшой успех
Ниже приведена полная версия всего кода, включенного в примеры в этом разделе.
Полный код класса UnsafeTask:
Скопируйте код кода следующим образом:
пакет com.diguage.books.concurrencycookbook.chapter1.recipe9;
импортировать java.util.Date;
импортировать java.util.concurrent.TimeUnit;
/**
* Примеры, когда безопасность потоков не может быть гарантирована.
* Дата: 23 сентября 2013 г.
* Время: 23:58
*/
публичный класс UnsafeTask реализует Runnable {
частная дата startDate;
@Override
общественный недействительный запуск () {
startDate = новая дата();
System.out.printf("Начальный поток: %s : %s/n",
Thread.currentThread().getId(), startDate);
пытаться {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
е.printStackTrace();
}
System.out.printf("Поток завершен: %s: %s/n",
Thread.currentThread().getId(), startDate);
}
}
Полный код класса UnsafeMain:
Скопируйте код кода следующим образом:
пакет com.diguage.books.concurrencycookbook.chapter1.recipe9;
импортировать java.util.concurrent.TimeUnit;
/**
* Пример небезопасного потока
* Дата: 24 сентября 2013 г.
* Время: 00:04
*/
общественный класс UnsafeMain {
public static void main(String[] args) {
Задача UnsafeTask = новая UnsafeTask();
для (int я = 0; я <10; я++) {
Поток потока = новый поток (задача);
поток.start();
пытаться {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
е.printStackTrace();
}
}
}
}
Полный код класса SafeTask:
Скопируйте код кода следующим образом:
пакет com.diguage.books.concurrencycookbook.chapter1.recipe9;
импортировать java.util.Date;
импортировать java.util.concurrent.TimeUnit;
/**
* Используйте локальные переменные потока для обеспечения безопасности потока.
* Дата: 29 сентября 2013 г.
* Время: 23:34
*/
публичный класс SafeTask реализует Runnable {
частный статический ThreadLocal<Date> startDate = новый
ThreadLocal<Дата>() {
@Override
защищенная дата InitialValue() {
вернуть новую дату();
}
};
@Override
общественный недействительный запуск () {
System.out.printf("Начальный поток: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
пытаться {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
е.printStackTrace();
}
System.out.printf("Поток завершен: %s: %s/n",
Thread.currentThread().getId(), startDate.get());
}
}
Полный код класса SafeMain:
Скопируйте код кода следующим образом:
пакет com.diguage.books.concurrencycookbook.chapter1.recipe9;
импортировать java.util.concurrent.TimeUnit;
/**
* Пример безопасной нити
* Дата: 24 сентября 2013 г.
* Время: 00:04
*/
общественный класс SafeMain {
public static void main(String[] args) {
Задача SafeTask = новая SafeTask();
для (int я = 0; я <10; я++) {
Поток потока = новый поток (задача);
поток.start();
пытаться {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
е.printStackTrace();
}
}
}
}