Java multithreading learning notes

Start with selling tickets

Let's look at a classic example of multithreading: create three windows to sell tickets, and the total number of votes is 100.
Look at this code first:

package com.xzc;

class Window extends Thread{
    public Window(){

    }
    public Window(String name){
        super(name);
    }
    private int ticket = 100;

    @Override
    public void run() {
        while (true){
            if(ticket > 0){
                System.out.println(getName()+":Ticket No.:"+ticket--);
            }else {
                break;
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Window w1 = new Window("Window one");
        Window w2 = new Window("Window II");
        Window w3 = new Window("Window three");
        w1.start();
        w2.start();
        w3.start();
    }

}

Output result:

The same ticket is sold by multiple windows. This is because the ticket we provide is an instance variable, belonging to every object and thread. This resource is not shared. If we set ticket as a static variable:

java private static int ticket = 100;
Output result:

Roughly shared resources, but there will be some problems! In fact, there is a thread safety problem here. The output here is due to a certain delay, so it does not decrease in turn from the output result.

Create multithreading

Method 2: implement Runnable interface

1. Create a class that implements the Runnable interface
2. Abstract method to implement Runnable class: run()
3. Create an object that implements the class
4. Pass this object as a parameter to the constructor of Thread class and create the object of Thread class
5. Call the start method through the Thread class object.

package com.xzc;

class MyThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        new Thread(new MyThread(),"Thread one").start();
    }

}

Output result:

After traversing the even number from 0 to 100, here is to create a class object that implements the runnable class interface and pass it as a parameter into the constructor of Thread class. In fact, the constructor can also have a parameter, that is, Thread name, which we use directly here. The start here has two functions: 1 Start the Thread. 2. Call the run method of the target of runnable type. We can reuse this class and start another Thread.

package com.xzc;

class MyThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        new Thread(new MyThread(),"Thread one").start();
        new Thread(new MyThread(),"Thread two").start();
    }

}

Output result:

Let's talk about selling tickets again

package com.xzc;

class Window implements Runnable{
    public Window(){}
    private int ticket;
    public Window(int ticket){
        this.ticket = ticket;
    }
    @Override
    public void run() {
        while (true){
            if(ticket > 0){
                System.out.println(Thread.currentThread().getName()+"Ticket No.:"+ticket--);
            }else {
                break;
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Window w = new Window(100);
        new Thread(w,"Window one").start();
        new Thread(w,"Window II").start();
        new Thread(w,"Window three").start();
    }

}

Output result:

It can be found that resource sharing has been well realized, but this implementation also has thread safety problems and heavy ticket problems. However, without the static keyword, we have 100 tickets. This is because although the ticket is an instance variable, we only have one window object in the constructor as a parameter, which is naturally the same ticket.

Two ways to create multithreading

During development, the method of realizing Runnable interface is preferred. The reasons are as follows:
1. The implementation method does not have the limitation of single class inheritance, and the class can inherit some specific parent classes.
2. The implementation method is more suitable to deal with the situation that multiple threads share data.
Contact: Thread itself implements the Runnable interface.
The same thing: both methods need to override the run method.

Thread life cycle


Thread safety

If we add sleep to the run method of the ticket program that implements the runnable interface

package com.xzc;

class Window implements Runnable{
    public Window(){}
    private int ticket;
    public Window(int ticket){
        this.ticket = ticket;
    }
    @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 No.:"+ticket--);
            }else {
                break;
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Window w = new Window(100);
        new Thread(w,"Window one").start();
        new Thread(w,"Window II").start();
        new Thread(w,"Window three").start();
    }

}

Output result:

The probability of output 0 and - 1 is greatly improved. This is because when there is a ticket, our thread t1 comes in and is blocked, but it has not been output. When t2 comes in, it is also blocked. Similarly, t3, and then the following occurs:

Whether it is a duplicate ticket or a wrong ticket, the reason for the problem is that when a thread operates the ticket and the operation has not been completed, other threads participate and operate the ticket. How to solve it? When a thread is operating a ticket, other threads cannot participate until the thread completes the operation. In this case, even if the thread is blocked, it cannot be changed.

Synchronous code block solves the thread safety problem of implementing Runnable interface

Keywords: synchronized

synchronized(/*Synchronization monitor*/){
	//Code to be synchronized
}
//The code that operates the shared data is the code that needs to be synchronized
//Synchronous monitor, commonly known as lock. The object of any class can act as a lock. Multiple threads are required to share the same lock.
package com.xzc;

class Window implements Runnable{
    public Window(){}
    private int ticket;
    public Window(int ticket){
        this.ticket = ticket;
    }
    Object obj = new Object();
    @Override
    public void run() {

        while (true){
            synchronized (obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "Ticket No.:" + ticket--);
                } else {
                    break;
                }
            }
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Window w = new Window(100);
        new Thread(w,"Window one").start();
        new Thread(w,"Window II").start();
        new Thread(w,"Window three").start();
    }

}

Output result:

It must be noted that multiple threads are required to share the same lock. Here, we have one window object and one obj object, which ensures that all threads share the same lock. There will be no thread safety issues.
The advantage of synchronization solves the thread safety problem, but when operating the synchronization code, only one thread can participate and other threads wait, which is equivalent to a single threaded process, and the efficiency is relatively low.

Synchronous code block solves the Thread safety problem of inheriting Thread class

Because there are different window objects at this time, if you use the same method as before, you can't share a lock. Because the obj instances of each window object are different, you can't share a lock. Set obj to static.

package com.xzc;

class Window2 extends Thread{
    public Window2(){

    }
    public Window2(String name){
        super(name);
    }
    private static int ticket = 100;
    private static final Object obj = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + ":Ticket No.:" + ticket--);
                } else {
                    break;
                }
            }
        }
    }
}
public class Test2 {
    public static void main(String[] args) {
        Window2 w1 = new Window2("Window one");
        Window2 w2 = new Window2("Window II");
        Window2 w3 = new Window2("Window three");
        w1.start();
        w2.start();
        w3.start();
    }

}

Output result:

When implementing the lock of Runnable interface, you can directly use this keyword. synchronized (this) {}, because this here refers to an object of Window. It is the only object of the class that implements the Runnable interface. Therefore, for convenience, you can use this directly without creating a new object. However, this cannot be used in objects that inherit the thread class, because we have three new objects, which are not unique. What's the easy way? Use reflection.

package com.xzc;

class Window2 extends Thread{
    public Window2(){

    }
    public Window2(String name){
        super(name);
    }
    private static int ticket = 100;
    private static final Object obj = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (Window2.class) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + ":Ticket No.:" + ticket--);
                } else {
                    break;
                }
            }
        }
    }
}
public class Test2 {
    public static void main(String[] args) {
        Window2 w1 = new Window2("Window one");
        Window2 w2 = new Window2("Window II");
        Window2 w3 = new Window2("Window three");
        w1.start();
        w2.start();
        w3.start();
    }

}

We can just pass in the Class object of window2 in the synchronized parameter. synchronized (Window2.class)

Keywords: Java Multithreading

Added by fahim_junoon on Fri, 18 Feb 2022 05:03:04 +0200