Analysis of Java interview questions

1. volatile

  1. Memory visibility: variables in the main memory will be copied to the thread's private memory when used in a multithreaded environment. Variables modified by volatile keyword can ensure the consistency of the same variables in the main memory and the thread's private memory.
  2. No guarantee of atomicity: variables modified by volatile keyword can guarantee the atomicity of a single operation (instruction rearrangement is prohibited), but cannot guarantee the atomicity of multiple operations (such as + + or –).
  3. Prohibit instruction rearrangement: byte code instructions compiled by the JVM are not necessarily executed according to the coding order, but variables modified with volatile keyword will prohibit byte code instruction rearrangement during operation.

2. CAS(CompareAndSet)

CompareAndSet is a native method of Unsafe class. In rt.jar, the native modified method can directly operate specific memory like a pointer in C language.
CAS is a concurrent primitive of CPU (the execution of primitive must be continuous and the execution process cannot be interrupted)

public class AtomicInteger extends Number implements java.io.Serializable {
    private volatile int value;
    public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }

CompareAndSet is used to obtain the variable value in the current main memory as the thread's private memory and compare it with the expected value expect. If it is equal, update the value to update and return true. Otherwise, do not update and return false.
Disadvantages of CompareAndSet:

  1. If the comparison results are not equal, it will keep trying, which may bring great overhead to the CPU.
  2. Only atomic operations of one shared variable can be guaranteed.

3. ABA

ABA problem refers to that among two or more threads with large execution frequency difference, the thread with high execution frequency modifies the value of main memory for many times without the knowledge of other threads, and finally modifies it to the original value, resulting in other threads thinking that the value in main memory has not changed.
ABA problem will be found in programs that pay attention to the execution process. For programs that only pay attention to the call results, this problem can be ignored.

// ABA problem demonstration and solution
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(100, 1);
        new Thread(() -> {
            boolean res = reference.compareAndSet(100, 101, reference.getStamp(), reference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t" + res + "\t" + reference.getStamp() + "\t" + reference.getReference());
            res = reference.compareAndSet(101, 100, reference.getStamp(), reference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t" + res + "\t" + reference.getStamp() + "\t" + reference.getReference());
        }, "t1").start();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean res = reference.compareAndSet(100, 101, 1, reference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t" + res + "\t" + reference.getStamp() + "\t" + reference.getReference());
        }, "t2").start();

Solution: atomic reference
Atomic reference atomicreference < V > can encapsulate a java class as an atomic object. Using this feature to combine timestamp with atomic reference can solve the ABA problem. The encapsulated timestamp class atomicstampedreference < V > has been provided in java.

4. Thread safety of collection class

Example of ArrayList concurrency exception

ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
    new Thread(()-> {
        list.add(UUID.randomUUID().toString().substring(0, 8));
        System.out.println(list);
    }, "t" + i).start();
}

Exception Name: Java util. ConcurrentModificationException

