Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the difference between the concurrency options in the Java language

2025-03-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article focuses on "what are the differences in concurrency options in Java language". Interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn "what are the differences in concurrency options in the Java language?"

Perfect number

The mathematician Nico Markus (born in the 6th century BC) divided natural numbers into unique perfect numbers (perfect number), excess numbers (abundant number) or deficiency numbers (deficient number). A perfect number is equal to the sum of its positive factors (excluding itself). For example, 6 is a perfect number because its factors are 1, 2, 3, and 6. 28 is also a perfect number (28 = 1 + 2 + 4 + 7 + 14). The sum of the factors of the excess number is greater than the number, and the sum of the factors of the deficiency is less than the number.

The perfect number classification is used here for convenience. Unless you have to deal with a large number of numbers, whether to find factors or not is a trivial issue to benefit from parallelization. There are some benefits from using more threads, but the switching overhead between threads is expensive for fine-grained jobs.

Parallelize existing code

In the "functional coding style" article, we encourage you to use more advanced abstractions, such as simplification, mapping, and filters, rather than iterations. One of the advantages of this approach is that it is easy to parallelize.

Readers of my functional thinking series are familiar with numerical classification patterns that contain perfect numbers (see the sidebar of perfect numbers). None of the solutions I have shown in this series take advantage of concurrency. But because these solutions use transformation functions such as map, I can do very little work in each Java.net language to create parallelized versions.

Listing 1 is a Scala example of a perfect number classifier.

Listing 1. Parallel perfect number classifier in Scala

