java multithreading, thread synchronization

Multi-threading

In an application, there are multiple threads, and different threads can perform tasks in parallel
Advantages:

Improve program processing capabilities

Improve cpu utilization

Improve the program structure, divide complex tasks into multiple threads, and run them independently

Disadvantages:

There are many threads and it takes up a lot of memory.

Multi-threading requires coordination and management, tracking and management of threads is required, and the CPU overhead increases.

Threads will affect each other’s access to shared resources, and if not controlled, data errors will occur (for example, in the tortoise and the hare race problem, the hare and the tortoise walked 1,000 steps to the end at the same time)

Thread synchronization:

In order to prevent multi-threading from affecting access to shared resources, a “synchronization” mechanism is required to limit thread execution on a first-come, first-served basis.

Synchronization is also “queuing” and “locking”

Queuing between threads, operating on shared resources sequentially instead of simultaneously

Use locks to ensure the correctness of data when accessed in methods

Lock:

Ensure that only one thread accesses the shared resource at a point in time. Whichever thread acquires the lock has the right to access the shared resource.

synchronized(synchronized lock){<!-- -->
//Code block that needs to be synchronized
}
public synchronized void show(String name){<!-- -->
    //Code block that needs to be synchronized
}
Practical application:

Ticket buying problem, buy tickets at two windows

@Override
    public void run(){<!-- -->
        while(true) {<!-- -->
                if (t > 0) {<!-- -->
                    try {<!-- -->
                        Thread.sleep(10);
                    } catch (InterruptedException e) {<!-- -->
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "got it" + t + "ticket"));
                    t--;
                }
                else{<!-- -->
                    break;
                }
        }
    }

test:

 TicketThread ticketThread1=new TicketThread();
        TicketThread ticketThread2=new TicketThread();
        ticketThread1.setName("t1");
        ticketThread2.setName("t2");
        ticketThread1.start();
        ticketThread2.start();
/*
t1 got 10 votes
t2 got 10 votes
t2 got 8 votes
t1 got 8 votes
t2 got 6 votes
t1 got 6 votes
t2 got 4 votes
t1 got 4 votes
t1 got 2 votes
t2 got 2 votes
t1 got 0 votes
*/

It is found that there will be situations where two windows buy the same ticket, so synchronized is needed to lock the thread that accesses first.

 static int t=10;
    static Object object=new Object();
    @Override
    public void run(){<!-- -->
        while(true) {<!-- -->
            //synchronized (object) {//After locking, only one other thread accesses the shared resource
                if (t > 0) {<!-- -->
                    try {<!-- -->
                        Thread.sleep(10);
                    } catch (InterruptedException e) {<!-- -->
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "got it" + t + "ticket"));
                    t--;
                }
                else{<!-- -->
                    break;
                }
            //}
        }
    }

Synchronization object: The objects corresponding to multiple line scales must be the same
It is used to record whether any thread has entered the synchronized code block. There is an empty space in the object header of the object to record whether any thread has entered the synchronized code block.

A synchronization object can be an object of any class in Java

Lock method:

synchronized modification method

The synchronization object will have a default

synchronized If the modified method is a non-static method, then the synchronization object is this

If synchronized modifies a static method, then the synchronization object is the Class object of the current class.

Non-static methods:

public synchronized void printTicket(){<!-- -->
        while(true) {<!-- -->
                if (t > 0) {<!-- -->
                    try {<!-- -->
                        Thread.sleep(10);
                    } catch (InterruptedException e) {<!-- -->
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "got it" + t + "ticket"));
                    t--;
                }
                else{<!-- -->
                    break;
                }
            }
        }
/*
t1 got 10 votes
t2 got 10 votes
t1 got 8 votes
t2 got 8 votes
t1 got 6 votes
t2 got 6 votes
t1 got 4 votes
t2 got 4 votes
t1 got 2 votes
t2 got 2 votes
t1 got 0 votes
*/

Static method:

public static synchronized void printTicket(){<!-- -->
        while(true) {<!-- -->
                if (t > 0) {<!-- -->
                    try {<!-- -->
                        Thread.sleep(10);
                    } catch (InterruptedException e) {<!-- -->
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "got it" + t + "ticket"));
                    t--;
                }
                else{<!-- -->
                    break;
                }
            }
        }
I found that I bought tickets normally
Use task interface synchronized:
int t=1000;
    @Override
    public void run(){<!-- -->
        while(true) {<!-- -->
            synchronized (this) {<!-- -->//After locking, only one other thread accesses the shared resource
                if (t > 0) {<!-- -->
                    try {<!-- -->
                        Thread.sleep(10);
                    } catch (InterruptedException e) {<!-- -->
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "got it" + t + "ticket"));
                    t--;
                }
                else{<!-- -->
                    break;
                }
            }
        }
    }