Solution:

  1. Using vector < E >: the operation of vector < E > will be locked to ensure data consistency.
  2. Collections. Synchronizedlist (New ArrayList < > () encapsulates thread unsafe collections into thread safe collections.
  3. Use copyonwritearraylist < E > to separate reading and writing.

CopyOnWriteArray source code

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

When you write to a set, you will lock it first, copy the current set, write to the copied set, and finally overwrite the written set with the original set, and then unlock it.

5. Pointer reference

class Person() {
    public int age;
    public String name;
    public void setAge(int age) {
        age = 30;
    }
    public void setName(Person person) {
        person.name = "xxx";
    }
    public void setName(String name) {
        name = "str";
    }
}

Demo case 1 = 3

// Case 1: the setage method changes the value of the formal parameter, which is a copy of the argument variable. Therefore, changing the value of the copy does not affect the value of the variable itself
Person person = new Person("test", 10);
int age = 20;
person.setAge(age);
System.out.println("age=" + age);
// Result: age=20
// Case 2: at present, there are two Person pointers pointing to (test,10) and (abc,10) respectively. In the setName method, the name value of the Person object pointed to by the person1 pointer is changed to "xxx", so the value is really changed
Person person1 = new Person("abc", 10);
person.setName(person1)
System.out.println("name=" + person1.name);
// Result: name=xxx
// Case 3: at present, there are two String pointers pointing to XXX (argument and formal parameter) at the same time. The formal parameter in setName method points to str, and the actual parameter has not changed, so str=xxx
String str = "xxx";
person.setName(str);
System.out.println("name=" + str);
// Result: name=xxx

6. Thread lock

6.1. Fair lock / unfair lock

// The default is reentran lock
public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

Fair lock means that multiple threads acquire locks according to the order of applying for locks, and abide by the principle of first come, first served; Instead of fair locks, it is possible to apply first and then obtain the lock. The advantage of unfair lock is that the throughput is relatively large.
Note: synchronized is a non fair lock.

6.2. Reentrant lock (recursive lock)

When there are nested locks in the code, the same thread will automatically acquire the lock when entering the internal code after the external code acquires the lock.
synchronized and ReentrantLock are typical reentrant locks

6.3. Spin lock

When the thread that obtains the lock fails, it will not block immediately, but will try to obtain the lock in a circular way

AtomicReference<Thread> reference = new AtomicReference<>();
// Lock with spin lock
public void lock () {
    Thread thread = Thread.currentThread();
    System.out.println(Thread.currentThread().getName() + "\t come in (+_+)?");
    while (!reference.compareAndSet(null, thread)) {
        //TODO: operation after obtaining lock
    }
}
// Unlock spin lock
public void unlock () {
    Thread thread = Thread.currentThread();
    reference.compareAndSet(thread, null);
}

6.4. Exclusive lock (write lock) / shared lock (read lock) / mutex lock

ReentrantLock and synchronized are exclusive locks
The principle of read-write lock lies in the separation of read and write. The atomicity of write operation should be guaranteed, and the operation process cannot be interrupted; Read operations can share locks with multiple threads.

ReentrantReadWriteLock lock = new ReentrantReadWriteLock()
// Write lock - lock
lock.writeLock().lock()
// Write lock unlock
lock.writeLock().unlock()
// Read lock - lock
lock.readLock().lock()
// Read lock unlock
lock.readLock().unlock()

6.4. Lock and Condition

When judging conditions in multithreading, you should use while instead of if, because while can judge whether the conditions are met again after the thread is awakened, and if will execute directly

// Create Condition through ReentrantLock
Condition condition = lock.newCondition();
  1. One ReentrantLock can create multiple conditions.
  2. When calling condition. On thread A Await() function can make the current thread in blocking state; When thread B calls condition The signal() function can wake up thread A.
  3. A Condition can be bound to multiple threads, using Condition Signalall() can wake up all threads in blocking state, if Condition. Is called Signal() will wake up one of them randomly.

7. CountDownLatch/CyclicBarrier/Semaphore

7.1. Countdownlatch (countdown)

Countdown lock: when CountDownLatch is initialized, it will specify the time from which to start the countdown. Every time the countDown() function is executed, it will reduce the value of the counter by one. When the countdown is 0, the blocking state of await() will end. Otherwise, it will wait for the countDown() function to execute until the count reaches 0.

int count = 5;
CountDownLatch latch = new CountDownLatch(count);
// Countdown thread
for (int i = 0; i < count; i++) {
    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "completion of enforcement...");
        latch.countDown();
    }, "t" + i).start();
}
latch.await();
System.out.println("Main thread execution...");

7.2. Cyclicbarrier (collector)

The initialization parameters of the collector contain two items: the first is the number of collections that meet the conditions, and the second is the threads that execute after the conditions are met.
When the await() function is invoked in the thread, the number of collection will be added. Then the collector will determine whether the current collection number satisfies the condition. When the number is satisfied, the thread in the initialization parameter will be executed.

int count = 5;
CyclicBarrier barrier = new CyclicBarrier(count, () -> {
    System.out.println("Collector execution...");
});
// Collect threads
for (int i = 0; i < count; i++) {
    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "collect");
        try {
            barrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }).start();
}