Object NumberClassifier {def isFactor (factor: Int, number: Int) = number% factor = = 0def factors (number: Int) = {val factorsBelowSqrt = (1 to Math.sqrt (number) .toInt). Par.filter (isFactor (_, number)) val factorsAboveSqrt = factorsBelowSqrt.par.map (number / _) (factorsBelowSqrt + + factorsAboveSqrt) .toList.def sum (factors: Seq [Int]) = factors.par.foldLeft (0) (_ + _) def isPerfect (number: Int) = sum (factors (number)-number = = number}

The factors () method in listing 1 returns a list of factors and uses the isFactor () method to filter all possible values. The factors () method uses an optimization that I described in more detail in "functional thinking: transformation and Optimization." Simply put, it is inefficient to filter each number to find a factor, because by definition, a factor is one of two numbers whose product is equal to the target number.

Instead, I only filter numbers that do not exceed the square root of the target number, and then generate a list of symmetric factors by dividing the target number by each factor less than the square root. In listing 1, the factorsBelowSqrt variable contains the results of the filter operation. The value of factorsAboveSqrt is a mapping of an existing list that is used to generate these symmetric values. Finally, the return value of factors () is a concatenated list that converts from a parallel List to a regular List.

Notice that the par modifier is added in listing 1. This modifier causes filter, map, and foldLeft to run in parallel, allowing multiple threads to be used to process requests. The par method, which is consistent throughout the Scala collection library, converts the sequence into a parallel sequence. Because the two types of sequences reflect their signatures, the par function becomes a temporary way to parallelize an operation.

The simplicity of common problems in parallelization in Scala has been proven in both language design and functional patterns. Functional programming encourages the use of generic functions, such as map, filter, and reduce, which can be further optimized by the runtime in an invisible way. Scala language designers take these optimizations into account, resulting in the design of a collection of API.

Marginal condition

In the implementation of the factors () method in listing 1, the square root of the integer (for example, the square root of 16: 4) is displayed in two lists. Therefore, the last line returned by the factors () method is a call to the distinct function, which removes duplicate values from the list. You can also use Set everywhere, not just in lists, but List often has useful functions that are not available in Set.

Groovy also allows you to easily modify existing function code and parallelize it through the GPars library, which is bundled with each Groovy distribution. The GPars framework creates useful abstractions on top of the built-in Java parallelism primitives, often wrapped in syntax candy. GPars provides a dazzling parallel mechanism, one of which can be used to allocate thread pools and then distribute operations among those pools. Listing 2 shows a perfect number classifier written in Groovy and using the GPars thread pool.

Listing 2. Parallel perfect number classifier in Groovy

Class NumberClassifierPar {static def factors (number) {GParsPool.withPool {def factors = (1..round (sqrt (number) + 1)). FindAllParallel {number% it = = 0} (factors + factors.collectParallel {number / it}). Unique ()} static def sumFactors (number) {factors (number) .inject (0, {I, j-> I + j})} static def isPerfect (number) {sumFactors (number)-number = = number}}

The factors () method in listing 2 uses the same algorithm as listing 1: it generates all factors that do not exceed the square root of the target number, then generates the remaining factors and returns a concatenated set. As in listing 1, I use the unique () method to ensure that the square root of the integer does not generate duplicate values.

Instead of zooming in on the collection as in Scala to create symmetrical parallel versions, Groovy designers created xxxParallel () versions of the language's transformation methods (such as findAllParallel () and collectParallel ()). But these methods will not work unless they are wrapped in a GPars thread pool code block.

In listing 2, I create a thread pool and call GParsPool.withPool to create a code block that supports the use of the xxxParallel () method. There are other variants of the withPool method. For example, you can specify the number of threads in the pool.

Clojure provides a similar temporary parallelization mechanism through the simplifier library. Use the simplifier version of the conversion function to achieve automatic parallelization, for example, using r/map instead of map. (r / is the simplifier namespace. The implementation of the simplifier is a compelling case study of Clojure's syntax flexibility, which enables powerful additions with minimal changes.

Actor in Scala

Scala contains many mechanisms of concurrency and parallelism. One of the more popular mechanisms is the actor model, which provides the advantage of distributing work to threads without the complexity of synchronization. Conceptually, actor has the ability to do the job and then send a non-blocking result to the coordinator. To create an actor, create a subclass of the Actor class and implement the act () method. By using Scala's syntax sugar, you can bypass many definition rituals and define actor within a block of code.

One of the optimizations I didn't perform for the number classifier in listing 1 is to use threads to partition the factor lookup part of the job. If I have four processors on my computer, I can create a thread for each processor and split the work. For example, if I try to find the sum of the factors of the number 16, I can arrange processor 1 to find factors from 1 to 4 (and sum), processor 2 to process 5 to 8, and so on. Using actor is a natural choice: I create an actor for each scope, execute each actor independently (implicitly through syntax sugar or explicitly by calling its act () method), and then collect the results, as shown in listing 3.

Listing 3. Using actor in Scala to identify perfect numbers

Object NumberClassifier extends App {def isPerfect (candidate: Int) = {val RANGE = 10000val numberOFPartitions = (candidate.toDouble / RANGE). Ceil.toIntval coordinator = selffor (I responsesExpected-= 1sum + = partialSum}} sum = = 2 * candidate}}

To keep this example simple, I wrote isPerfect () as a single complete function. I first created some partitions based on the constant RANGE. Second, I need a way to collect messages generated by actor. In the coordinator variable, I have a reference for actor to send messages to, where self is a member of Actor that represents a reliable way to get thread identifiers in Scala.

I then create a loop for the partition number and use the RANGE offset to generate the lower and upper limits of the range. Next, create an actor for the scope, using Scala's syntax sugar to avoid formal class definitions. Within actor, I created a temporary cache for partialSum, then analyzed the range and collected the found factors into partialSum. After collecting the partial sum (the sum of all factors in this range), (coordinator! PartialSum) sends a message back to the coordinator, using the exclamation point operator. This messaging syntax is inspired by the Erlang language and is used as a way to make non-blocking calls to another thread. )

Next, I start a loop and wait for all the actor to finish processing. While waiting, I entered a block of receive code. Within this code block, I want an Int message, which I assign to partialSum locally, and then decrement the desired number of responses to add that part to the sum. After all the actor is completed and the results are reported, the last line of the method compares the and with twice the number of candidates. If the comparison result is true, then my candidate number is a perfect number, and the return value of this function is true.

One of the good advantages of actor is ownership partitioning. Each actor has a partialSum local variable, but they are never related to each other. When a message is received through the coordinator, the underlying execution mechanism is not visible: you have created a receive block, and other implementation details are not visible.

The actor mechanism in Scala is an excellent example of the Java next-generation language that encapsulates existing tools for JVM and extends them with consistent abstractions. Writing similar code in the Java language and using low-level concurrency primitives requires very complex coordination of multiple threads. The actor in Scala hides all the complexity, leaving an abstraction that is easy to understand.

At this point, I believe you have a better understanding of "what are the differences in concurrency options in the Java language?" you might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report