JUC concurrent programming
-
Packages involved
-
java.util.concurrent
-
java.util.concurrent.atomic
-
java.util.concurrent.locks
-
java.util.function
-
-
The essence of concurrent programming: making full use of CPU resources
JUC
Common thread code
- Thread
- Runnable has no return value, and its efficiency is relatively lower than that of Callable
Companies generally use Callable
Concurrency and parallelism
Concurrency: multiple threads are simulated under one core CPU. Multiple threads operate the same resource and execute alternately
Parallel: under multi-core CPU, multiple threads execute together
System.out.println(Runtime.getRuntime().availableProcessors()); //Gets the number of cores of the CPU
Threads and processes
- An execution of a program is called a process
- A thread is the actual operating unit of a process
- A thread is a separate resource class without any attached operations
- A process often contains multiple threads, at least one
- Java has two threads by default: main and GC
- Java cannot start a thread. It starts a thread by calling the underlying C through a local method
- Java cannot directly manipulate hardware
Six states of threads
- NEW freshmen
- RUNNABLE run
- BLOCKED
- WAITING unlimited WAITING
- TIMED_WAITING timeout, wait for more than a period of time to stop
- TERMINATED
The difference between wait and sleep
From different classes
-
wait comes from the Object class
-
sleep comes from the Thread class
-
The methods under the TimeUnit class (under the java.util.concurrent package) are used in the enterprise
About lock release
-
wait releases the lock
-
sleep does not release the lock
Different scope of use
- wait must be used in synchronous code blocks
- Unlimited sleep range
Lock (key)
Synchronized
package BXBF; public class CPU { public static void main(String[] args) { //Actual development needs to reduce coupling //Concurrency: multiple threads operate on the same resource class and throw the resource class into the thread Ticket ticket = new Ticket(); //lambda expressions new Thread(()-> { for (int i = 0; i < 101; i++) { ticket.sale(); } },"ash").start(); new Thread(()-> { for (int i = 0; i < 101; i++) { ticket.sale(); } },"black").start(); new Thread(()-> { for (int i = 0; i < 101; i++) { ticket.sale(); } },"white").start(); } } //Resource class OOP class Ticket{ //Properties, methods private int number=100; public synchronized void sale(){ if (number>0){ System.out.println(Thread.currentThread().getName()+"Bought the second"+number--+"Ticket"); } } }
Lock
Implementation class
- ReentrantLock (reusable lock (common))
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
Unfair lock: NonfairSync: unfair. You can jump the queue. The unfair lock is used by default
Fair lock: FairSync: very fair, first come, first served
-
ReentrantReadWriteLock.ReadLock (read lock)
-
ReentrantReadWriteLock.WriteLock (write lock)
method
- lock()
- unlock()
- tryLock(), trying to acquire a lock
package BXBF; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Ticketer { public static void main(String[] args) { Ticket1 ticket = new Ticket1(); new Thread(()-> { for (int i = 0; i < 101; i++) { ticket.sale(); } },"ash").start(); new Thread(()-> { for (int i = 0; i < 101; i++) { ticket.sale(); } },"black").start(); new Thread(()-> { for (int i = 0; i < 101; i++) { ticket.sale(); } },"white").start(); } } class Ticket1{ private int number=100; Lock lock=new ReentrantLock(); //Create lock public void sale(){ lock.lock(); //Lock try { if (number>0){ System.out.println(Thread.currentThread().getName()+"Bought the second"+number--+"Ticket"); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); //Unlock } } }
The difference between Synchronized and Lock
- Synchronized is a built-in java keyword; Lock is a java class
- Synchronized cannot judge the status of obtaining the lock; Lock can determine whether a lock is obtained
- Synchronized will automatically release the lock; Lock the lock must be released manually. If not released, a deadlock may occur
- Synchronized thread 1 obtains the lock, and thread 2 waits all the time, which is easy to block; Lock thread 1 obtains the lock, and thread 2 will not wait all the time
- Synchronized is a reentrant lock that cannot be interrupted and is not fair; Lock is reentrant and can judge whether the lock is unfair (can be set)
- Synchronized is suitable for locking a small number of code synchronization problems; Lock is suitable for locking a large number of code synchronization problems
Read write lock (ReadWriteLock)
- Read lock (writeLock()), shared lock
- Write lock (read()), exclusive lock
package BXBF; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteText { public static void main(String[] args) { MyRW myRW = new MyRW(); for (int i = 0; i < 5; i++) { final int i1=i; new Thread(()->{ myRW.Write(i1+"",i1+""); },String.valueOf(i)).start(); } for (int i = 0; i < 5; i++) { final int i2=i; new Thread(()->{ myRW.Read(i2+""); },String.valueOf(i)).start(); } } } class MyRW{ private volatile Map<String,String> map=new HashMap<>(); private ReentrantReadWriteLock lock=new ReentrantReadWriteLock(); public void Write(String key,String value){ lock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName()+"Start writing"); map.put(key,value); System.out.println(Thread.currentThread().getName()+"Finished"); } catch (Exception e) { e.printStackTrace(); } finally { lock.writeLock().unlock(); } } //Dirty reads may occur without a read lock public void Read(String key){ lock.readLock().lock(); try { System.out.println(Thread.currentThread().getName()+"Start reading"); String s = map.get(key); System.out.println(Thread.currentThread().getName()+"Finished reading"); } catch (Exception e) { e.printStackTrace(); } finally { lock.readLock().unlock(); } } }
Producer and consumer issues
Synchronized solution
method
- Wait
- notifyAll() wakes up all threads
package BXBF; public class PC { public static void main(String[] args) { Product product = new Product(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { product.add(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { product.subtract(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); } } class Product{ private int number=0; public synchronized void add() throws InterruptedException { if (number!=0){ this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"---->"+number); this.notifyAll(); } public synchronized void subtract() throws InterruptedException { if (number==0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"---->"+number); this.notifyAll(); } }
When the producer and consumer are each one, thread safety
When producers and consumers are not for a while, in order to prevent false awakening, waiting should always appear in the cycle
package BXBF; public class PC { public static void main(String[] args) { Product product = new Product(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { product.add(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { product.add(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { product.subtract(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { product.subtract(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } class Product{ private int number=0; public synchronized void add() throws InterruptedException { while (number!=0){ this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"---->"+number); this.notifyAll(); } public synchronized void subtract() throws InterruptedException { while (number==0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"---->"+number); this.notifyAll(); } }
Problem: threads are out of order and execute randomly
Lock resolution
Condition replaces the use of object monitor methods
Function: accurately notify and wake up threads to solve the problem of disordered and random execution of Synchronized threads
method
- await() wait
- signalAll() wakes up all threads
package BXBF; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditPCL { public static void main(String[] args) { Productions product = new Productions(); new Thread(()->{ for (int i = 0; i < 10; i++) { product.A(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { product.B(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { product.C(); } },"C").start(); } } class Productions{ private int number=1; Lock lock=new ReentrantLock(); Condition condition1=lock.newCondition(); Condition condition2=lock.newCondition(); Condition condition3=lock.newCondition(); public void A(){ lock.lock(); try { while (number!=1){ condition1.await(); } System.out.println(Thread.currentThread().getName()+"---->"+number); number=2; condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void B(){ lock.lock(); try { while (number!=2){ condition2.await(); } System.out.println(Thread.currentThread().getName()+"---->"+number); number=3; condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void C(){ lock.lock(); try { while (number!=3){ condition3.await(); } System.out.println(Thread.currentThread().getName()+"---->"+number); number=1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
6 questions about lock execution
Question 1
After the following code is executed, output 1 or output 2 first?
package BXBF;import java.util.concurrent.TimeUnit;public class EightS { public static void main(String[] args){ Cards cards = new Cards(); new Thread(()->{ cards.C1(); },"1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ cards.C2(); },"2").start(); }}class Cards{ public synchronized void C1(){ System.out.println("1"); } public synchronized void C2(){ System.out.println("2"); }}
A: output 1 first
Reason: the object of the synchronized lock is the caller (object) of the method
The two methods use the same lock. Who gets it first and who executes it. In the code, thread 2 waits for one second, so thread 1 obtains the lock first and outputs 1 first
Question 2
After the following code is executed, output 1 or output 2 first?
package BXBF;import java.util.concurrent.TimeUnit;public class EightS { public static void main(String[] args){ Cards cards = new Cards(); new Thread(()->{ cards.C1(); },"1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ cards.C2(); },"2").start(); }}class Cards{ public synchronized void C1(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1"); } public synchronized void C2(){ System.out.println("2"); }}
A: output 1 first
Reason: in method C1, thread 1 has obtained the lock before resting for 4 seconds, and sleep will not release the lock, so output 1 first
Question 3
After the following code is executed, output 1 first or output 3 first?
package BXBF;import java.util.concurrent.TimeUnit;public class EightS { public static void main(String[] args){ Cards cards = new Cards(); new Thread(()->{ cards.C1(); },"1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ cards.C3(); },"2").start(); }}class Cards{ public synchronized void C1(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1"); } public synchronized void C2(){ System.out.println("2"); } public void C3(){ System.out.println("3"); }}
A: output 3 first
Reason: Although thread 1 has obtained the lock, C3 has no lock, is not a synchronization method, and is not affected by the lock. After 1 second of rest, thread 2 executes and outputs 3. At this time, thread 1 is still holding the lock for rest
Question 4
After the following code is executed, output 1 or output 2 first?
package BXBF; import java.util.concurrent.TimeUnit; public class EightS { public static void main(String[] args){ Cards cards = new Cards(); Cards cards1 = new Cards(); new Thread(()->{ cards.C1(); },"1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ cards1.C2(); },"2").start(); } } class Cards{ public synchronized void C1(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1"); } public synchronized void C2(){ System.out.println("2"); } }
A: output 2 first
Reason: two different objects have two locks. Thread 1 holds the lock of object 1 and rests for 4 s. thread 2 obtains the lock of object 2 after resting for 1 s. therefore, thread 2 executes first and outputs 2 first
Question 5
After the following code is executed, output 1 or output 2 first?
package BXBF; import java.util.concurrent.TimeUnit; public class EightS { public static void main(String[] args){ Cards cards = new Cards(); Cards cards1 = new Cards(); new Thread(()->{ cards.C1(); },"1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ cards1.C2(); },"2").start(); } } class Cards{ public static synchronized void C1(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1"); } public static synchronized void C2(){ System.out.println("2"); } }
A: output 1 first
Reason: the static static method is available as soon as the Class is loaded. The Class template (the only Class object) is locked here. No matter how many objects of this Class are created, there is only one lock. Thread 1 grabs the lock first, so thread 1 is executed first and output 1 first
Question 6
After the following code is executed, output 1 or output 2 first?
package BXBF; import java.util.concurrent.TimeUnit; public class EightS { public static void main(String[] args){ Cards cards = new Cards(); new Thread(()->{ cards.C1(); },"1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ cards.C2(); },"2").start(); } } class Cards{ public static synchronized void C1(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1"); } public synchronized void C2(){ System.out.println("2"); } }
A: output 2 first
Reason: there are two methods in the code. One is a static synchronization method, and the lock is a Class template; One is a common synchronization method, which locks the caller (object) of the Class. The locks of the two methods are different and do not need to compete. Thread 1 holds the lock of the Class template and rests for 4 s econds. Thread 2 obtains the lock of the object after resting for 1 second. Therefore, thread 2 executes first and outputs 2 first
Collection unsafe
List
package BXBF; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.UUID; public class ListText { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 1; i <= 10; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } }
- Concurrent modificationexception will be reported
- And sending ArrrayList is not safe
resolvent
-
List<String> list = new Vector<>();
Not recommended, low efficiency
-
List<String> list = Collections.synchronizedList(new ArrayList<>());
-
List<String> list = new CopyOnWriteArrayList<>();
efficient
CopyOnWrite copy on write, COW idea, an optimization strategy in the field of computer design
The idea of copy on write (COW) is a general optimization strategy in the field of computer programming. The core idea is that if multiple Callers access the same resource (such as memory or data storage on disk) at the same time, they will jointly obtain the same pointer to the same resource. The system will not really copy a private copy to the caller until a caller modifies the content of the resource, The original resources seen by other Callers remain unchanged. This process is transparent to other Callers. The main advantage of this method is that if the caller does not modify the resource, no private copy will be created. Therefore, multiple Callers can share the same resource only during the read operation. When the caller adds an element to a resource, he does not directly operate the current resource, but modifies the special copy copied by the system. After modification, he points the reference of the original resource to the new resource, that is, the whole modification operation is completed;
Set
package BXBF; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; public class ListText { public static void main(String[] args) { Set<String> set = new HashSet<>(); for (int i = 1; i <= 20; i++) { new Thread(()->{ set.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(set); },String.valueOf(i)).start(); } } }
- Concurrent modificationexception will be reported
- HashSet is not safe under concurrency
resolvent
-
Set<String> set = Collections.synchronizedSet(new HashSet<>());
-
Set<String> set = new CopyOnWriteArraySet<>();
HashSet bottom
- HashMap
public HashSet() { map = new HashMap<>();}
- add method of HashSet
public boolean add(E e) { return map.put(e, PRESENT)==null; //PRESENT is a constant}
- The essence of Set is the key of Map. The key cannot be repeated, so Set is out of order
Map
- HashMap is not applicable at work
- Default equivalent: new HashMap < > (16,0.75)
package BXBF; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; //Concurrent modificationexception concurrent modification exception public class ListText { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); for (int i = 1; i <= 20; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } } }
- Concurrent modificationexception will be reported
- HashMap is not safe under concurrency
resolvent
-
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
-
Map<String, String> map = new ConcurrentHashMap<>();
Callable
- The third way to create multithreading
- Can have return value
- Exceptions can be thrown
- Different methods need to be rewritten (call())
@FunctionalInterface public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
- The parameter of the generic is equal to the return value of the method
package BXBF; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class callable { //Exceptions can be thrown public static void main(String[] args) throws ExecutionException, InterruptedException { Mycallable mycallable = new Mycallable(); FutureTask futureTask = new FutureTask(mycallable); new Thread(futureTask).start(); String str = (String) futureTask.get(); //The return value can be obtained. This method may cause blocking. Generally, it is put at the end or processed by asynchronous communication System.out.println(str); } } class Mycallable implements Callable<String> { @Override public String call() { System.out.println("call"); return "145236"; } }
-
Thread can only be started through new Thread().start();
-
However, Callable cannot pass new Thread().start(); Direct opening
Method opened by Callable thread class
- It is known that only the Runnable interface can pass new Thread().start(); Directly start the thread, that is, new Thread(new Runnable()).start();
- The implementation of the Runnable interface is used to start the thread, that is, new Thread(new FutureTask()).start();
- FutureTask class has a constructor with Callable type parameters, which can call Callable, that is, new Thread(new FutureTask(Callable)).start();
- Final completion thread start
-
When multiple FutureTask class objects open threads, they only return results once
When the JVM calls the thread held by the FutureTask object lock again, the start of FutureTask is no longer in the new state, and the corresponding thread will be ended directly. The task will not be executed. Only the results returned during the first call are saved, so the results will be returned only once
Common auxiliary classes
CountDownLatch
- Subtraction counter
method
-
countDown(), counter minus 1
-
await(), after the counter is set to 0, the program continues to execute downward
package BXBF; import java.util.concurrent.CountDownLatch; public class Count { public static void main(String[] args) throws InterruptedException { CountDownLatch cdl = new CountDownLatch(5); for (int i = 1; i <=5; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"The number is gone"); cdl.countDown(); },String.valueOf(i)).start(); } cdl.await(); System.out.println("close"); } }
CyclicBarrier
- Addition counter
method
- await(), when the number of counters reaches the specified value, blocks the current thread and executes the thread in the constructor
package BXBF; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class Cl { public static void main(String[] args) { CyclicBarrier cb = new CyclicBarrier(5, () -> { System.out.println("Open the door"); }); for (int i = 0; i < 5; i++) { //When you want to use i in the for loop in a lambda expression, you need to convert i as follows // final int ii=i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+"Here comes classmate No"); try { cb.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } } }
Semaphore
- Count semaphore
method
- acquire() to obtain the position. If the position is full, wait for the position to be released
- release(), release the current semaphore and wake up the waiting thread
package BXBF; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class Sp { public static void main(String[] args) { //Current limiting Semaphore sp = new Semaphore(4); //Only four threads can execute concurrently at a time for (int i = 1; i <= 8; i++) { new Thread(()->{ try { sp.acquire(); //Wait until position System.out.println(Thread.currentThread().getName()+"The number came in"); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName()+"The number is gone"); } catch (InterruptedException e) { e.printStackTrace(); } finally { sp.release(); //Release position } },String.valueOf(i)).start(); } } }
Function: mutually exclusive use of multiple shared resources, concurrent flow restriction, and control the maximum number of threads
Blocking queue
- If the queue is full, it needs to be blocked and wait for data extraction
- If the queue is empty, you need to block waiting for data to be written
- Four sets of API s
1. Exception thrown
- Add method: add()
- Remove method: remove()
- Get team leader data: element()
package BXBF; import java.util.concurrent.ArrayBlockingQueue; public class BQ { public static void main(String[] args) { test(); } public static void test(){ ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3); System.out.println(abq.add("145")); System.out.println(abq.add("258")); System.out.println(abq.add("198")); System.out.println(abq.add("198")); } }
The blocking queue size is defined as 3. When the number of written data is greater than 3, an error is reported: IllegalStateException: Queue full illegal status exception, and the queue is full
package BXBF; import java.util.concurrent.ArrayBlockingQueue; public class BQ { public static void main(String[] args) { test(); } public static void test(){ ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3); System.out.println(abq.add("145")); System.out.println(abq.add("258")); System.out.println(abq.add("198")); System.out.println(abq.remove()); System.out.println(abq.remove()); System.out.println(abq.remove()); System.out.println(abq.remove()); } }
The size of the blocking queue is 3. When the number of data fetched after writing 3 data is greater than the number of data written, an error is reported: NoSuchElementException element exception is not found
2. If there is a return value, no exception will be thrown
- Add method: offer()
- Removal method: poll()
- Get team leader data: peek()
package BXBF; import java.util.concurrent.ArrayBlockingQueue; public class BQ { public static void main(String[] args) { test(); } public static void test(){ ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3); System.out.println(abq.offer("145")); System.out.println(abq.offer("258")); System.out.println(abq.offer("198")); System.out.println(abq.offer("123")); } }
The blocking queue size is defined as 3. When the number of written data is greater than 3, no error will be reported, but false will be returned
package BXBF; import java.util.concurrent.ArrayBlockingQueue; public class BQ { public static void main(String[] args) { test(); } public static void test(){ ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3); System.out.println(abq.offer("145")); System.out.println(abq.offer("258")); System.out.println(abq.offer("198")); System.out.println(abq.poll()); System.out.println(abq.poll()); System.out.println(abq.poll()); System.out.println(abq.poll()); } }
The size of the blocking queue is 3. After three data are written, when the number of data taken is greater than the number of data written, no error will be reported, but null will be returned
3. Blocking waiting (waiting all the time)
- Add method: put()
- Remove method: take()
package BXBF; import java.util.concurrent.ArrayBlockingQueue; public class BQ { public static void main(String[] args) throws InterruptedException { test(); } public static void test() throws InterruptedException { ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3); abq.put("145"); abq.put("258"); abq.put("198"); abq.put("18"); } }
The blocking queue size is defined as 3. When the number of written data is greater than 3, the program blocks until there is a vacancy in the queue to write data
package BXBF; import java.util.concurrent.ArrayBlockingQueue; public class BQ { public static void main(String[] args) throws InterruptedException { test(); } public static void test() throws InterruptedException { ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3); abq.put("145"); abq.put("258"); abq.put("198"); System.out.println(abq.take()); System.out.println(abq.take()); System.out.println(abq.take()); System.out.println(abq.take()); } }
The size of the blocking queue is 3. After three data are written, when the number of data taken is greater than the number of data written, the program blocks until new data is written in the queue
4. Timeout wait (timeout exit)
- Add method: offer(), with parameters
- Removal method: poll(), with parameters
package BXBF; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; public class BQ { public static void main(String[] args) throws InterruptedException { test(); } public static void test() throws InterruptedException { ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3); System.out.println(abq.offer("145")); System.out.println(abq.offer("258")); System.out.println(abq.offer("198")); System.out.println(abq.offer("11", 1, TimeUnit.SECONDS)); } }
The size of the blocking queue is defined as 3. When the number of written data is greater than 3, the program blocks. After waiting for the specified time, the location is obtained and written. Otherwise, false is returned
package BXBF; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; public class BQ { public static void main(String[] args) throws InterruptedException { test(); } public static void test() throws InterruptedException { ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3); System.out.println(abq.offer("145")); System.out.println(abq.offer("258")); System.out.println(abq.offer("198")); System.out.println(abq.poll()); System.out.println(abq.poll()); System.out.println(abq.poll()); System.out.println(abq.poll(1, TimeUnit.SECONDS)); } }
The size of the blocking queue is 3. When the number of data fetched after writing 3 data is greater than the number of data written, the program will block. After waiting for the specified time, if there is new data written, it will be removed, otherwise null will be returned
Synchronous queue
-
No capacity
When you enter an element, you must wait for it to be taken out before you can put another element in it
-
Add method: put()
-
Remove method: take()
package BXBF; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; public class BQ { public static void main(String[] args) throws InterruptedException { SynchronousQueue<String> s = new SynchronousQueue<>(); new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+"Data written"); //Printing will preempt cpu resources first s.put("123"); System.out.println(Thread.currentThread().getName()+"Data written"); s.put("123"); System.out.println(Thread.currentThread().getName()+"Data written"); s.put("123"); } catch (InterruptedException e) { e.printStackTrace(); } },"1").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName()+"Data removed"+s.take()); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName()+"Data removed"+s.take()); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName()+"Data removed"+s.take()); } catch (InterruptedException e) { e.printStackTrace(); } },"2").start(); } }
Thread pool (key)
- Thread pools are typically customized using ThreadPoolExecutor()
- Executors are not secure
Pool technology
- Optimize the use of resources
- Prepare some resources in advance, use them when needed, and return them after use to avoid multiple creation and destruction
- Benefits: reduce resource consumption, improve response speed, facilitate management - > thread reuse, control the maximum number of concurrent threads, and manage threads
- Thread pool, connection pool, memory pool, object pool
Three methods to implement thread pool
newSingleThreadExecutor()
- Create a thread pool of size one
package BXBF; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Ex { public static void main(String[] args) { ExecutorService es = Executors.newSingleThreadExecutor(); //Create a thread pool of size one try { for (int i = 0; i < 10; i++) { es.execute(()->{ //Open thread pool open thread System.out.println(Thread.currentThread().getName()); }); } } catch (Exception e) { e.printStackTrace(); } finally { es.shutdown(); //Close thread pool } } }
newFixedThreadPoo()
- Create a fixed size thread pool
package BXBF; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Ex { public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(5); //Create a fixed size thread pool try { for (int i = 0; i < 10; i++) { es.execute(()->{ System.out.println(Thread.currentThread().getName()); }); } } catch (Exception e) { e.printStackTrace(); } finally { es.shutdown(); } } }
newCachedThreadPoo()
- Create a variable size thread pool
package BXBF; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Ex { public static void main(String[] args) { ExecutorService es = Executors.newCachedThreadPool(); //Create a variable size thread pool try { for (int i = 0; i < 10; i++) { es.execute(()->{ System.out.println(Thread.currentThread().getName()); }); } } catch (Exception e) { e.printStackTrace(); } finally { es.shutdown(); } } }
7 major parameters
- It is known from the source code that the three methods newsinglethreadexecution(), newFixedThreadPoo(), newCachedThreadPoo() essentially call the ThreadPoolExecutor() method
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
Seven parameters in ThreadPoolExecutor() method
int corePoolSize
- The size of the core thread pool and the initial capacity of the thread pool
int maximumPoolSize
- The maximum core thread pool size. The maximum capacity that the thread pool can be opened when the BlockingQueue workQueue is full
long keepAliveTime
- If it is not called after the specified time, the thread pool capacity will be initialized
TimeUnit unit
- Units of time beyond the specified time
BlockingQueue workQueue
- For blocking queues, you need to set the size of the blocking queue. When the upper limit is exceeded, the thread pool expansion will be triggered
ThreadFactory threadFactory
- Thread factory, create threads, generally do not move
RejectedExecutionHandler handler
- Solution strategy
Four solutions
AbortPolicy
- When the thread pool reaches the maximum capacity and the blocking queue reaches the upper limit, data enters, refuses to handle and throws an exception
package BXBF; import java.util.concurrent.*; public class Ex { public static void main(String[] args) { ThreadPoolExecutor tpe = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); try { for (int i = 1; i <= 9; i++) { tpe.execute(() -> { System.out.println(Thread.currentThread().getName()); }); } } catch (Exception e) { e.printStackTrace(); } finally { tpe.shutdown(); } } }
CallerRunsPolicy
- When the thread pool reaches the maximum capacity and the blocking queue reaches the upper limit, data will enter and be handed over to the thread calling the data for execution
package BXBF; import java.util.concurrent.*; public class Ex { public static void main(String[] args) { ThreadPoolExecutor tpe = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); try { for (int i = 1; i <= 9; i++) { tpe.execute(() -> { System.out.println(Thread.currentThread().getName()); }); } } catch (Exception e) { e.printStackTrace(); } finally { tpe.shutdown(); } } }
DiscardOldestPolicy
- When the thread pool reaches the maximum capacity and the blocking queue reaches the upper limit, new data enters. Remove the oldest data and try to enter again
package BXBF; import java.util.concurrent.*; public class Ex { public static void main(String[] args) { ThreadPoolExecutor tpe = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); try { for (int i = 1; i <= 9; i++) { tpe.execute(() -> { System.out.println(Thread.currentThread().getName()); }); } } catch (Exception e) { e.printStackTrace(); } finally { tpe.shutdown(); } } }
DiscardPolicy
- When the thread pool reaches the maximum capacity and the blocking queue reaches the upper limit, data still enters, and processing is rejected without throwing exceptions
package BXBF; import java.util.concurrent.*; public class Ex { public static void main(String[] args) { ThreadPoolExecutor tpe = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy()); try { for (int i = 1; i <= 9; i++) { tpe.execute(() -> { System.out.println(Thread.currentThread().getName()); }); } } catch (Exception e) { e.printStackTrace(); } finally { tpe.shutdown(); } } }
How is the maximum capacity of a thread pool defined
CPU intensive
- The computer has several threads, which can ensure the highest CPU efficiency
ThreadPoolExecutor tpe = new ThreadPoolExecutor( 2, Runtime.getRuntime().availableProcessors(), //Gets the number of threads on the current computer 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
IO intensive
- Determine the number of threads consuming IO in the program, and define the maximum capacity of the thread pool as twice the number of threads
Four functional interfaces (key points)
- Interface with only one method
@FunctionalInterface //Functional interface public interface Runnable { public abstract void run(); }}
- It can simplify the programming model and be widely used at the bottom of the new version of the framework
Function
- Functional interface
public interface Function<T, R> { //Pass in parameter T and return type R R apply(T t); }
package BXBF; import java.util.function.Function; public class Funct { public static void main(String[] args) { Function f=new Function<String,String>() { @Override public String apply(String s) { return s; } }; System.out.println(f.apply("147")); } }
lambda expression simplification
package BXBF; import java.util.function.Function; public class Funct { public static void main(String[] args) { Function f=(s)->{return s;}; System.out.println(f.apply("147")); } }
Predicate
- Deterministic interface
public interface Predicate<T> { //Pass in the parameter T and return the boolean type boolean test(T t); }
package BXBF; import java.util.function.Predicate; public class Pr { public static void main(String[] args) { Predicate<String> p=new Predicate<String>() { @Override public boolean test(String s) { return s.isEmpty(); } }; System.out.println(p.test("")); } }
lambda expression simplification
package BXBF; import java.util.function.Predicate; public class Pr { public static void main(String[] args) { Predicate<String> p=(s)-> {return s.isEmpty();}; System.out.println(p.test("")); } }
Consumer
- Consumer interface
public interface Consumer<T> { //Passed in parameter T, no return value void accept(T t); }
package BXBF; import java.util.function.Consumer; public class Co { public static void main(String[] args) { Consumer<String> c= new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; c.accept("147"); } }
lambda expression simplification
package BXBF; import java.util.function.Consumer; public class Co { public static void main(String[] args) { Consumer<String> c=(s)-> {System.out.println(s);}; c.accept("147"); } }
Supplier
- Supply type interface
public interface Supplier<T> { //No parameters, only return values T get(); }
package BXBF; import java.util.function.Supplier; public class Spl { public static void main(String[] args) { Supplier s=new Supplier<String>() { @Override public String get() { return "14526"; } }; System.out.println(s.get()); } }
lambda expression simplification
package BXBF; import java.util.function.Supplier; public class Spl { public static void main(String[] args) { Supplier s=()-> {return "14526";}; System.out.println(s.get()); } }
Stream flow calculation
- Big data: storage + computing
- Collection, database used to store
- Flow is used to calculate
method
- Filter: filter()
- Convert: map()
- Comparison: sorted()
- Specified number of outputs: limit()
- Printing: forEach()
- Add data of this interval (open interval): range()
- Add data of this interval (front opening and rear closing): rangeClosed()
- Parallel computing: parallel()
Small example
Do the following for four users
- Only those with even ID s are reserved
- Only those older than 17 are retained
- Convert user name to uppercase
- Sort user names upside down
- Output only one user
package BXBF; import java.util.Arrays; import java.util.List; import java.util.Locale; public class UUSER { public static void main(String[] args) { User user = new User("a",1,12); User user1 = new User("b",2,18); User user2 = new User("c",3,1); User user3 = new User("d",4,28); List<User> list = Arrays.asList(user, user1, user2, user3); list.stream() .filter(u->{return u.getId()%2==0;}) .filter(u->{return u.getAge()>17;}) .map(u->{u.setName(u.getName().toUpperCase());return u;}) .sorted((u1,u2)->{return u2.getName().compareTo(u1.getName());}) .limit(1) .forEach(System.out::println); } }
ForkJoin
- Execute tasks in parallel and use them when the amount of data is particularly large to improve efficiency
- Principle, the large task is continuously divided into small tasks, and then the small tasks are executed to obtain the results. The results are combined upward at one time to finally obtain the results of the large task
- It maintains double ended queues
- Features: work stealing. When a thread completes its own work, it will help other threads complete the unfinished work, starting from the end of the team
How to use FrokJoin
-
Execute through ForkJoinPool
Related methods: execute()
Submit task: submit()
-
Create a new calculation task, ForkJoinPool.execute(ForkJoinTask task)
ForkJoinTask has two implementation classes: RecursiveAction (recursive event with no return value) and recursivetask (recursive task with return value)
-
The calculation class inherits the Resursivetask implementation class
Method of implementing class of Resursivetask
- Split the task and push the task into the thread queue: fork()
- Get execution result: join()
Small example: addition of a large amount of data
package BXBF; import java.util.concurrent.RecursiveTask; public class ForkJ extends RecursiveTask<Long> { private Long start; private Long end; private Long middle=10000L; public ForkJ(long start, long end) { this.start = start; this.end = end; } @Override protected Long compute() { if ((end-start)<middle){ Long sum=0L; for (Long i = start; i <= end; i++) { sum+=i; } return sum; }else { long conter=(start+end)/2; ForkJ forkJ1 = new ForkJ(start, conter); forkJ1.fork(); ForkJ forkJ2 = new ForkJ(conter+1, end); forkJ2.fork(); return forkJ1.join()+forkJ2.join(); } } }
package BXBF; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.stream.LongStream; public class FJtext { public static void main(String[] args) throws ExecutionException, InterruptedException { test1(); test2(); test3(); } //Direct calculation public static void test1(){ Long sum=0L; long start = System.currentTimeMillis(); for (Long i = 0L; i <= 1_0000_0000L; i++) { sum+=i; } long end = System.currentTimeMillis(); System.out.println("The result is:"+sum+",Time:"+(end-start)); } //ForkJoin calculation public static void test2() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); ForkJoinPool fjp = new ForkJoinPool(); ForkJoinTask<Long> fj = new ForkJ(0L, 1_0000_0000L); ForkJoinTask<Long> submit = fjp.submit(fj); Long sum=submit.get(); long end = System.currentTimeMillis(); System.out.println("The result is:"+sum+",Time:"+(end-start)); } //Stream flow calculation public static void test3(){ long start = System.currentTimeMillis(); Long sum = LongStream.rangeClosed(0L, 1_0000_0000L).parallel().reduce(0, Long::sum); long end = System.currentTimeMillis(); System.out.println("The result is:"+sum+",Time:"+(end-start)); } }
Efficiency: Stream flow calculation > forkjoin calculation > direct calculation
ForkJoin can improve efficiency by changing the threshold
JMM
- The Java memory model, which does not exist, is a convention
Some synchronization conventions about JMM
- Thread before locking
- The latest value in main memory must be read into the working memory of the thread
- Before thread unlocking
- When a thread needs to use a variable in main memory, it will not directly operate on the main memory variable, but will copy it to the thread's own working memory. The thread actually operates the replica in its own working memory. Before unlocking, the modified replica must be refreshed back to main memory immediately to replace the main memory variable
- Locking and unlocking must be the same lock
8 operations
- There are eight operations for data exchange between memory and working memory in JMM specification, and each operation is atomic
Lock: a variable that acts on main memory. Only one thread can lock a variable at a time. This operation represents a variable that is exclusively locked by this thread
unlock: a variable that acts on the main memory, indicating that the state of the variable is released from the locked state so that other threads can lock the variable
read: acts on the main memory variable, which means to transfer the value of a main memory variable to the working memory of the thread for subsequent load operations
load: a variable that acts on the working memory of a thread, indicating that the value of the variable read from the main memory by the read operation is placed in the variable copy of the working memory
use: a variable in the working memory that acts on a thread, indicating that the value of a variable in the working memory is passed to the execution engine
assign: a variable that acts on the working memory of a thread, indicating that the result returned by the execution engine is assigned to a variable in the working memory
store: a variable in the working memory that acts on the thread. It passes the value of a variable in the working memory to the main memory for subsequent write operations
write: a variable that acts on the main memory and puts the value of the variable obtained from the working memory by the store operation into the variable of the main memory
Volatile
-
Java is a built-in keyword
-
Lightweight synchronization mechanism provided by Java virtual machine
Three functions
Ensure visibility
package BXBF; import java.util.concurrent.TimeUnit; public class TextJ { private static int i=0; public static void main(String[] args) throws InterruptedException { new Thread(()->{ while (i==0){ } },"1").start(); TimeUnit.SECONDS.sleep(1); i=1; System.out.println(i); } }
In the above case, the i value in main memory has changed, but thread 1 is invisible to the change in main memory, and the program keeps cycling
Modify the variable with Volatile to make thread 1 visible to the changes of main memory
package BXBF; import java.util.concurrent.TimeUnit; public class TextJ { private volatile static int i=0; public static void main(String[] args) throws InterruptedException { new Thread(()->{ while (i==0){ } },"1").start(); TimeUnit.SECONDS.sleep(1); i=1; System.out.println(i); } }
Atomicity is not guaranteed
- Atomicity: indivisible
- When executing tasks, threads cannot be disturbed or divided. They either succeed or fail at the same time
How to ensure atomicity without using synchronized and Lock
Use Atomic atomic classes to solve atomicity problems
package BXBF; import java.util.concurrent.atomic.AtomicInteger; public class At { // private static int i=0; private static AtomicInteger i=new AtomicInteger(); public static void add(){ // i++; // Nonatomic operation i.getAndIncrement(); //The + 1 method in AtomicInteger class uses CAS, which is very efficient } public static void main(String[] args) { for (int i1 = 0; i1 < 10; i1++) { new Thread(()->{ for (int i2 = 0; i2 < 1000; i2++) { add(); } }).start(); } while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(i); } }
Avoid instruction rearrangement
- Instruction rearrangement: the computer does not execute according to the program you write
- Code execution process
- Source code - > compiler optimization rearrangement - > instruction parallelism may also rearrange - > memory system rearrangement - > execution
- When the processor rearranges instructions, it will consider the dependency between data
- Memory barrier (CPU instruction), most used in singleton mode
- effect
- Ensure the execution sequence of specific operations
- Memory visibility of some variables can be guaranteed (visibility is achieved by volatile using these features)
- effect
- When volatile is added to the target of an operation in a thread, a memory barrier will be added before and after the execution of the thread when it is executed by the CPU
Singleton mode
- The constructor is private to ensure that the object is unique
- Reflection can break singleton mode
Hungry Han style single case
package BXBF; public class Hungry { //The contents of this class will be loaded at the beginning of the program, which may cause a waste of space private byte[] a1=new byte[1024*1024]; private byte[] a2=new byte[1024*1024]; private byte[] a3=new byte[1024*1024]; private byte[] a4=new byte[1024*1024]; private Hungry() { } private final static Hungry Hungry=new Hungry(); //Guarantee object uniqueness public static Hungry getInstance(){ return Hungry; } }
Lazy single case
package BXBF; public class Lazy { private Lazy() { System.out.println(Thread.currentThread().getName()); } private volatile static Lazy lazy; //Dual detection lock mode, DCL lazy private static Lazy getInstance(){ if (lazy==null){ synchronized (Lazy.class){ if (lazy == null) { lazy = new Lazy(); //Not an atomic operation /**Execution sequence * 1.Allocate memory space * 2.Execute the construction method to initialize the object * 3.Point the object to this space * It is possible that the instruction rearrangement order is changed to 132, that is, the thread has not been initialized but occupies the space in advance * When a new thread enters, it will judge lazy= Null, which returns lazy directly * Therefore, variables must be modified with volatile to avoid instruction rearrangement*/ } } } return lazy; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ lazy.getInstance(); }).start(); } } }
Anonymous Inner Class
package BXBF; public class ClassR { private ClassR(){ } public static ClassR getInstance(){ return ClassROO.Classr; } public static class ClassROO{ private static final ClassR Classr=new ClassR(); } }
Singleton mode is not safe and can be broken by reflection
enumeration
- It is also a Class
- There is no parameterless construction, only parameterless construction (including two parameters)
- Safe, unable to be destroyed by launch
- Is the best way to use singleton mode
CAS
- compareAndSet (compare and exchange)
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
When the first parameter is equal to the initial value, update the initial value to the second parameter. Otherwise, it will not be updated and will continue to cycle
- Java cannot manipulate memory
- Java can call C through the native method++
- C + + can manipulate memory
- Java can manipulate memory through the unsafe class
- Disadvantages:
- The cycle takes time
- One time can only guarantee the atomicity of one shared variable
- There will be ABA problems