순차 프로그래밍 즉, 프로그램의 모든 항목은 언제든지 한 단계만 수행할 수 있습니다. 동시 프로그래밍, 프로그램은 프로그램의 여러 부분을 병렬로 실행할 수 있습니다.
스레드가 작업을 구동할 수 있으므로 Runnable
인터페이스에서 제공할 수 있는 작업을 설명하는 방법이 필요합니다. 작업을 정의하려면 Runnable
인터페이스를 구현하고 작업이 명령을 실행할 수 있도록 run()
메서드를 작성하면 됩니다.
클래스가 Runnable
에서 파생되면 run()
메서드가 있어야 하지만 이 메서드에는 특별한 것이 없습니다. 내장 함수를 생성하지 않습니다. 스레드 기능. 스레드 동작을 구현하려면 Runnable
接口来提供。要想定义任务,只需实现Runnable
接口并编写run()
方法,使得该任务可以执行你的命令。
当从Runnable
导出一个类时,它必须具有run()
方法,但是这个方法并无特殊之处——它不会产生任何内在的线程能力。要实现线程行为,你必须显式地将一个任务附着到线程上。
FixedThreadPool
与 CachedThreadPool
FixedThreadPool
, 可以一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了。这可以节省时间,因为你不用为每个任务都固定地付出创建线程的开销。在事件驱动的系统中,需要线程的事件处理器,通过直接从池中获取线程,也可以如你所愿地得到服务。你不会滥用可获得的资源,因为FixedThreadPool使用的Thread对象的数量是有界的。
注意,在任何线程池中,现有线程在可能的情况下,都会被自动复用。
尽管本书将使用CachedThreadPool
,但是也应该考虑在产生线程的代码中使用FiexedThreadPool
。CachedThreadPool
在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor
的首选。只有当这种方式会引发问题时,你才需要切换到FixedThreadPool
。
SingleThreadExecutor
就像是线程数量为1
的FixedThreadPool
。(它还提供了一种重要的并发保证,其他线程不会(即没有两个线程会)被调用。这会改变任务的加锁需求)
如果向SingleThreadExecutor
提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束,所有的任务将使用相同的线程。在下面的示例中,你可以看到每个任务都是按照它们被提交的顺序,并且是在下一个任务开始之前完成的。因此,SingleThreadExecutor
会序列化所有提交给它的任务,并会维护它自己(隐藏)的悬挂任务队列。
Runnable
是执行工作的独立任务,但是它不返回任务值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable
接口而不是Runnable
接口。在Java SE5中引入的Callable
是一种具有类型参数的泛型,它的类型参数表示的是从方法call()
(而不是run()
)中返回的值,并且必须使用ExecutorService.submit()
方法调用它。
另一种可能会看到的惯用法是自管理的Runnable
。
这与从Thread
继承并没有什么特别的差异,只是语法稍微晦涩一些。但是,实现接口使得你可以继承另一个不同的类,而从Thread
继承将不行。
注意,自管理的Runnable
是在构造器中调用的。这个示例相当简单,因此可能是安全的,但是你应该意识到,在构造器中启动线程可能会变得很有问题,因为另一个任务可能会在构造器结束之前开始执行,这意味着该任务能够访问处于不稳定状态的对象。这是优选Executor
而不是显式地创建Thread对
스레드에 작업을 명시적으로 연결
🎜 모든 스레드 풀에서는 가능하면 기존 스레드가 자동으로 재사용됩니다. 🎜
FixedThreadPool
및CachedThreadPool
사용FixedThreadPool
비용이 많이 드는 스레드 할당을 한 번에 미리 수행할 수 있어 스레드 수를 제한할 수 있습니다. 이렇게 하면 각 작업에 대해 스레드를 생성하는 고정 오버헤드를 지불할 필요가 없으므로 시간이 절약됩니다. 이벤트 기반 시스템에서는 스레드가 필요한 이벤트 핸들러를 풀에서 직접 스레드를 가져와서 원하는 대로 제공할 수도 있습니다. FixThreadPool에서 사용하는 Thread 객체의 수가 제한되어 있으므로 사용 가능한 리소스를 남용하지 않습니다.🎜21.2.4 작업에서 반환 값 생성🎜🎜
- 🎜이 책에서는
CachedThreadPool
을 사용하지만 스레드를 생성하는 코드에서는FiexedThreadPool
사용도 고려해야 합니다.CachedThreadPool
은 일반적으로 프로그램 실행 중에 필요한 만큼의 스레드를 생성한 다음 오래된 스레드를 재활용할 때 새 스레드 생성을 중지하므로Executor
첫 번째 선택에 적합합니다. 이 접근 방식으로 인해 문제가 발생하는 경우에만FixedThreadPool
로 전환해야 합니다. 🎜- 🎜
SingleThreadExecutor
는 스레드 수가1
인FixedThreadPool
과 같습니다. (또한 다른 스레드(즉, 두 개의 스레드가 호출되지 않음)가 호출되지 않는다는 중요한 동시성 보장을 제공합니다. 이는 작업의 잠금 요구 사항을 변경합니다.)SingleThreadExecutor
를 묻는 경우 여러 작업이 제출되면 작업이 대기열에 추가되고 다음 작업이 시작되기 전에 각 작업이 완료될 때까지 실행됩니다. 아래 예에서는 각 작업이 제출된 순서대로 완료되고 다음 작업이 시작되기 전에 완료되는 것을 볼 수 있습니다. 따라서SingleThreadExecutor
는 제출된 모든 작업을 직렬화하고 보류 중인 작업의 자체(숨겨진) 대기열을 유지 관리합니다. 🎜Runnable
은 작업을 수행하는 독립적인 작업이지만 작업 값을 반환하지는 않습니다. 작업이 완료되었을 때 값을 반환하도록 하려면Runnable
인터페이스 대신Callable
인터페이스를 구현할 수 있습니다. Java SE5에 도입된Callable
은 유형 매개변수가 있는 일반 유형입니다. 해당 유형 매개변수는run()
이 아닌call()
메소드를 나타냅니다. 이며ExecutorService.submit()
메서드를 사용하여 호출해야 합니다. 🎜🎜21.2.9 코딩 변형 🎜🎜 여러분이 볼 수 있는 또 다른 관용구는 자체 관리되는Runnable
입니다. 🎜🎜 이는Thread
에서 상속하는 것과 특별히 다르지 않지만 구문이 약간 더 모호합니다. 그러나 인터페이스를 구현하면 다른 클래스에서 상속할 수 있지만Thread
에서 상속하면 그렇지 않습니다. 🎜🎜 자체 관리형Runnable
이 생성자에서 호출된다는 점에 유의하세요. 이 예제는 매우 간단하므로 안전할 수 있지만 생성자가 끝나기 전에 다른 작업이 실행되기 시작할 수 있으므로 생성자에서 스레드를 시작하면 문제가 될 수 있다는 점을 알아야 합니다. 즉, 작업이 불안정한 상태의 개체에 액세스할 수 있다는 뜻입니다. 이것이Thread 개체
를 명시적으로 생성하는 것보다Executor
를 선호하는 또 다른 이유입니다. 🎜🎜21.2.13 스레드 그룹🎜🎜🎜 스레드 그룹은 스레드 모음을 보유하고 있습니다. 스레드 그룹의 가치는 Joshua Bloch의 말을 인용하여 요약할 수 있습니다. "스레드 그룹은 무시할 수 있는 실패한 시도로 생각하는 것이 가장 좋습니다."
나처럼 스레드 그룹의 가치를 발견하기 위해 많은 시간과 노력을 쏟았다면 수년 동안 같은 질문을 했지만 이 주제에 대한 Sun의 공식 성명이 없는 이유에 놀랄 수도 있습니다. 나는 또한 Java에서 발생한 다른 변경 사항에 대해서도 여러 번 물었습니다. 이 문제는 노벨 경제학상 수상자인 조셉 스티글리츠(Joseph Stiglitz)의 삶의 철학으로 설명할 수 있습니다. 이를 '확진 헌신 이론'이라고 합니다. 곰.”
스레드의 특성상 스레드에서 벗어나는 예외는 잡을 수 없습니다. 예외가 작업의 run()
메서드를 벗어나면 이 잘못된 예외를 잡기 위해 특별한 조치를 취하지 않는 한 콘솔로 전파됩니다. run()
方法,它就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常。
可以把单线程程序当作在问题域求解的单一实体,每次只能做一件事情。
因为canceled
标志是boolean
类型的,所以它是原子性的,即诸如赋值和返回值这样的简单操作在发生时没有中断的可能,因此你不会看到这个域处于在执行这些简单操作的过程中的中间状态。
有一点很重要,那就是要注意到递增程序自身也需要多个步骤,并且在递增过程中任务可能会被纯种机制挂起——也就是说,在Java中,递增不是原子性的操作。因此,如果不保护任务,即使单一的递增也不是安全的。
Executor
上调用shutdownNow()
,它将发送一个interrupt()
调用给它启动的所有线程。
Executor
通过调用submit()
而不是excutor()
来启动任务,就可以持有该任务的上下文。submit()
将返回一个泛型的Future<?>
,持有这种Future
的关键在于你可以在其上调用cancel()
,并因此可以使用它来中断某个特定任务。如果你将true
传递给cancel()
,那么它就会拥有在该线程上调用interrupt()
以停止这个线程的权限。因此,cancel()
是一个种中断由Excutor
启动的单个线程的方式。
SleepBlock()
是可中断的阻塞,而IOBlocked
和SynchronizedBlocked
是不可中断的阻塞。上面三个类的示例证明I/O和在synchronized
块上的等待是不可中断的。无论是I/O还是尝试调用synchronized
方法,都不需要任何InterruptedException
处理器。
从关于上面三个类的示例的输出中可以看到,你能够中断对sleep()
的调用(或者任何要求抛出InterruptedException
的调用)。但是,你不能中断试图获取synchronized
锁或者试图执行I/O操作的线程。这有点令人烦恼,特别是在妊I/O的任务时,因为这意味着IO具有锁住你的多线程程序的潜在可能。特别是对于基于Web的程序,这更是关乎利害。
对于这类问题,有一个略显笨拙但是有时确实行之有效的解决方案,即关闭任务在其上发生阻塞的底层资源:
wait()
使你可以等待某个条件发生变化,而改变这个条件超出了当前方法的控制能力。通常,这种条件将由另一个任务来改变。你肯定不想在你的任务测试这个条件的同时,不断地进行空循环,这被称为忙等待, 通常是一种不良的周期使用方式。因此wait()
会在等等外部世界产生变化的时候将任务挂起,并且只有在notify()
或notifyAll()
发生时,即表示发生了某些感兴趣的事物,这个任务才会被唤醒并去检查所产生的变化。因此,wait()
提供了一种在任务之间对活动同步的方式。
调用sleep()
的时候锁并没有被 释放,调用yield()
也属于这种情况,理解这一点很重要。 wait()
, notify()
以及notifyAll()
有一个比较特殊的方面,那就是这些方法是基类Object
的一个部分,而不是属于Thread
canceled
플래그가 boolean
유형이기 때문에 원자적입니다. 즉, 간단한 할당 및 반환 값 작업이 중단 없이 발생하므로 이러한 간단한 작업을 수행하는 동안 중간 상태의 도메인은 표시되지 않습니다. 🎜🎜 증분 절차 자체에는 여러 단계가 필요하며 증분 프로세스 중에 순수 메커니즘에 의해 작업이 일시 중단될 수 있다는 점에 유의하는 것이 중요합니다. 즉, Java에서 증분은 원자성 작동이 아닙니다. 따라서 작업을 보호하지 않으면 단 한 번의 증분도 안전하지 않습니다. 🎜Executor
에서 shutdownNow()
를 호출하면 interrupt() 해당 스레드에 의해 시작된 모든 스레드에 대해 호출됩니다. 🎜🎜 <code>Executor
는 작업을 시작하기 위해 excutor()
대신 submit()
를 호출하여 작업의 컨텍스트를 보유할 수 있습니다. submit()
은 일반 Future<?>
를 반환합니다. 이 Future
를 유지하는 열쇠는 에서 호출할 수 있다는 것입니다. >cancel()
이므로 특정 작업을 중단하는 데 사용할 수 있습니다. cancel()
에 true
를 전달하면 해당 스레드에서 interrupt()
를 호출하여 중지할 수 있는 권한을 갖게 됩니다. 따라서 cancel()
은 Excutor
에 의해 시작된 단일 스레드를 중단하는 방법입니다. 🎜🎜 SleepBlock()
은 중단 가능한 차단이고, IOBlocked
및 SynchronizedBlocked
는 무중단 차단입니다. 위 세 가지 클래스의 예는 I/O 및 동기화
블록 대기가 중단되지 않음을 증명합니다. I/O나 synchronized
메서드 호출 시도에는 InterruptedException
핸들러가 필요하지 않습니다. sleep()
호출(또는 InterruptedException
을 발생시켜야 하는 다른 호출)을 중단할 수 있습니다. > 전화). 그러나 동기화
잠금을 획득하려고 하거나 I/O 작업을 수행하려고 하는 스레드는 중단할 수 없습니다. 이는 특히 I/O 작업을 수행할 때 약간 짜증나는 일입니다. 이는 IO가 다중 스레드 프로그램을 잠글 가능성이 있다는 것을 의미하기 때문입니다. 특히 웹 기반 프로그램의 경우 이는 중요한 문제입니다. 🎜🎜 이 유형의 문제에 대한 약간 서투르지만 때로는 효과적인 해결책은 작업이 차단된 기본 리소스를 닫는 것입니다. 🎜wait()
를 사용하면 특정 조건이 변경될 때까지 기다릴 수 있으며, 이 조건을 변경하는 것은 현재 메서드의 제어 범위를 벗어납니다. 종종 이 조건은 다른 작업에 의해 변경됩니다. 작업이 이 조건을 테스트하는 동안 빈 루프를 계속 수행하고 싶지는 않을 것입니다. 이를 바쁜 대기라고 하며 일반적으로 주기를 잘못 사용하는 것입니다. 따라서 wait()
는 외부 세계의 변경을 기다리는 동안 작업을 일시 중지하며 notify()
또는 notifyAll()
이 실행될 때만 작업을 일시 중지합니다. 발생한다는 것은 관심 있는 일이 발생했음을 의미하며 작업이 활성화되어 변경 사항을 확인하게 됩니다. 따라서 wait()
는 작업 간 활동을 동기화하는 방법을 제공합니다. 🎜🎜 sleep()
이 호출될 때 잠금이 해제되지 않습니다. 이는 yield()
를 호출할 때도 마찬가지입니다. wait()
, notify()
및 notifyAll()
에는 특별한 측면이 있습니다. 즉, 이러한 메서드는 기본 클래스 A입니다. 스레드
의 일부가 아닌 객체
의 일부입니다. 🎜🎜 신호를 놓쳤습니다. 🎜 Java의 스레딩 메커니즘에 대한 논의에서 혼란스러운 설명이 있습니다. notifyAll()
은 "다음 등의 모든 작업"을 깨울 것입니다. 이는 wait()
상태의 모든 작업이 프로그램의 어느 곳에서나 notifyAll()
에 대한 호출에 의해 활성화된다는 의미입니까? 이것이 사실이 아님을 보여주는 예가 있습니다. 실제로 특정 잠금에 대해 notifyAll()
이 호출되면 이 잠금을 기다리는 작업만 활성화됩니다. notifyAll()
将唤醒“所有下在等等的任务”。这是否意味着在程序中任何地方,任何处于wait()
状态中的任务都将被任何对notifyAll()
的调用唤醒呢?有示例说明情况并非如此——事实上,当notifyAll()
因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒。
由Edsger Dijkstrar提出的哲学家就餐问题是一个经典的死锁例证。
要修正死锁问题,你必须明白,当以下四个条件同时满足时,就会发生死锁:
互斥条件。任务使用的资源中至少有一个是不能共享的。这里,一根Chopstick一次就只能被一个Philosopher使用。
至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。也就是说,要发生死锁,Philosopher必须拿着一根Chopstick并且等待另一根。
资源不能被任务抢占,任务必须把资源释放当作普通事件。Philosopher很有礼貌,他们不会从其他Philosopher那里抢占Chopstick。
必须有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的浆,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。在DeadlockingDiningPhilosophers.java中,因为每个Philosopher都试图先得到右边的Chopstick,然后得到左边的Chopstick,所以发徨了循环等待。
所以要防止死锁的话,只需破坏其中一个即可。防止死锁最容易的方法是破坏第4个条件。
适用场景:它被用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作完成。即一个或多个任务需要等待,等待到其它任务,比如一个问题的初始部分,完成为止。
你可以向CountDownLatch
对象设置一个初始值,任何在这个对象上调用wait()的方法都将阻塞,直到这个计数值到达0.其他因结束其工作时,可以在访对象上调用countDown()来减小这个计数值。CountDownLatch
被设计为只解发一次,计数值不能被重置。如果你需要能够重置计数值的版本,则可以使用CyclicBarrier
。
调用countDown()
的任务在产生这个调用时并没有被阻塞,只有对await()
的调用会被阻塞,直至计数值到达0
。
CountDownLatch
的典型用法是将一个程序分为n
个互相独立的可解决任务,并创建值为n
的CountDownLatch
。当每个任务完成时,都会在这个锁存器上调用countDown()
。等待问题被解决的任务在这个锁存器上调用await()
,将它们自己挂起,直至锁存器计数结束。
适用于这样的情况:你希望创建一组任务,它们并行地执行工作,然后在进行下一下步骤之前等待,直至所有任务都完成(看起来有些像Join())。它使得所有的并行任务都将在栅栏处列队,因此可以一致地向前移动。
例如程序赛马程序:HorseRace.java
DelayQueue
是一个无界的BlockingQueue
(同步队列),用于放置实现了Delayed
接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象是最先到期的对象。如果没有到期的对象,那么队列就没有头元素,所以poll()
将返回null
(也正因为此,我们不能将null
放置到这种队列中)。如上所述,DelayQueue
CountDownLatch
개체에 초기 값을 설정할 수 있습니다. 이 개체에서 wait()를 호출하는 모든 메서드는 카운트 값이 0에 도달할 때까지 차단됩니다. 다른 요소가 작업을 완료하면 countDown( ) 액세스 개체에 대해 카운트 값을 줄입니다. CountDownLatch
는 한 번만 내보내도록 설계되었으며 카운트 값은 재설정될 수 없습니다. 카운트를 재설정하는 버전이 필요한 경우 CyclicBarrier
를 사용할 수 있습니다. 🎜🎜 이 호출이 이루어질 때 countDown()
을 호출하는 작업은 차단되지 않습니다. 카운트 값이 0에 도달할 때까지 <code>await()
에 대한 호출만 차단됩니다. . 🎜🎜 CountDownLatch
의 일반적인 사용법은 프로그램을 n
개의 독립적인 해결 가능한 작업으로 나누고 값이 n
>CountDownLatch인 를 생성하는 것입니다. 코드>. 각 작업이 완료되면 이 래치에서 <code>countDown()
이 호출됩니다. 문제가 해결되기를 기다리는 작업은 이 래치에서 await()
를 호출하여 래치 카운트가 끝날 때까지 스스로를 일시 중단합니다. 🎜🎜21.7.2 CyclicBarrier🎜🎜 작업을 병렬로 수행하는 일련의 작업을 생성한 후 다음 단계로 진행하기 전에 모든 작업이 완료될 때까지 기다리려는 상황에 적합합니다(조금 Join()과 유사함). 이로 인해 모든 병렬 작업이 펜스에 대기하게 되므로 균일하게 앞으로 이동합니다. 🎜🎜 예를 들어, 경마 프로그램: HorseRace.java🎜🎜21.7.3 DelayQueue🎜🎜 DelayQueue
는 무제한 BlockingQueue
(동기화 대기열)입니다. Delayed
인터페이스의 객체 구현. 객체가 만료될 때만 대기열에서 제거될 수 있습니다. 이 대기열은 순서가 지정되어 있습니다. 즉, 헤드 개체가 가장 먼저 만료됩니다. 만료된 객체가 없으면 대기열에 헤드 요소가 없으므로 poll()
는 null
을 반환합니다(이 때문에 null
을 전달할 수 없습니다). 코드> 코드> 이 대기열에 배치됨). 위에서 언급했듯이 DelayQueue
는 우선순위 대기열의 변형이 됩니다. 🎜🎜21.7.4 PriorityBlockingQueue🎜🎜 이는 읽기 작업을 차단하는 매우 기본적인 우선순위 대기열입니다. 이 큐의 차단 특성은 필요한 모든 동기화를 제공하므로 여기서 명시적인 동기화가 필요하지 않다는 점에 유의해야 합니다. 큐에서 읽을 때 이 큐에 요소가 있는지 여부에 대해 걱정할 필요가 없습니다. 대기열 요소가 없으면 리더가 직접 차단됩니다. 🎜 “온실 제어 시스템”은 동시성 문제로 간주될 수 있으며, 원하는 각 온실 이벤트는 예정된 시간에 실행되는 작업입니다. ScheduledThreadPoolExecutor
가 이 문제를 해결할 수 있습니다. 그 중, Schedule()은 작업을 한 번 실행하는 데 사용되고, ScheduleAtFixedRate()는 지정된 시간마다 작업을 반복적으로 실행하는 데 사용됩니다. 두 메소드 모두 DelayTime 매개변수를 수신합니다. 실행 가능한 개체는 미래의 특정 시점에 실행되도록 설정할 수 있습니다. ScheduledThreadPoolExecutor
可以解决这种问题。其中schedule()用来运行一次任务,scheduleAtFixedRate()每隔规定的时间重复执行任务。两个方法接收delayTime参数。可以将Runnable对象设置为在将来的某个时刻执行。
BlockingQueue
: 同步队列,当第一个元素为空或不可用时,执行.take()时,等待(阻塞、Blocking)。
SynchronousQueue
BlockingQueue
: 동기 대기열, 첫 번째 요소가 비어 있거나 사용할 수 없는 경우 실행 시 . take(), 대기(차단, 차단). SynchronousQueue
: 내부 용량이 없는 차단 대기열이므로 각 put()은 take()를 기다려야 하고 그 반대도 마찬가지입니다(즉, 각 take()는 put을 기다려야 합니다) ()). 이는 마치 누군가에게 물건을 건네주는 것과 같습니다. 물건을 놓을 테이블이 없으므로, 그 사람이 손을 뻗어 물건을 받을 준비가 되어 있는 경우에만 작업할 수 있습니다. 이 경우, 동기식 대기열은 언제든지 하나의 요리만 제공될 수 있다는 개념을 강화하기 위해 식당 앞에 설정된 위치를 나타냅니다. 이 예제에서 관찰해야 할 매우 중요한 점 중 하나는 대기열을 사용하여 작업 간 통신에 따른 관리 복잡성입니다. 이 단일 기술은 제어를 반전시켜 동시 프로그래밍 프로세스를 크게 단순화합니다. 작업은 서로 직접 간섭하지 않고 대신 대기열을 통해 서로 개체를 보냅니다. 수신 작업은 개체를 처리하고 개체에 메시지를 보내는 대신 메시지로 처리합니다. 가능할 때마다 이 기술을 따르면 강력한 동시 시스템을 구축할 가능성이 크게 높아집니다.
21.8.3 작업 배포
21.9.1 뮤텍스 기술 비교
이 점에서는 컴파일러와 런타임 시스템이 다르기 때문에 정확히 무슨 일이 일어날지 알기는 어렵지만 컴파일러가 결과의 가능성을 예측하는 것을 방지해야 합니다.
Java 프로그래밍 사고 학습 수업(6) 19장 - 열거 유형
Java 프로그래밍 사고 학습 수업(7) 20장 - 주석
위 내용은 자바 프로그래밍 사고 학습 수업(8) 21장 - 동시성의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!