Java learning ==> Multithreading

First, the three ways to create threads

The first

public class App {

  public static void main(String[] args) {

    Thread thread = new Thread(() -> {
      while (true) {
        System.out.println("testThread");
      }
    });

    thread.start();
  }
}

The second

public class App {

  public static void main(String[] args) {

    Thread thread = new Thread(new Runnable() {
      @Override
      public void run() {
        while (true) {
          System.out.println("testThread");
        }
      }
    });

    thread.start();
  }
}

The first and second essentially the same way, but the first method is to use the wording of lambda expressions.

The third

public class App extends Thread{

  public static void main(String[] args) {

    App app = new App();
    app.run();
  }

  @Override
  public void run() {
    System.out.println("testThread");
  }
}

Two, synchronized keyword lock

  In order to solve the problem of multiple threads to modify the same data coverage or data loss occurs, Java provides synchronized keyword to add protective umbrella to ensure data security. protection synchronized keyword has shared data in two ways, one is used to modify the code block, one is a method for modifying. 

1, the modified code blocks

  Modifying the code block is executed thread in vivo method involves modifying the operation of shared data, encapsulated by {}, and the modified block with the synchronized keyword. Let's look at the thread-safety issues in the case of modification methods and code blocks:

public class App {

  private int i;

  public static void main(String[] args) {

    App app = new App();

    new Thread(() -> {
      app.produce();
    }).start();

    new Thread(() -> {
      app.consume();
    }).start();

  }

  public void produce() {
    while (i < 5) {
      i++;
      System.out.println("produce = " + i);
    }

  }

  public void consume() {
    while (i > 0) {
      i--;
      System.out.println("consume = " + i);
    }
  }
}

// 输出结果
produce = 1
consume = 0
produce = 1
produce = 1
consume = 0
consume = 1
consume = 0
produce = 2
produce = 1
produce = 2
produce = 3
produce = 4
produce = 5
Thread safety problems did not add synchronized keyword

From the output, it is clear the two threads of the manipulated variable i is out of whack, if we use the synchronized keyword to modification, is not a problem you can solve it? Let's look at the following code:

public class App {

  private int i;

  public static void main(String[] args) {

    App app = new App();

    new Thread(() -> {
      app.produce();
    }).start();

    new Thread(() -> {
      app.consume();
    }).start();

  }

  public void produce() {
    synchronized (this){
      while (i < 5) {
        i++;
        System.out.println("produce = " + i);
      }
    }
  }

  public void consume() {
    synchronized (this){
      while (i > 0) {
        i--;
        System.out.println("consume = " + i);
      }
    }
  }
}

// 输出结果
produce = 1
produce = 2
produce = 3
produce = 4
produce = 5
consume = 4
consume = 3
consume = 2
consume = 1
consume = 0
synchronized keyword solve thread safety issues

From the test results, the increase in the synchronized keyword, no data confusion, the following will be analyzed thread-safety problems why not add synchronized keyword, first look at the following chart:

  We know that kind of information is stored in the data area zone run-time method, and the method area class property (a global variable) naturally stored in the runtime data area belonging to the thread shared area. We use two threads execute simultaneously produce and consume method, will be replicated in a copy of the stack i and stored in the stack, two threads in each stack for i has been operated and changed the value of i (at this time two threads value stack i is not the same), then two threads are the value stack i brush back to the main memory, then it causes confusion values ​​of i.

  After adding synchronized keyword, re-operation of the i's main memory, two threads compete lock resources, competition to other thread releases the lock thread lock resource for i operate, not compete to lock the thread entered the BLOCKED state waiting for resources and then re-competition, so i will not be disturbed value. It is worth noting: synchronized keyword lock object should be the same object, otherwise, there will be problems.

public class App {

  private static int i;

  public static void main(String[] args) {

    new Thread(()->{
      new App().produce();
    }).start();

    new Thread(()->{
      new App().consume();
    }).start();
  }

  public void produce() {
    synchronized (this){
      while (i < 5) {
        i++;
        System.out.println("produce = " + i);
      }
    }
  }

  public void consume() {
    synchronized (this){
      while (i > 0) {
        i--;
        System.out.println("consume = " + i);
      }
    }
  }
}

// 输出结果
produce = 1
produce = 2
consume = 1
consume = 1
consume = 0
produce = 2
produce = 1
produce = 2
produce = 3
produce = 4
produce = 5
Different synchronized lock object

  From the above point of view the code, although the synchronized keyword is the this lock object, but the implementation Produce () and Consume () method using two different objects, i.e., its lock object is not the same object, although defined for i static variables, the operation is the same i, but has emerged thread safety issue, then, we need to lock objects synchronized keyword changed App.class, as follows:

public class App {

  private static int i;

  public static void main(String[] args) {

    new Thread(()->{
      new App().produce();
    }).start();

    new Thread(()->{
      new App().consume();
    }).start();
  }

  public void produce() {
    synchronized (App.class){
      while (i < 5) {
        i++;
        System.out.println("produce = " + i);
      }
    }
  }