7.3. Semaphore (signal light)

When multiple threads preempt multiple resources, when the resources are insufficient to allocate to all threads, in order to ensure that all threads can allocate resources, a controller similar to a semaphore can be used to limit the flow. Semaphore's constructor needs to pass in a limited number of resources. This class will maintain the number of resources. When the thread calls the acquire() function, the number of resources will be reduced by one; When the thread calls the release() function, the number of resources is increased by one; When a thread requests a resource, it will be blocked if the number of resources is 0.

Semaphore semaphore = new Semaphore(3);
// Preempt thread
for (int i = 0; i < 6; i++) {
    new Thread(() -> {
    try {
        semaphore.acquire();
        System.out.println(Thread.currentThread().getName() + "\t Grab");
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName() + "\t leave");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        semaphore.release();
    }
    }).start();
}

8. Blocking queue

  1. ArrayBlockingQueue: a finite blocking queue composed of arrays
  2. LinkedBlockingQueue: a limited blocking queue composed of linked lists (the default size is Integer.MAX_VALUE)
  3. PriorityBlockingQueue: an unbounded blocking queue that supports priority
  4. DelayQueue: delay unbounded blocking queue supporting priority
  5. SynchronousQueue: a blocking queue that does not store elements, but a queue of a single element
  6. LinkedTransferQueue: an unbounded blocking queue composed of linked lists
  7. LinkedBlockingDeque: a bidirectional blocking queue composed of linked lists

    Exception group function

    BlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
    // add to
    System.out.println(queue.add("one"));
    System.out.println(queue.add("two"));
    // Throw an exception when the queue overflows: Java lang.IllegalStateException
    // System.out.println(queue.add("three"));
    // Gets the next element to be taken out
    System.out.println(queue.element());
    // delete
    System.out.println(queue.remove());
    System.out.println(queue.remove());
    // Exception thrown when deleting empty queue: Java util. NoSuchElementException
    System.out.println(queue.remove());

    Return bool value group

    BlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
    // add to
    // offer(e, time, unit): blocking time can be specified
    System.out.println(queue.offer("one"));
    System.out.println(queue.offer("two"));
    // Returns: false when the queue overflows
    System.out.println(queue.offer("three"));
    // Gets the next element to be taken out
    System.out.println(queue.peek());
    // delete
    // poll(time, unit): the blocking time can be specified
    System.out.println(queue.poll());
    System.out.println(queue.poll());
    // null when deleting an empty queue
    System.out.println(queue.poll());

    Blocking group

    BlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
    // add to
    queue.put("one");
    queue.put("two");
    // Block when queue overflows
    queue.put("three");
    queue.forEach(System.out::println);
    // delete
    System.out.println(queue.take());
    System.out.println(queue.take());
    // Blocking when deleting empty queues
    System.out.println(queue.take());

    SynchronousQueue

    BlockingQueue<String> queue = new SynchronousQueue<>();
    // add to
    queue.add("one");
    // Queue overflow throw exception: Java lang.IllegalStateException
    queue.add("two");
    // take out
    System.out.println(queue.take());

9. Thread pool

9.1. Thread pool 7 parameters

  1. corePoolSize: the number of resident core threads in the thread pool
  2. maximumPoolSize: the maximum number of threads that the thread pool can hold at the same time
  3. keepAliveTime: the survival time of redundant idle threads
  4. Unit: the time unit of keepalivetime
  5. workQueue: task queue. When all core threads are busy, the newly added thread will be put into the task queue; When the task queue overflows, the thread pool will open new threads until the number of threads reaches the maximum (when the task queue overflows, the newly added thread will preempt the thread execution in the task queue)
  6. threadFactory: factory that generates threads
  7. handler: reject policy when thread pool overflows

9.2. Reject strategy

  1. Abortpolicy (default): throw the RejectedExecutionException exception directly
  2. CallerRunsPolicy: returns the task to the caller without throwing an exception or discarding the task
  3. Discard oldest policy: discard the task that has been waiting for the longest time in the queue, then add the current task to the task queue and try to submit the task again
  4. DiscardPolicy: directly discard the task without throwing an exception

