java multithreading and high concurrency 3 -- Record learning

Atoms   --   Atomic Integer

public class T01_AtomicInteger {
	/*volatile*/ //int count1 = 0;
	
	AtomicInteger count = new AtomicInteger(0); 

	/*synchronized*/ void m() { 
		for (int i = 0; i < 10000; i++)
			//if count1.get() < 1000
			count.incrementAndGet(); //count1++ uses cas's atomic operations
	}

	public static void main(String[] args) {
		T01_AtomicInteger t = new T01_AtomicInteger();

		List<Thread> threads = new ArrayList<Thread>();

		for (int i = 0; i < 10; i++) {
			threads.add(new Thread(t::m, "thread-" + i));
		}

		threads.forEach((o) -> o.start());

		threads.forEach((o) -> {
			try {
				o.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		});

		System.out.println(t.count);

	}

}
Simple performance comparison between cas and synchronized
public class T02_AtomicVsSyncVsLongAdder {
    static long count2 = 0L;
    static AtomicLong count1 = new AtomicLong(0L);
    static LongAdder count3 = new LongAdder();

    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[1000];

        for(int i=0; i<threads.length; i++) {
            threads[i] =
                    new Thread(()-> {
                        for(int k=0; k<100000; k++) count1.incrementAndGet();
                    });
        }

        long start = System.currentTimeMillis();

        for(Thread t : threads ) t.start();

        for (Thread t : threads) t.join();

        long end = System.currentTimeMillis();

        //TimeUnit.SECONDS.sleep(10);

        System.out.println("Atomic: " + count1.get() + " time " + (end-start));
        //-----------------------------------------------------------
        Object lock = new Object();

        for(int i=0; i<threads.length; i++) {
            threads[i] =
                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        for (int k = 0; k < 100000; k++)
                            synchronized (lock) {
                                count2++;
                            }
                    }
                });
        }

        start = System.currentTimeMillis();

        for(Thread t : threads ) t.start();

        for (Thread t : threads) t.join();

        end = System.currentTimeMillis();


        System.out.println("Sync: " + count2 + " time " + (end-start));


        //----------------------------------
        for(int i=0; i<threads.length; i++) {
            threads[i] =
                    new Thread(()-> {
                        for(int k=0; k<100000; k++) count3.increment();
                    });
        }

        start = System.currentTimeMillis();

        for(Thread t : threads ) t.start();

        for (Thread t : threads) t.join();

        end = System.currentTimeMillis();

        //TimeUnit.SECONDS.sleep(10);

        System.out.println("LongAdder: " + count1.longValue() + " time " + (end-start));

    }

}
LongAdder explains that the bottom layer uses a segmented lock (also a cas operation), which places the values in an array, locks them on each node, and finally adds up the values of each node, so LongAdder has an advantage when the number of threads is particularly high.

The above is just a simple test, in fact, the number of threads and loops will be reduced, LongAdder may not have the advantage, so in the actual production of traditional Chinese medicine, which kind of use needs to be tested through the actual situation and how high concurrency level to determine which one to use. atomic is faster than sync here because there is no need to lock. Sync may need to apply for a heavy lock, so sync is less efficient in this case.

AtomicXXX is implemented using CAS. At the bottom, it uses the weakCompareAndSet method of the Unsafa class. The Unsafe class is allocateMemory, which can be accessed directly by jdk1.8 and previously by reflection. After 1.9, it is prohibited for use by their own internal staff, although he has opened a getUnsafe() method. But errors will occur when it runs.

public class HelloUnsafe {
    static class M {
        private M() {}

        int i =0;
    }

   public static void main(String[] args) throws InstantiationException {
        Unsafe unsafe = Unsafe.getUnsafe();
        M m = (M)unsafe.allocateInstance(M.class);
        m.i = 9;
        System.out.println(m.i);
    }
}

cas interpretation

cas operates strictly on two values, but sometimes on three, such as the object you want to change

cas (Expected, Updated)

m=0

m++

cas(0,1){

        for(;;) If the current m-value==0 means that no one has come in to change the value, you can now change the value to 1, m=1

}

In fact, in the process of judging m=0 and setting m to 1 above, threads will come in to change the value of M. But now cup supports cas operation at the primitive level. It adds barriers around the contents of this memory, so that no one can move it, and changes the contents of this memory directly and atomically.

