thread

Usually there can be multiple threads in a process, but there is at least one thread in a process. Threads can make use of the resources owned by processes. In operating systems that introduce threads, processes are usually regarded as the basic unit of resource allocation, and threads are regarded as the basic unit of independent operation and independent scheduling

Selling tickets

Here is an example of learning Java basic time will try to sell tickets

// Define a resource class
class Ticker {
    private int ticker = 30;
    public synchronized void sale(a){
        if (ticker>0){
            ticker--;
            System.out.println(Thread.currentThread().getName()+":"+ticker); }}}Copy the code
public class test1 {
    /** * Thread manipulates resource classes * determines work notifications * prevents false wake up *@param args
     */
    public static void main(String[] args) {

        Ticker ticker = new Ticker();

        // Three threads call the ticket-selling method
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                for (int i = 0; i < 20; i++) { ticker.sale(); }}},"A").start();
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                for (int i = 0; i < 20; i++) { ticker.sale(); }}},"B").start();
        //lambada
        new Thread(()->{
            for (int i = 0; i < 15; i++) { ticker.sale(); }},"C").start(); }}Copy the code

After running the project, all operations can be processed without problems such as oversold and repeat selling, thanks to synchronized

ReentrantLock

Concurrent Lock class (java.util.concurrent), where ReentrantLock implements the Lock interface, and ReentrantLock implements the synchronized keyword

  • synchronizedThere is no need for the user to manually release the lock, synchronized code after the system will automatically let the thread release the lock occupation.
  • ReentrantLockThe user must manually release the lock. If the lock is not released manually, a deadlock may occur. This is typically done with lock() and unlock() methods combined with try/finally blocks, making release more flexible.

The basic implementation method is also relatively simple

class Ticker {
    private int ticker = 30;
    Lock lock = new ReentrantLock();
    public  void sale(a){
        lock.lock();
        try {
            if (ticker>0){
                ticker--;
                System.out.println(Thread.currentThread().getName()+":"+ticker); }}finally{ lock.unlock(); }}}Copy the code
  • Lock is an explicit Lock and synchronized is an implicit Lock
  • Lock only has code block locks, whereas synchronized has code block locks and method locks
  • Lock locks allow the JVM to schedule threads in less time, perform better, and have more subclasses that scale well
  • Priority Lock > synchronized

Producer-consumer

General solution

Start with a topic

  • Now two threads can operate on a variable with an initial value of zero, with one thread incrementing that variable by 1, and one thread incrementing that variable by -1, alternating for 10 rounds with an initial value of zero.
  • Start by defining the resources of the resource class and the methods of the operations
class Shop {
    private int cake = 0;

    public synchronized void add(a) throws InterruptedException {

        while(cake! =0) {this.wait();
        }
        cake++;
        System.out.println(Thread.currentThread().getName()+":"+cake);
        this.notifyAll();
    }
    
    public synchronized void sub(a) throws InterruptedException {

        while (cake==0) {this.wait();
        }
        cake--;
        System.out.println(Thread.currentThread().getName()+":"+cake);
        this.notifyAll(); }}Copy the code

For example, there are more than two threads operating on this resource class. For example, there are four threads ABCD, AC for add(), BD for sub().

  • If the variable is 0, then B and D are waiting at the same time, and A and C have only one method to add because the add method is locked
  • After notifyAll, the C thread also adds because it is already inside the condition
  • If we use if, we do not continue to judge number after waking up, and the same is true for threads B and D. The meaning of using while is to ask him to make a new judgment

Object methods wait and notify must be used together with synchronized. Object methods wait and notify must be synchronized.

  • Write threads, here we use four
public class _Producer consumer{
    public static void main(String[] args) {
        Shop shop = new Shop();

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    for (int i = 0; i < 10; i++) { shop.add(); }}catch(InterruptedException e) { e.printStackTrace(); }}},"AA").start();

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    for (int i = 0; i < 10; i++) { shop.sub(); }}catch(InterruptedException e) { e.printStackTrace(); }}},"BB").start();

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    for (int i = 0; i < 10; i++) { shop.add(); }}catch(InterruptedException e) { e.printStackTrace(); }}},"CC").start();

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    for (int i = 0; i < 10; i++) { shop.sub(); }}catch(InterruptedException e) { e.printStackTrace(); }}},"DD").start(); }}Copy the code