9.3. Creation of thread pool

The command in Alibaba Java development manual prohibits the creation of thread pool using the thread pool object returned by Executors:

// Create thread pool manually
int CPU_COUNT = Runtime.getRuntime().availableProcessors();
ExecutorService executorService = new ThreadPoolExecutor(
        CPU_COUNT / 2,
        CPU_COUNT * 2,
        1L,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<Runnable>(CPU_COUNT),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy()
);

How to determine a reasonable number of thread pools?

  1. CPU intensive business (large amount of computation and less IO operations): CPU cores + 1
  2. IO intensive (blocking coefficient: 0.8 ~ 0.9)
    1. CPU cores / (1 - blocking factor)
    2. Number of CPU cores * 2

10. Deadlock problem

Java deadlock demo

Deadlock problem solution

calong > jps -l
10540
11436 jdk.jcmd/sun.tools.jps.Jps
12508 club.calong.jvm.demo.DeadLock
5788 org.jetbrains.jps.cmdline.Launcher
calong > jstack 12508
Java stack information for the threads listed above:
===================================================
"t3":
        at club.calong.jvm.demo.SyncThread.run(DeadLock.java:22)
        - waiting to lock <0x000000076b577cf8> (a java.lang.Object)
        - locked <0x000000076b577d18> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
"t1":
        at club.calong.jvm.demo.SyncThread.run(DeadLock.java:22)
        - waiting to lock <0x000000076b577d08> (a java.lang.Object)
        - locked <0x000000076b577cf8> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
"t2":
        at club.calong.jvm.demo.SyncThread.run(DeadLock.java:22)
        - waiting to lock <0x000000076b577d18> (a java.lang.Object)
        - locked <0x000000076b577d08> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.

11. JVM parameters

  1. java -XX:+PrintFlagsInitial -version: view the default initial value of JVM parameters
  2. java -XX:+PrintFlagsFinal -version: view the modified JVM parameters and the modified values
  3. java -XX:+PrintFlagsFinal -XX: parameter = value T: modify JVM parameters
  4. jinfo -flags PID: View JVM parameters of Java process
  5. jinfo -flag parameter PID: view the specified JVM parameters of the Java process
  6. java -XX:+PrintCommandLineFlags: View Java runtime command line and specify JVM parameters

Common parameters:

  1. -XSS (- XX: threadsacksize): the stack size of a single thread. The default is 512~1024k
  2. -Xms(-XX:InitialHeapSize): the initial memory size, which is 1 / 64 of the physical memory by default
  3. -Xmx(-XX:MaxHeapSize): the maximum allocated memory, which is 1 / 4 of the physical memory by default
  4. -20: Metaspacesize: the size of the meta space, which is not in the virtual memory. The size is only limited by the local memory. The default size is 20.8M
  5. -20: Printgcdetails: check the CG running log and memory usage of Java thread
  6. -20: Survivorratio: set the space ratio of S0/S1 in the Cenozoic Eden
  7. -20: Newratio: set the proportion of heap memory structure of new generation and old generation
  8. -20: Maxtenuringthreshold: sets the maximum age of garbage

12. Citation issues

  1. Strong reference: Object obj = new Object();, No strong recycling will occur.
  2. Soft reference: softreference < Object > obj = new softreference < > (New object()), when the system memory is sufficient, it will not be recycled, and when it is insufficient, it will be recycled.
  3. Weak reference: WeakReference < Object > obj = new WeakReference < > (New object()), the object will be recycled as long as GC is run. In weakhashmap < T >, if you point the Key to null and run GC, the Key and Value will be recycled.
  4. Virtual reference: phantom reference < Object > obj = new phantom reference < > (New object()), which is a virtual reference object. This object must be used with the reference queue ReferenceQueue < T >.
    Weak and virtual reference objects will be put into the reference queue before being recycled by GC. The put objects can be obtained by using the reference queue method poll().

Keywords: Java

Added by jassikundi on Mon, 07 Mar 2022 21:25:09 +0200