Solutions to Java multithreading security problems

Causes of occurrence

When multiple statements are operating on the same thread to share data, one thread only executes part of multiple statements, and another thread participates in the execution. The error that caused the shared data.

Examples

class Window implements Runnable{
    private int ticket = 10;

    @Override
    public void run() {
        while (true){
            if (ticket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"Ticket number:"+ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}
public class TicketDemo {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("Window 1");
        t2.setName("Window 2");
        t3.setName("Window 3");

        t1.start();
        t2.start();
        t3.start();
    }
}


In this example, three windows represent three threads selling 10 tickets at the same time, but there are wrong tickets with ticket numbers of 0 and - 1 in the running result. This is a thread. In the process of operating shared data, other threads also participate in the operation, resulting in thread safety problems.

terms of settlement

For statements that share data with multiple operations, only one thread can be executed. Other threads cannot participate in the execution process.
Java provides a professional solution to the security problem of multithreading: synchronization mechanism
The synchronization lock mechanism is to lock a resource when it is used by a task. The first task that accesses a resource must lock the resource so that other tasks cannot access it before it is unlocked. When it is unlocked, another task can lock and use it.

1. Synchronization code block

synchronized (object){ 
	// Code to be synchronized;
}

Examples

class Window implements Runnable{
    private int ticket = 10;

    @Override
    public void run() {
        while (true){
            synchronized(this){
                if (ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"Ticket number:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}
public class TicketDemo {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("Window 1");
        t2.setName("Window 2");
        t3.setName("Window 3");

        t1.start();
        t2.start();
        t3.start();
    }
}


The lock in the synchronization code block can be specified by itself, often as this or class name class. Any object can be used as a synchronous lock, and all objects automatically contain a single lock (monitor).
For example, multithreading is implemented by implementing the Runnable interface. Since multiple threads share one object, the synchronization lock can be any object at this time. If you implement multithreading by inheriting Thread, because multiple threads are not the same object, you can only use the class name at this time Class or static object. In short, it is very important to ensure that multiple threads using the same resource share a lock, otherwise the security of shared resources cannot be guaranteed.

2. Synchronization method

public synchronized void show (String name){
	...
}

Examples

class Window extends Thread{
    private static int ticket = 10;
    private static Object obj = new Object();
    @Override
    public void run() {
        while (ticket != 0){
            sell();
        }
    }

    private static synchronized void sell(){
            if (ticket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"Ticket number:"+ticket);
                ticket--;
            }
    }
}

public class TicketDemo {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        w1.setName("Window 1");
        w2.setName("Window 2");
        w3.setName("Window 3");

        w1.start();
        w2.start();
        w3.start();
    }
}

  • Lock of synchronous method: static method (class name). Class), non static method (this)
  • All static methods in a thread class share the same lock (class name). Class), and all non static methods share the same lock (this)

3. Lock

Starting from JDK 5.0, Java has provided a more powerful thread synchronization mechanism -- synchronization is achieved by explicitly defining synchronization Lock objects. Synchronous locks use Lock objects as.
java.util.concurrent.locks.Lock interface is a tool that controls multiple threads to access shared resources. Locks provide exclusive access to shared resources. Only one thread can lock the lock object at a time. Threads should obtain the lock object before accessing shared resources.
ReentrantLock class implements Lock. It has the same concurrency and memory semantics as synchronized. ReentrantLock is commonly used in thread safety control, which can explicitly add and release locks.

class A{
	private final ReentrantLock lock = new ReenTrantLock(); 
	public void m(){ 
		lock.lock(); 
		try{ 
			//Thread safe code; 
		} 
		finally{ 
			lock.unlock();  
		} 
	}
}

Examples

import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable{
    private int ticket = 10;

    //1. Instantiate ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                //2. Call the locking method lock()
                lock.lock();

                if(ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"Ticket number:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            } finally {
                //3. Call the unlock method: unlock()
                lock.unlock();
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("Window 1");
        t2.setName("Window 2");
        t3.setName("Window 3");

        t1.start();
        t2.start();
        t3.start();
    }
}


Lock is an explicit lock (manually open and close the lock, don't forget to close the lock), and synchronized is an implicit lock, which is automatically released out of the scope. Using lock lock, the JVM will spend less time scheduling threads and perform better. And it has better scalability (providing more subclasses)

Tags: Java Multithreading

Posted by Nytemare on Thu, 12 May 2022 08:23:32 +0300