  public void consume() {
    synchronized (App.class){
      while (i > 0) {
        i--;
        System.out.println("consume = " + i);
      }
    }
  }
}

// 输出结果
produce = 1
produce = 2
produce = 3
produce = 4
produce = 5
consume = 4
consume = 3
consume = 2
consume = 1
consume = 0
synchronized lock object same

2, modification methods

When the synchronized keyword modification methods, on the return to the previous value, if the method is static, then the lock object is a Class object of the current class, if not a static method, this object is a lock

public class App {

  private static int i;

  public static void main(String[] args) {

    new Thread(()->{
      new App().produce();
    }).start();

    new Thread(()->{
      new App().consume();
    }).start();
  }

  public static synchronized void produce() {
    while (i < 5) {
      i++;
      System.out.println("produce = " + i);
    }
  }

  public static synchronized void consume() {
    while (i > 0) {
      i--;
      System.out.println("consume = " + i);
    }
  }
}

// 输出结果
produce = 1
produce = 2
produce = 3
produce = 4
produce = 5
consume = 4
consume = 3
consume = 2
consume = 1
consume = 0
synchronized keyword modification methods

Let's do a little exercise: producers and consumers multi-threaded mode, two thread loop print 1 and 0

public class App {

  private int i;

  public static void main(String[] args) {

    App app = new App();

    new Thread(app::produce).start();
    new Thread(app::consume).start();
  }

  public synchronized void produce() {
    while (true) {
      if (i < 5) {
        i++;
        System.out.println("produce = " + i);
        this.notify();
      }
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  public synchronized void consume() {
    while (true) {
      if (i > 0) {
        i--;
        System.out.println("consume = " + i);
        this.notify();
      }
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace (); 
      } 
    } 
  } 
}
synchronized instance method modification
public class App {

  private static int i;

  public static void main(String[] args) {

    new Thread(()->{
      new App().produce();
    }).start();

    new Thread(()->{
      new App().consume();
    }).start();

  }