After running, there are no exceptions

Use JUC

Lock (ReentrantLock), condition, condition. Await (), condition. SignalAll ();

  • The following code
/** * new version */
class Shop2 {
    private int cake = 0;
    private Lock lock = new ReentrantLock();
    private Condition  condition = lock.newCondition();

    public void add(a) throws InterruptedException {
        lock.lock();
        try {
            while(cake! =0){
                condition.await();
            }
            cake++;
            System.out.println(Thread.currentThread().getName()+":"+cake);
            condition.signalAll();
        }finally{ lock.unlock(); }}public void sub(a) throws InterruptedException {
        lock.lock();
        try {
            while (cake==0){
                condition.await();
            }
            cake--;
            System.out.println(Thread.currentThread().getName()+":"+cake);
            condition.signalAll();
        }finally{ lock.unlock(); }}}Now two threads can operate on a variable with an initial value of zero. * One thread increments the variable by 1, and one thread increments the variable by -1, and * alternates the variable by 10 rounds with an initial value of 0. With high cohesion and low coupling, threads operate on resource classes * 2. Judge/do/notify * 3. * * * * * * * * * * * * * * * * * * * * * *
public class _Producer Consumer 2{
    public static void main(String[] args) {
        Shop2 shop = new Shop2();

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    for (int i = 0; i < 10; i++) { shop.add(); }}catch(InterruptedException e) { e.printStackTrace(); }}},"AA").start();

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    for (int i = 0; i < 10; i++) { shop.sub(); }}catch(InterruptedException e) { e.printStackTrace(); }}},"BB").start();

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    for (int i = 0; i < 10; i++) { shop.add(); }}catch(InterruptedException e) { e.printStackTrace(); }}},"CC").start();

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    for (int i = 0; i < 10; i++) { shop.sub(); }}catch(InterruptedException e) { e.printStackTrace(); }}},"DD").start(); }}Copy the code

So why use JUC instead of the original approach? The emergence of new technologies can replace things that old technologies cannot solve. Let’s take a look at the following examples

Accurate notification

The order of call between multiple threads, realizing A->B->C: the starting order of three threads is as follows: AA prints 5 times, BB prints 10 times,CC prints 15 times, lasting 5 rounds.

  • The main method
public class _Precise notification of sequential access{
    public static void main(String[] args) {
        PrintfDemo2 printfDemo = new PrintfDemo2();
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                printfDemo.printf(1.5); }},"AA").start();
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                printfDemo.printf(2.10); }},"BB").start();
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                printfDemo.printf(3.15); }},"CC").start(); }}Copy the code
  • The resource class
class PrintfDemo2{
    private int num = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private Condition conditions[] = {condition1,condition2,condition3};

    public void printf(int signal,int count){
        lock.lock();
        try {
            while(num! =signal){ conditions[signal-1].await();
            }
            for (int i = 1; i <= count; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
            num=num%3+1;
            conditions[num-1].signal();
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally{ lock.unlock(); }}}Copy the code

Another simple way to write it

class PrintfDemo{
    private int num = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void printf5(a){
        lock.lock();
        try {
            / / determine
            while(num! =1){
                condition1.await();
            }
            / / operation
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+"Print"+i+"Time");
            }
            / / notice
            num = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}public void printf10(a){
        lock.lock();
        try {
            / / determine
            while(num! =2){
                condition2.await();
            }
            / / operation
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"Print"+i+"Time");
            }
            / / notice
            num = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}public void printf15(a){
        lock.lock();
        try {
            / / determine
            while(num! =3){
                condition3.await();
            }
            / / operation
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName()+"Print"+i+"Time");
            }
            / / notice
            num = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}}Copy the code

So this is an optimization of the old technique, accurate notification, accurate awakening, awakening of a given thread by awakening of a given condition.