ReentrantLock

Re-entrainable locks, lock.lock() needs to be unlocked manually after running (important things are said three times). If syn locks are used, the jvm will automatically release the locks if an exception is encountered, but locks must be released manually, so locks are often released in finally

public class T02_ReentrantLock2 {
	Lock lock = new ReentrantLock();

	void m1() {
		try {
			lock.lock(); //synchronized(this)
			for (int i = 0; i < 10; i++) {
				TimeUnit.SECONDS.sleep(1);

				System.out.println(i);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	void m2() {
		try {
			lock.lock();
			System.out.println("m2 ...");
		} finally {
			lock.unlock();
		}

	}

	public static void main(String[] args) {
		T02_ReentrantLock2 rl = new T02_ReentrantLock2();
		new Thread(rl::m1).start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(rl::m2).start();
	}
}

ReentrantLock can use tryLock to attempt to lock, and the method will continue to execute regardless of whether the lock is locked or not. It can either be determined by the return value of tryLock or specify the time of tryLock. Since tryLock(time) throws an exception, it is important to note that unclock handling must be placed in finally

public class T03_ReentrantLock3 {
	Lock lock = new ReentrantLock();

	void m1() {
		try {
			lock.lock();
			for (int i = 0; i < 3; i++) {
				TimeUnit.SECONDS.sleep(1);

				System.out.println(i);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	/**
	 * Attempting to lock using tryLock will continue the method regardless of whether it is locked or not
	 * Locking can be determined by the return value of tryLock
	 * You can also specify a time for tryLock, because tryLock(time) throws an exception, so be aware that unclock processing must be placed in finally
	 */
	void m2() {
		/*
		boolean locked = lock.tryLock();
		System.out.println("m2 ..." + locked);
		if(locked) lock.unlock();
		*/
		
		boolean locked = false;
		
		try {
            //Don't wait five seconds to pick it up, it's within five seconds to pick up the lock
			locked = lock.tryLock(5, TimeUnit.SECONDS);
			System.out.println("m2 ..." + locked);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			if(locked) lock.unlock();
		}
		
	}

	public static void main(String[] args) {
		T03_ReentrantLock3 rl = new T03_ReentrantLock3();
		new Thread(rl::m1).start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(rl::m2).start();
	}
}
Using ReentrantLock, you can also call the lockInterruptibly method, which responds to the thread interrupt method and can be interrupted while a thread is waiting for a lock
public class T04_ReentrantLock4 {
		
	public static void main(String[] args){
		Lock lock = new ReentrantLock();
		
		
		Thread t1 = new Thread(()->{
			try {
				lock.lock();
				System.out.println("t1 start");
				TimeUnit.SECONDS.sleep(10);
				System.out.println("t1 end");
			} catch (InterruptedException e) {
				System.out.println("interrupted!");
			} finally {
				lock.unlock();
			}
		});
//		t1.start();
		
		Thread t2 = new Thread(()->{
			try {
				//lock.lock();
				lock.lockInterruptibly(); //You can respond to the interrupt() method
				System.out.println("t2 start");
				for(int i = 0 ; i < 5 ; i++){
					TimeUnit.SECONDS.sleep(1);
					System.out.println(Thread.currentThread().getName() +":"+ i);

				}
				System.out.println("t2 end");
			} catch (InterruptedException e) {
				System.out.println("interrupted!");
			} finally {
				lock.unlock();
			}
		});
		t2.start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t1.start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		t2.interrupt(); //Interrupt Thread 2's Wait
		
	}
}
ReentrantLock can also be specified as a fair lock, which is unfair by default

Fair Lock: When a thread comes in, it first waits in the queue to check if there are any threads waiting. If it does, it does not compete directly for locks

public class T05_ReentrantLock5 extends Thread {
		
	private static ReentrantLock lock=new ReentrantLock(true); //The parameter true denotes a fair lock. Compare the output
    public void run() {
        for(int i=0; i<100; i++) {
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName()+"Acquire locks");
            }finally{
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        T05_ReentrantLock5 rl=new T05_ReentrantLock5();
        Thread th1=new Thread(rl);
        Thread th2=new Thread(rl);
        th1.start();
        th2.start();
    }
}

Tags: Java Back-end

Posted on Sat, 27 Nov 2021 12:58:17 -0500 by jplavoie