test:

 TicketTask ticketTask=new TicketTask();
        Thread t1=new Thread(ticketTask,"t1");
        Thread t2=new Thread(ticketTask,"t2");
        t1.start();
        t2.start();

Member variables do not need to be static, and synchronized can also use this instead of object, because only one object needs to be created at this time.

The second locking method Lock

Use the ReentrantLock class to implement locking

lock lock, unlock release lock

The lock must be released in finally, because if an exception occurs after adding the lock and it will not be executed later, the lock cannot be released, and the thread will be stuck. After an exception is captured in finally, the finally statement can be executed to release the lock. Convenient for executing other threads

LockThread:

public class LockThread implements Runnable{<!-- -->
    int t=10;
    ReentrantLock reentrantLock=new ReentrantLock();
    @Override
    public void run(){<!-- -->
        while(true) {<!-- -->
                if (t > 0) {<!-- -->
                    try {<!-- -->
                        reentrantLock.lock();
                        Thread.sleep(10);
                    } catch (InterruptedException e) {<!-- -->
                        e.printStackTrace();
                    }finally {<!-- -->
                        reentrantLock.unlock();
                    }
                    System.out.println((Thread.currentThread().getName() + "got it" + t + "ticket"));
                    t--;
                }
                else{<!-- -->
                    break;
                }
        }
    }
}

Test:

 LockThread lockThread=new LockThread();
        Thread t1=new Thread(lockThread,"t1");
        Thread t2=new Thread(lockThread,"t2");
        t1.start();
        t2.start();
/*
t1 got 10 votes
t2 got 9 votes
t1 got 8 votes
t2 got 7 votes
t1 got 6 votes
t2 got 5 votes
t1 got 4 votes
t2 got 3 votes
t1 got 2 votes
t2 got 1 vote
t1 got 0 votes
*/
Differences between the two methods:

synchronization: synchronization reentrant: reentrant

synchronized is a keyword ReentrantLock is a class

synchronized is released implicitly. If an exception occurs during operation, the lock will be released by itself.

Reentrant requires manually adding locks and releasing locks

synchronized modifies code blocks and methods ReentrantLock can only modify code blocks

Thread communication

Thread communication refers to the interaction between multiple threads through mutual restraint and mutual scheduling.

Involves three methods

.wait Once this method is executed, the current thread enters the blocking state and releases the synchronization lock object.

.notify Once this method is executed, a thread that was waited will be awakened. If multiple threads are waited,

Just wake up the one with higher priority.

.notifyAll Once this method is executed, all wait threads will be awakened.

The three methods .wait(), notify(), and notifyAll() must be used in synchronized code blocks or synchronized methods.

In the law.

These three methods must be called by the lock object

Thread:

 static int t=0;
    static Object object=new Object();//Use static to ensure that the synchronization lock is always the same object
    @Override
    public void run(){<!-- -->
        while(true) {<!-- -->
            synchronized (object) {<!-- -->
                object.notify();//Wake up the waiting thread
                if(t<=20){<!-- -->
                    System.out.println(Thread.currentThread().getName() + ":" + t);
                    t + + ;
                }else {<!-- -->
                    break;
                }
                try {<!-- -->
                    object.wait();//Let the thread wait and release the lock at the same time. The waiting continuation process cannot wake up by itself and must let another thread wake up.
                } catch (InterruptedException e) {<!-- -->
                    e.printStackTrace();
                }
            }
        }
    }
/*
w1:0
w2:1
w1:2
w2:3
w1:4
w2:5
w1:6
w2:7
w1:8
w2:9
w1:10
w2:11
w1:12
w2:13
w1:14
w2:15
w1:16
w2:17
w1:18
w2:19
w1:20
*/

The third way to create a thread:

Override the call method using the Callable interface

The call method has a return value and can throw an exception. You can also customize the type of return result

CaTask:

public class CaTask<T> implements Callable<T> {<!-- -->

    @Override
    public T call() throws Exception {<!-- -->
        Integer i=0;
        for (int j = 0; j < 10; j + + ) {<!-- -->
            i + =j;
        }
        return (T)i;
    }
}

CaTest:

CaTask<Integer> caTask=new CaTask<>();
        //Need to use the FutureTask class to obtain the return result
        //Receive tasks
        FutureTask<Integer> futureTask=new FutureTask<>(caTask);//transfer station
        Thread thread=new Thread(futureTask);
        thread.start();
        try {<!-- -->
            System.out.println(Integer.valueOf(futureTask.get()));//The return value is obtained with T.valueOf(futureTask.get())
        } catch (InterruptedException e) {<!-- -->
            e.printStackTrace();
        } catch (ExecutionException e) {<!-- -->
            e.printStackTrace();
        }
//45