  public static synchronized void produce() {
    while (true) {
      if (i < 5) {
        i++;
        System.out.println("produce = " + i);
        App.class.notify();
      }
      try {
        App.class.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  public static synchronized void consume() {
    while (true) {
      if (i > 0) {
        i--;
        System.out.println("consume = " + i);
        App.class.notify();
      }
      try {
        App.class.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
synchronized modification of static methods

Three, Lock Interface

  Before Lock interface goes, Java program is realized by the synchronized keyword lock function. After contracting in JDK1.5 and added Lock interface and associated implementation class to implement the lock function. Although synchronized methods and scope of the mechanisms statements makes it easier to monitor lock program, and helps avoid many common programming errors involving locks, but sometimes we need a more flexible approach to the lock. For example, some algorithms for traversing the data structure requires the use of concurrent access to the "manual" or "chain lock": the locking You Get the node A, and then acquires a node B, A and acquire C and then released, and then release the B and D and the like obtained . In this scenario, the synchronized keyword is not so easy to implement, and easy to use Lock many interfaces.

synchronized keyword and Lock difference:

  • Lock is not the Java language built-in, synchronized keyword Java language and is therefore built-in features. Lock is a class that can be synchronized access through this class;
  • Lock and synchronized it is very much different, with synchronized without requiring the user to manually release the lock, when synchronized method or synchronized block of code executed, the system will automatically make the thread releases the lock of the occupation; and Lock must be user to manually release the lock, if not take the initiative to release the lock, it may lead to a deadlock.

Lock the advantages and limitations of synchronized

  If a block is modified synchronized keyword, when a thread gets the corresponding lock, and executes the code block other threads will occupy only waits until the lock thread releases the lock. In fact, occupy the lock thread releases the lock will generally be one of three things:

  • Possession over the lock thread execution code block, then release the possession of the lock;
  • Possession of lock thread execution exception occurs, then JVM will automatically thread releases the lock;
  • WAITING state occupies a thread into the lock to release the lock, for example, the calling thread wait () method and the like.

Then we try to consider the following three conditions:

  • In the case of the use of the synchronized keyword, if lock thread possession due to IO wait, or for other reasons (such as call sleep method) is blocked, but it does not release the lock, then other threads can only have been waiting for, no other . This will greatly affect the efficiency of program execution. Therefore, we need to have a mechanism to prevent the thread has been waiting indefinitely to wait any longer (for example, only wait for a certain period of time (Solution: tryLock (long time, TimeUnit unit)) or to respond to interrupt (Solution: lockInterruptibly () )), this situation can be resolved through Lock;
  • We know that when multiple threads read and write files, read the phenomenon of conflict and write operations occur, write and write conflict phenomenon also occurs, but read and read operations do not conflict occur. But if the synchronized keyword synchronized, it would cause a problem, that is, when multiple threads are only read, only one thread can be read in other threads waiting for the lock can only be released and can not be read operating. Thus, a mechanism is needed so that when multiple threads are only read operations, no conflict between threads. Similarly, Lock can solve this situation (Solution: ReentrantReadWriteLock);
  • We can learn Lock thread has not acquired successfully lock (Solution: ReentrantLock), but this can not be done is synchronized.

Three cases mentioned above, we can be resolved by Lock, but the synchronized keyword can not do anything. In fact, Lock is the interface under java.util.concurrent.locks bag, Lock implementations provide more extensive than the synchronized keyword lock operation, it can in a more elegant way to handle thread synchronization issues. In other words, Lock provides more functionality than synchronized.

Lock simple to use interface

public class App {

  private int i;

  private Lock locker = new ReentrantLock();

  public static void main(String[] args) {
    App app = new App();

    new Thread(app::produce).start();
    new Thread(app::consume).start();
  }

  public void produce() {
    locker.lock();
    try {
      while (i < 5) {
        i++;
        System.out.println("produce = " + i);
      }
    } finally {
      locker.unlock();
    }
  }

  public void consume() {
    locker.lock();
    try {
      while (i > 0) {
        i--;
        System.out.println("consume = " + i);
      }
    } finally {
      locker.unlock();
    }
  }
}
Use Lock to ensure the security thread

In order to prevent while the code block code execution error caused Lock does not release the lock, while we should try ... finally block of code placed in, finally placed lock.unlock (), regardless of whether an error while the code block execution will release the lock.

Use Condition achieve waiting / notification mechanism

  synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。

  在使用notify/notifyAll()方法进行通知时,被通知的线程是由JVM选择的,使用ReentrantLock类结合Condition实例可以实现“选择性通知”,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。

public class App {

  private int i;

  private Lock locker = new ReentrantLock();
  private Condition produceCondition = locker.newCondition();
  private Condition consumeCondition = locker.newCondition();

  public static void main(String[] args) {
    App app = new App();

    new Thread(app::produce).start();
    new Thread(app::consume).start();
  }

  public void produce() {
    locker.lock();
    try {
      while (true) {
        while (i < 5) {
          i++;
          System.out.println("produce = " + i);
          consumeCondition.signalAll();
        }
        try {
          produceCondition.await();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    } finally {
      locker.unlock();
    }
  }

  public void consume() {
    locker.lock();
    try {
      while (true) {
        while (i > 0) {
          i--;
          System.out.println("consume = " + i);
          produceCondition.signalAll();
        }
        try {
          consumeCondition.await();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    } finally {
      locker.unlock();
    }
  }
}
使用Condition实现等待/通知机制

四、线程池

  Java中频繁的创建和销毁线程是非常消耗资源的,为了减少资源的开销,提高系统性能,Java 提供了线程池。Java 中创建线程池主要有以下两种方式:

  • Executors
  • ThreadPoolExecutor

Executors

public class App {

  public static void main(String[] args) {

    ExecutorService executors = Executors.newFixedThreadPool(5);

    Runnable runnable = () -> {
      System.out.println("testThread...");
    };

    for (int i = 0; i < 5; i++) {
      executors.submit(runnable);
    }
    executors.shutdown();
  }
}
使用Executors创建线程池

我们不建议使用这种方式创建线程池,先来看下以下这段代码:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }


public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

@Native public static final int   MAX_VALUE = 0x7fffffff;

使用 Executors.newFixedThreadPool() 来创建线程池,调用的是 ThreadPoolExecutor() 类的构造方法,其中有个参数是 new LinkedBlockingQueue<Runnable>() 队列,允许的请求队列长度为 Integer.MAX_VALUE,如果短时间接收的请求太多的话,队列中可能会堆积太多请求,最终导致内存溢出。

ThreadPoolExecutor

正确的创建线程池的方式如下:

public class App {

  public static void main(String[] args) {

    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
            .setNameFormat("demo-pool-%d").build();
    ExecutorService executors = new ThreadPoolExecutor(
            5,
            200,
            10,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1024),
            namedThreadFactory,
            new ThreadPoolExecutor.AbortPolicy());

    Runnable runnable = () -> {
      System.out.println("testThread...");
    };

    for (int i = 0; i < 5; i++) {
      executors.submit(runnable);
    }
    executors.shutdown();
  }
}

指定线程的名称、核心线程数、最大线程数,阻塞队列长度以及拒绝策略,拒绝策略的意思是队列中的请求超过设定的值时,我们该如何操作,上面这段代码中的 new ThreadPoolExecutor.AbortPolicy() 代表拒绝,如果对于超出阻塞队列长度的请求不想拒绝的话,可以把请求存入其他介质,比如 redis 当中。我们先来看一下AbortPolicy的实现:

    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
AbortPolicy

AbortPolicy 类实现自 RejectedExecutionHandler 接口,如果我们想要用其他的处理方式来处理超出队列长度的请求,可以自己来写一个策略实现 RejectedExecutionHandler接口。

五、JUC

Guess you like

Origin www.cnblogs.com/L-Test/p/12122235.html