Часть 7 серии статей о многопоточности в Java.
В этой статье мы поговорим еще об одной особенности потоков: поток демона или пользовательский поток?
Давайте сначала посмотримThread.setDaemon()
Аннотация к методу, как показано ниже.
1. Marks this thread as either a daemon thread or a user thread.
- The Java Virtual Machine exits when the only threads running are all daemon threads.
- This method must be invoked before the thread is started.
В нем упоминаются 3 пункта информации, и я объясню их один за другим:
Официальные функции
1. Демон пользователя нить или треды?
Разделите потоки Java на две категории, одна из которыхТема пользователя, то есть когда мы создаем поток, класс потока по умолчанию, атрибутdaemon = false
; другой естьНить демона, когда мы устанавливаемdaemon = true
, это такая нить.
Общее отношение между ними таково: пользовательский поток — это поток, работающий на переднем плане, а поток демона — это поток, работающий в фоновом режиме.В общем, поток демона предоставляет некоторые услуги пользовательскому потоку. Например, в Java мы часто говорим, что поток утилизации памяти сборщика мусора — это поток демона.
2. JVM и пользовательские потоки сосуществуют
Второй пункт выше переводится как:Когда все пользовательские потоки выполняются и работают только потоки демона, JVM завершает работу.. Я читал интернет-материалы и некоторые книги, и во всех есть это предложение, но в них только это предложение, без объяснения причин, кажется, что это предложение стало теоремой и не нуждается в доказательстве. Теперь, когда мы недавно создали среду отладки JVM, давайте выясним. (Потребовалось много времени, чтобы узнать)
Мы видим исходный код JVMthread.cpp
Файл, вот код, который реализует поток. Через приведенное выше предложение, указывающее на то, что есть место для мониторинга количества текущей нити Non-Daememon, в противном случае вы знаете, что сейчас есть только темы демона? Скорее всего, способ удалить нить на внутреннюю часть, наряду с этой идеей, мы посмотрим на файлremove()
метод. код показывает, как показано ниже.
/**
* 移除线程 p
*/
void Threads::remove(JavaThread* p, bool is_daemon) {
// Reclaim the ObjectMonitors from the omInUseList and omFreeList of the moribund thread.
ObjectSynchronizer::omFlush(p);
/**
* 创建一个监控锁对象 ml
*/
// Extra scope needed for Thread_lock, so we can check
// that we do not remove thread without safepoint code notice
{ MonitorLocker ml(Threads_lock);
assert(ThreadsSMRSupport::get_java_thread_list()->includes(p), "p must be present");
// Maintain fast thread list
ThreadsSMRSupport::remove_thread(p);
// 当前线程数减 1
_number_of_threads--;
if (!is_daemon) {
/**
* 非守护线程数量减 1
*/
_number_of_non_daemon_threads--;
/**
* 当非守护线程数量为 1 时,唤醒在 destroy_vm() 方法等待的线程
*/
// Only one thread left, do a notify on the Threads_lock so a thread waiting
// on destroy_vm will wake up.
if (number_of_non_daemon_threads() == 1) {
ml.notify_all();
}
}
/**
* 移除掉线程
*/
ThreadService::remove_thread(p, is_daemon);
// Make sure that safepoint code disregard this thread. This is needed since
// the thread might mess around with locks after this point. This can cause it
// to do callbacks into the safepoint code. However, the safepoint code is not aware
// of this thread since it is removed from the queue.
p->set_terminated_value();
} // unlock Threads_lock
// Since Events::log uses a lock, we grab it outside the Threads_lock
Events::log(p, "Thread exited: " INTPTR_FORMAT, p2i(p));
}
Я добавил в него несколько комментариев, и можно обнаружить, что это именно то, что мы думали, количество потоков, не являющихся демонами, записывается, и когда поток, не являющийся демоном, равен 1, он проснется черезdestory_vm()
Поток, ожидающий в методе, мы подтверждаем, что нашли код потока, который вызовет выход JVM для мониторинга пробуждения, когда количество потоков, не являющихся демонами, равно 1. Далее посмотримdestory_vm()
код, также вthread.cpp
под файл.
bool Threads::destroy_vm() {
JavaThread* thread = JavaThread::current();
#ifdef ASSERT
_vm_complete = false;
#endif
/**
* 等待自己是最后一个非守护线程条件
*/
// Wait until we are the last non-daemon thread to execute
{ MonitorLocker nu(Threads_lock);
while (Threads::number_of_non_daemon_threads() > 1)
/**
* 非守护线程数大于 1,则一直等待
*/
// This wait should make safepoint checks, wait without a timeout,
// and wait as a suspend-equivalent condition.
nu.wait(0, Mutex::_as_suspend_equivalent_flag);
}
/**
* 下面代码是关闭 VM 的逻辑
*/
EventShutdown e;
if (e.should_commit()) {
e.set_reason("No remaining non-daemon Java threads");
e.commit();
}
...... 省略余下代码
}
Здесь мы видим, что, когда количество нитей без демона больше 1, он ждет до тех пор, пока не оставшаяся нить без демона, нить будет выполнена после выхода JVM. На этот раз необходимо найти точку, при звонкеdestroy_vm()
метод?还是通过查看代码以及注释,发现是在main()
Запускается после завершения выполнения метода.
существуетjava.c
документJavaMain()
В методе вызов наконец выполняетсяLEAVE()
метод, который вызывает(*vm)->DestroyJavaVM(vm);
чтобы вызвать выход JVM и, наконец, вызватьdestroy_vm()
метод.
#define LEAVE() \
do { \
if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { \
JLI_ReportErrorMessage(JVM_ERROR2); \
ret = 1; \
} \
if (JNI_TRUE) { \
(*vm)->DestroyJavaVM(vm); \
return ret; \
} \
} while (JNI_FALSE)
Итак, мы также знаем, почему основной поток может выйти раньше дочернего? Хотя основной поток вызывается перед выходомdestroy_vm()
метод, но вdestroy_vm()
Метод ожидает завершения выполнения потока, не являющегося демоном.Если дочерний поток является потоком, не являющимся демоном, JVM будет продолжать ждать и не завершит работу немедленно.
Подытожим этот пункт:Когда программа Java выходит из основного потока, она инициирует выполнение операции выхода JVM, но метод выхода JVMdestroy_vm()
Будет ждать завершения выполнения всех потоков, не являющихся демонами, что является номером переменной.ofnondaemonКоличество потоков статистических потоков, не являющихся демонами, эта переменная при добавлении нового потока и поток будет удален, операция уменьшения.
Другая производная:При выходе из JVM все оставшиеся потоки демона будут отброшены, ни часть finally кода, ни операция раскрутки стека выполняться не будут (то есть исключение не будет перехвачено). Это очевидно, JVM завершает работу, и поток демона завершается естественным образом.Конечно, это особенность потока демона.
3. Это мужчина или женщина? Рожденный быть обреченным
Это легче понять, то есть, является ли поток пользовательским потоком или потоком демона, это должно быть определено до запуска потока. вызовstart()
До метода это был просто объект, не сопоставленный с потоком в JVM, его можно модифицировать в это время.daemon
собственность, звонитеstart()
После метода в JVM есть поток, который отображает объект потока, поэтому его нельзя изменить.
Другие особенности
1. Свойства потока демона наследуются от родительского потока.
Нам не нужно писать код, чтобы проверить это. Вы можете увидеть это непосредственно, посмотрев на метод построения исходного кода Thread. Код выглядит следующим образом.
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...省略一堆代码
this.daemon = parent.isDaemon();
...省略一堆代码
}
2. Приоритет потока демона ниже, чем у пользовательского потока.
Видеть много книг и материалов сказать это, я тоже очень скептически. Поэтому я написал следующий код для проверки, является ли приоритетную атмосферу потока демона ниже, чем у пользовательской нитки?
public class TestDaemon {
static AtomicLong daemonTimes = new AtomicLong(0);
static AtomicLong userTimes = new AtomicLong(0);
public static void main(String[] args) {
int count = 2000;
List<MyThread> threads = new ArrayList<>(count);
for (int i = 0; i < count; i ++) {
MyThread userThread = new MyThread();
userThread.setDaemon(false);
threads.add(userThread);
MyThread daemonThread = new MyThread();
daemonThread.setDaemon(true);
threads.add(daemonThread);
}
for (int i = 0; i < count; i++) {
threads.get(i).start();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("daemon 统计:" + daemonTimes.get());
System.out.println("user 统计:" + userTimes.get());
System.out.println("daemon 和 user 相差时间:" + (daemonTimes.get() - userTimes.get()) + "ms");
}
static class MyThread extends Thread {
@Override
public void run() {
if (this.isDaemon()) {
daemonTimes.getAndAdd(System.currentTimeMillis());
} else {
userTimes.getAndAdd(System.currentTimeMillis());
}
}
}
}
Результат работы следующий.
结果1:
daemon 统计:1570785465411405
user 统计:1570785465411570
daemon 和 user 相差时间:-165ms
结果2:
daemon 统计:1570786615081403
user 统计:1570786615081398
daemon 和 user 相差时间:5ms
Не удивительно, что это почти одно и то же, но я не могу определить этот случай: приоритет потока демона и пользовательского потока одинаков. Прочитав код JVM, я не могу найти, что приоритет потока демона ниже, чем у пользовательского потока.Я все еще сомневаюсь в этом.Друзья, которые это знают, могут оставить сообщение, чтобы сказать что-то и обменяться и учиться у каждого разное.
Суммировать
Подводя итог пунктам, изложенным в этой статье, скажем, что потоки делятся на два типа: один — пользовательский поток, а другой — поток демона; если вы хотите установить поток как поток демона, вам нужно вызвать его в нитьstart()
предварительные настройки методаdaemon
свойства, а с точки зрения исходного кода JVM, почему JVM автоматически завершает работу после выполнения всех пользовательских потоков. Затем было объяснено, что поток демона имеет наследование, родительский поток является потоком демона, затем дочерний поток является потоком демона по умолчанию; кроме того, в некоторых книгах и материалах говоритсяРезьбы демона имеют более низкий приоритет, чем пользовательские потокиПредложите себя и надейтесь узнать, что друзья могут помочь ответить.
Если вы считаете, что эта статья полезна, пожалуйста, нажмите在看
, поддержка, оригинальность не так просто.
推荐阅读
После стольких лет написания Java-кода я, наконец, отлаживаю его на JVM.
Оригинал | Последняя и самая простая компиляция кода openjdk13 во всей сети
Самые базовые знания о потоках
Босс сказал вам перестать блокировать
Ешьте фаст-фуд, чтобы узнать последовательный, параллельный, параллелизм
Заварите чашку чая и научитесь быть асинхронным
Как много вы знаете о процессе?
Шаблоны проектирования читают и забывают, забывают и снова читают?
Ответьте на «Шаблоны проектирования» в фоновом режиме, чтобы получить электронную книгу «Одна история, один шаблон проектирования».
觉得文章有用帮忙转发&点赞,多谢朋友们!
Эта статья опубликована в блогеOpenWriteвыпуск!