高并发编程实战第一阶段(Java线程基础API教程)

高并发编程实战第一阶段(Java线程基础)

文章目录

  • 高并发编程实战第一阶段(Java线程基础)
    • 1 线程基本介绍
      • 1.1 进程的七状态模型
      • 1.2 线程的状态模型
    • 2 多线程基础API
      • 2.1 Thread类
      • 2.2 线程API之Runnable接口
      • 2.3 线程创建
      • 2.4 银行多线程叫号的案例
      • 2.5 线程安全问题
        • 2.5.1 Servelt线程不安全
        • 2.5.2 i--与System.out.println()的线程不安全问题
      • 2.6 ThreadGroup
      • 2.6 线程名字
      • 2.7 run方法
      • 2.8 stacksize
      • 2.9 守护线程
      • 2.10 优先级和Id
      • 2.11 Join/Detach
      • 2.12 interrupt
      • 2.13 优雅的中断
      • 2.14 暂停线程
      • 2.15 synchronized线程锁
      • 2.16 死锁
      • 2.17 线程间的通信
        • 2.17.1 API
        • 2.17.2 单生产者单消费者
        • 2.17.3 多生产者多消费者
      • 2.18 自定义lock锁
      • 2.19 钩子和异常
      • 2.20 自定义线程池

来自汪文君大神的多线程编程实战的笔记以及一些自己看的书和博客的理解。

1 线程基本介绍

线程是处理机调度的基本单位,进程是资源分配的基本单位。

1.1 进程的七状态模型

高并发编程实战第一阶段(Java线程基础API教程)_第1张图片

1.2 线程的状态模型

高并发编程实战第一阶段(Java线程基础API教程)_第2张图片

0、Java的线程状态一共有六种,因为Java把就绪和运行状态合并为了RUNNABLE状态

1、阻塞状态是线程阻塞在进入sychronized关键字修饰的方法或者代码块(获取锁)时状态。

2、特殊的阻塞在java.concurrent包中L ock接口的线程状态却是等待状态

3、线程执行wait方法,线程进入等待状态

2 多线程基础API

2.1 Thread类

java.lang
Class Thread
java.lang.Object
java.lang.Thread
All Implemented Interfaces:
Runnable
Direct Known Subclasses:
ForkJoinWorkerThread
    
//Java的Thread在java.lang包下
public class Thread
extends Object
implements Runnable
A thread is a thread of execution in a program. The Java Virtual Machine allows an application to have multiple threads of execution running concurrently.
Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority. Each thread may or may not also be marked as a daemon. When code running in some thread creates a new Thread object, the new thread has its priority initially set equal to the priority of the creating thread, and is a daemon thread if and only if the creating thread is a daemon.

//java 虚拟机启动的时候会启动一个非守护线程main线程。
When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:

The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.
All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method.
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:

Thread()
Allocates a new Thread object.
Thread(Runnable target)
Allocates a new Thread object.
Thread(Runnable target, String name)
Allocates a new Thread object.
Thread(String name)
Allocates a new Thread object.
Thread(ThreadGroup group, Runnable target) 
Allocates a new Thread object.
Thread(ThreadGroup group, Runnable target, String name)
Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group.
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group, and has the specified stack size.
Thread(ThreadGroup group, String name)
Allocates a new Thread object.

2.2 线程API之Runnable接口

Java是单继承的,不支持多继承,所以为了改变这种限制,Runnable接口的实现方式就比较有必要了。

Interface Runnable
All Known Subinterfaces:
RunnableFuture<V>, RunnableScheduledFuture<V>
All Known Implementing Classes:
AsyncBoxView.ChildState, ForkJoinWorkerThread, FutureTask, RenderableImageProducer, SwingWorker, Thread, TimerTask
Functional Interface:
This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

@FunctionalInterface
public interface Runnable
The Runnable interface should be implemented by any class whose instances are intended to be executed by a thread. The class must define a method of no arguments called run.
This interface is designed to provide a common protocol for objects that wish to execute code while they are active. For example, Runnable is implemented by class Thread. Being active simply means that a thread has been started and has not yet been stopped.

In addition, Runnable provides the means for a class to be active while not subclassing Thread. A class that implements Runnable can run without subclassing Thread by instantiating a Thread instance and passing itself in as the target. In most cases, the Runnable interface should be used if you are only planning to override the run() method and no other Thread methods. This is important because classes should not be subclassed unless the programmer intends on modifying or enhancing the fundamental behavior of the class.

Since:
JDK1.0
See Also:
Thread, Callable

2.3 线程创建

1、创建方式一

///两种方法去创建线程,第一种是创建一个Thread的子类,并且实现run函数
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows

代码:

public class NewThread extends Thread {
    @Override
    public void run() {
        System.out.println("启动线程...");
    }
    public static void main(String[] args) {
        NewThread thread =  new NewThread();
        thread.start();
    }
}

2、创建方式二

//第二种方式创建一个实现Runnable接口的类,并将该类的对象当作参数传递给新创建的线程类。
The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following:

代码:

public class RunThread implements Runnable {
    public void run() {
        System.out.println("实现Runnable接口,创建Thread..");
    }

    public static void main(String[] args) {
        RunThread  run =  new RunThread();
        Thread thread = new Thread(run);
        thread.start();
    }
}

3、两者的区别

​ 建议使用方式二,方式二是一种策略模式。将线程的run方法行为参数化函数式编程

比如可以按照以下的写法

 Thread thread = new Thread(() -> System.out.println("Lambda 方式创建线程"));
 thread.start();

4、模板方法模式

模板方法模式回顾:

public class TemplateMethod {

    public void print(){
        System.out.println("前操作............");
        method(); ///钩子函数
        System.out.println("后操作............");
    }
    
    public void method(){
        ///模板方法,子类根据需求去重写该方法。
    }
}

Thread类的模板方法模式,start0()是钩子函数,由C++语言写的本地方法,负责调用run方法。

  public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
      	//start函数不能重复执行,第二次执行报IllegalThreadStateException
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0(); ///模板方法的钩子函数,C++方法,负责调用run方法。
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

5、总结

1. JVM启动的时候会创建非守护线程main线程;
2. 调用start函数的时候至少有两个线程。
3. 线程的状态为:New、Runnable、Running、Blocked、Terminated。
4. 全新的 **KaTeX数学公式** 语法;

2.4 银行多线程叫号的案例

1、粗糙版本

///TicketWindow类
public class TicketWindow extends Thread {
    private static int index = 0;
    private static int MAX = 50;
    private String winName;

    TicketWindow(String winName) {
        this.winName = winName;
    }

    @Override
    public void run() {
        while (index <= MAX) {
            System.out.println(winName + "叫号:" + index++);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class BankVersion1 {
    public static void main(String[] args) {
        TicketWindow ticketWindow1 = new TicketWindow("一号窗口");
        ticketWindow1.start();
        TicketWindow ticketWindow2 = new TicketWindow("二号窗口");
        ticketWindow2.start();
        TicketWindow ticketWindow3 = new TicketWindow("三号窗口");
        ticketWindow3.start();
    }
}
一号窗口叫号:0
二号窗口叫号:1
三号窗口叫号:2
一号窗口叫号:3
二号窗口叫号:4
三号窗口叫号:5
一号窗口叫号:6
二号窗口叫号:7

2、数据和业务分离出来

代码一存在的问题:static的生命周期长,伴随着jvm的启动与销毁。而且所有的线程各玩个的数据。

public class TicketWindowRunnable implements Runnable {
    private int index = 0;
    private int MAX = 50;

    @Override
    public void run() {
        while (index <= MAX) {
            System.out.println(Thread.currentThread().getName() + "叫号:" + index++);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class BankVersion2 {
    public static void main(String[] args) {
        TicketWindowRunnable runnable = new TicketWindowRunnable();
        Thread thread1 =  new Thread(runnable,"一号窗口");
        thread1.start();
        Thread thread2 =  new Thread(runnable,"二号窗口");
        thread2.start();
        Thread thread3 =  new Thread(runnable,"三号窗口");
        thread3.start();
    }
}
一号窗口叫号:0
二号窗口叫号:1
三号窗口叫号:2
.....
三号窗口叫号:33
三号窗口叫号:34
二号窗口叫号:34
一号窗口叫号:34
一号窗口叫号:35
二号窗口叫号:36

方案二将数据与线程处理分开,多个线程操作同一份数据。但是这种方案有一个弊端,就是线程同步问题,观看程序输出即可看出。

2.5 线程安全问题

2.5.1 Servelt线程不安全

​ servelt是单例的,不是线程安全的。多个线程操作同一个servelt对象,当servelt类中定义类变量时会出现线程不安全问题。所以建议不要在Servelt中出现实例变量

struct1.x的Action是线程不安全的。

struct2.x的Action是线程安全的。

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.sentimentsys.service.SentimeSysServiceDao;
import com.sentimentsys.service.Impl.SentimeSysServiceDaoImpl;

public class LoginServelt extends HttpServlet {
    private String username;
    private String userpassword;
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        SentimeSysServiceDao service =new SentimeSysServiceDaoImpl();
        username=req.getParameter("username");
        userpassword =req.getParameter("userpwd");
        HttpSession session=req.getSession();
        if(service.Login(username, userpassword)){
            req.getRequestDispatcher("QueryNewsServelt").forward(req, resp);
            session.setAttribute("username", username);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
2.5.2 i–与System.out.println()的线程不安全问题

System.out.println()是线程安全的,方法内部是同步的,含有同步锁。

System.out.println(i++);//i++先于输出执行,所以这里会出现线程不安全问题。

2.6 ThreadGroup

高并发编程实战第一阶段(Java线程基础API教程)_第3张图片

0、ThreadGroup可以包含其他的ThreadGroup,也可以包含Thread。是一个树形结构

1、不指定ThreadGroup的话,设置为父线程的ThreadGroup。此时子线程和父线程在同一个ThreadGroup中。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
       ****************************************************
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();
		****************************************************
    }

2、main线程创建一个Thread之后,此时还有一个**Monitor线程(监听键盘输入等事件) **

public class ThreadTest {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("执行线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(5_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();

        ThreadGroup threadGroup = thread.getThreadGroup();
        System.out.println("thread的ThreadGroup:" + threadGroup.getName());
        System.out.println("main的ThreadGroup:" + Thread.currentThread().getThreadGroup().getName());
        System.out.println("此时的线程数量:" + threadGroup.activeCount());
        Thread[] currentThreads = new Thread[threadGroup.activeCount()];
        Thread.enumerate(currentThreads);
        for (Thread temp: currentThreads) {
            System.out.println(temp.getName());
        }
    }
}
#控制台打印
thread的ThreadGroup:main
main的ThreadGroup:main
此时的线程数量:3
main
Monitor Ctrl-Break
Thread-0
执行线程Thread-0

2.6 线程名字

不指定线程name,默认 “Thread-” + nextThreadNum() ,Thread类维护了一个静态变量,给线程默认起名字。

2.7 run方法

不指定Runnable接口,并且没有复写run方法。

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

2.8 stacksize

1、jvm内存结构回顾

高并发编程实战第一阶段(Java线程基础API教程)_第4张图片

2、设置栈的大小,但是是依赖平台,也许有的平台,这个参数不管用。不指定的时候默认为0,0表示默认不做处理。
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

2.9 守护线程

​ **在构建守护线程时,不能依靠finally快中的内容来确保执行关闭或清理资源的逻辑。**因为守护线程执行完毕时,finally块并没有被执行。

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("子线程运行");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main线程结束,她的守护线程也会跟着结束,无论是什么线程状态");
    }
}

控制台打印

子线程运行
main线程结束,她的守护线程也会跟着结束,无论是什么线程状态

2.10 优先级和Id

优先级是相对的,有的时候不一定管用;同时创建多个线程,将他们的优先级分别设置,则会按照优先级进行调用,高优先级的线程总是大部分先执行完,但是不代表高优先的线程全部先执行完。

线程的优先级有继承性,例如,A线程启动B线程,则B线程的优先级与A线程的是一样的。

线程Id是**tid = nextThreadID();**线程id是Thread类维护的。

2.11 Join/Detach

Java里面没有detach,只有Join,Join的意思是当前线程等待该线程结束。 Waits for this thread to die.

如果主线程需要等待多个子线程并发执行结束之后,在执行操作,则需要先把所有线程都start之后,再统一join

带参数的是,我等待你多少时间,如果你还没执行完,我就结束,你随意。

void join()
Waits for this thread to die.
void join(long millis)
Waits at most millis milliseconds for this thread to die.
void join(long millis, int nanos)
Waits at most millis milliseconds plus nanos nanoseconds for this thread to die.
    
//带参数     
public final void join(long millis)
                throws InterruptedException
Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever.
This implementation uses a loop of this.wait calls conditioned on this.isAlive. As a thread terminates the this.notifyAll method is invoked. It is recommended that applications not use wait, notify, or notifyAll on Thread instances.

Parameters:
millis - the time to wait in milliseconds
Throws:
IllegalArgumentException - if the value of millis is negative
InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
    
///不带参数的Join==join(0)==永远等待,直到结束。
public final void join()
                throws InterruptedException
Waits for this thread to die.
An invocation of this method behaves in exactly the same way as the invocation
    join(0)
Thread.currentThread().join();//死循环,当前线程等待当前线程结束

高并发编程实战第一阶段(Java线程基础API教程)_第5张图片

Join广泛用在,这种主线程创建了N个子线程,子线程并发执行,主线程需要等待所有的子线程结束之后,还进行一些后续的操作的场景。需要在启动N个子线程的时候,Join。

join的底层是用wait实现的

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

3、使用InheritableThreadLocal可使子线程继承父线程的值

InheritableThreadLocal重写了ThreadLocal的三个方法。

InheritableThreadLocal在set/get的时候其实是往Thread类的inheritableThreadLocals里面进行set/get。

继承的关键是,创建Thread的时候会将parent.inheritableThreadLocals转移到自己nheritableThreadLocals里面

InheritableThreadLocal的ThreadLocalMap继承是深拷贝的,父线程的ThreadLocalMap值改变不影响子线程的ThreadLocalMap

ThreadLocalMap里面是用的Entry[] table存的,TheadLocal类内部维护着一个hash函数,计算key到int索引的映射。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
   
    protected T childValue(T parentValue) {
        return parentValue;
    }
    
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}
public class ThreadLocal<T> {
    
      public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
     public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
}
class Thread implements Runnable {

    ThreadLocal.ThreadLocalMap threadLocals = null; 
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
      private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
      	// 省略N行代码.....
        // 这里是实现子线程继承父线程ThreadLocal的关键。 
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); // 创建可继承的ThreadLocal
        this.stackSize = stackSize;
         // 省略N行代码.....
    }
    // 创建可继承的ThreadLocal
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
   
}
static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        private Entry[] table; //ThreadLocalMap里面是用的Entry[] table存的

        private int size = 0;

        private int threshold; // Default to 0

        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

         // ThreadLocalMap的构造函数,可以看出这里是深拷贝
  	 	 private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        } 
 }

2.12 interrupt

interrupt的作用不是停止线程,而是给当前线程设置一个中断状态位,不是真正的停止线程。

1、如果是等待、睡眠、join的阻塞,此次中断状态将会被清除,并且会抛出一个中断异常

2、如果是I/O阻塞,中断状态会被设置,并且抛出一个ClosedByInterruptException(中断关闭的异常)

3、如果是选择器阻塞,线程的状态会被设置,并且立即返回。

//interrupt
public void interrupt()
Interrupts this thread.
Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

If this thread is blocked in an I/O operation upon an InterruptibleChannel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a ClosedByInterruptException.

If this thread is blocked in a Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeup method were invoked.

If none of the previous conditions hold then this thread's interrupt status will be set.

Interrupting a thread that is not alive need not have any effect.

Throws:
SecurityException - if the current thread cannot modify this thread
    
///判断是否打断的方法    
public boolean isInterrupted()
Tests whether this thread has been interrupted. The interrupted status of the thread is unaffected by this method.
A thread interruption ignored because a thread was not alive at the time of the interrupt will be reflected by this method returning false.

Returns:
true if this thread has been interrupted; false otherwise.
    
///静态的判断是否中断的方法    
public static boolean interrupted()
Tests whether the current thread has been interrupted. The interrupted status of the thread is cleared by this method. In other words, if this method were to be called twice in succession, the second call would return false (unless the current thread were interrupted again, after the first call had cleared its interrupted status and before the second call had examined it).
A thread interruption ignored because a thread was not alive at the time of the interrupt will be reflected by this method returning false.

Returns:
true if the current thread has been interrupted; false otherwise.
See Also:
isInterrupted()

///代码实现,该方法获取的当前线程的是否打断状态。
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
} 

4、interrupt只是将状态位置为interrupt,并且抛出异常,注意并不会真的去中断线程

(1)普通状态下中断

public class InterruptThread {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                ///想要中断的话,在这里做判断,如果此时中断了,那就跳出循环。
                ///其中Thread.interrupted()==currentThread().isInterrupted
                /*if (Thread.interrupted()) {
                    break;
                }*/
            }
        });
        thread.start();
        Thread.sleep(1000);
        System.out.println("尝试中断");
        System.out.println(thread.isInterrupted() ? "子线程已经中断" : "子线程未中断");
        thread.interrupt();
        System.out.println(thread.isInterrupted() ? "子线程已经中断" : "子线程未中断");
    }
}

程序输出.注意这里线程并不会中断,程序会一直在运行。

尝试中断
子线程未中断
子线程已经中断

(2)Sleep下中断

只要sleep和interrupt方法碰到一起就会出现异常,无论是sleep状态执行interrupt方法;还是interrupt方法给线程打了中断的标记之后再执行sleep方法。

public class SleepInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("子线程运行");
                try {
                    Thread.sleep(10); ///sleep下中断,抛出中断异常
                } catch (InterruptedException e) {
                    System.out.println("InterruptedException之后的状态:" + Thread.interrupted());
                    e.printStackTrace();
                }
                if (Thread.interrupted()) {
                    break;
                }
            }
        });
        thread.start();
        Thread.sleep(10);
        System.out.println("尝试中断");
        System.out.println(thread.isInterrupted() ? "子线程已经中断" : "子线程未中断");
        thread.interrupt();
        System.out.println(thread.isInterrupted() ? "子线程已经中断" : "子线程未中断");
    }
}

控制台输出.程序会一直运行,因为中断标志被清除了。

尝试中断
子线程未中断
子线程已经中断
子线程运行
InterruptedException之后的状态:false
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at stage1.chapter3.SleepInterrupt.lambda$main$0(SleepInterrupt.java:9)
	at java.lang.Thread.run(Thread.java:748)
子线程运行

(3)wait下中断

public class WaitInterrupt {
    private static  Object LOCK = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                synchronized (LOCK){
                    System.out.println("子线程运行");
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        System.out.println("InterruptedException之后的状态:" + Thread.interrupted());
                        e.printStackTrace();
                    }
                    if (Thread.interrupted()) {
                        break;
                    }
                }
            }
        });
        thread.start();
        Thread.sleep(10);
        System.out.println("尝试中断");
        System.out.println(thread.isInterrupted() ? "子线程已经中断" : "子线程未中断");
        thread.interrupt();
        System.out.println(thread.isInterrupted() ? "子线程已经中断" : "子线程未中断");
    }
}

控制台输出,跟Sleep类似:

子线程运行
尝试中断
子线程未中断
子线程已经中断
InterruptedException之后的状态:false
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at stage1.chapter3.WaitInterrupt.lambda$main$0(WaitInterrupt.java:12)
	at java.lang.Thread.run(Thread.java:748)
子线程运行

(3)join下打断,这里有点特殊,打断执行t.join()的线程,才会出现InterruptedException。因为sleep和wait都是针对thread而言的,就是在thread线程下睡眠和阻塞,而join是在main线程下join的。

public class JoinInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("子线程运行");
                System.out.println("尝试中断");
                System.out.println(Thread.currentThread().isInterrupted() ? "子线程已经中断" : "子线程未中断");
                Thread.currentThread().interrupt();//直接中断,退出循环,而且程序结束
                System.out.println(Thread.currentThread().isInterrupted() ? "子线程已经中断" : "子线程未中断");
                if (Thread.interrupted()) {
                    break;
                }
            }
        });
        thread.start();
        thread.join();

    }
}

以下代码,main抛出异常,而且程序还会继续运行。

public class JoinInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread main = Thread.currentThread();
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("子线程运行");
                System.out.println("尝试中断");
                System.out.println(Thread.currentThread().isInterrupted() ? "子线程已经中断" : "子线程未中断");
                main.interrupt();//抛出异常,程序还会继续运行
                System.out.println(Thread.currentThread().isInterrupted() ? "子线程已经中断" : "子线程未中断");
                if (Thread.interrupted()) {
                    break;
                }
            }
        });
        thread.start();
        thread.join();
    }
}

控制台输出:

子线程运行
尝试中断
子线程未中断
Exception in thread "main" 子线程未中断
子线程运行

注意: 中断可以理解为线程的一个标识位属性,interrupt函数就是将线程的interrupt置为true。抛出InterruptedException的线程SleepThread,其中标识位会在抛出异常之后被清除。

2.13 优雅的中断

1、设置一个标志位(变量)来表示是否中断。判断变量的值来决定是否退出线程。

2、interrupt方式,通过判断中断状态,进行线程的退出操作。见2.1.7的4(1)

if (Thread.interrupted()) {
     break;
 }

3、手写一个线程服务类,创建子线程执行并设置守护线程,通过控制守护线程的终止,从而控制子线程的终止

public class ThreadService {
    private Thread currentThread;
    private boolean finished = false;

    public void execute(Runnable runer) {
        currentThread = new Thread(() -> {
            Thread thread = new Thread(runer);
            thread.setDaemon(true);
            thread.start();
            try {
                thread.join();
                finished = true;
            } catch (InterruptedException e) {
                //e.printStackTrace(); //抛出异常,不继续抛出了。
                System.out.println("InterruptedException......");
            }
        });
        currentThread.start();
    }

    public void shutdown(int mills) {
        long endTime = System.currentTimeMillis() + mills;
        while (!finished) {
            if (System.currentTimeMillis() >= endTime) {
                System.out.println("shutdown");
                currentThread.interrupt();
                System.out.println(currentThread.isInterrupted());
                break;
            }
            try {
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
        }
        finished = false;
    }

    public static void main(String[] args) {
//        Runnable run1 = () -> {
//            while (true) System.out.println("-----");
//        };
        Runnable run2 = () -> {
            try {
                Thread.sleep(5000); ///线程实际运行时间小于关机的等待时间,程序会自动退出,因为while (!finished)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        ThreadService threadService = new ThreadService();
        threadService.execute(run2);
        threadService.shutdown(10000);
    }
}

2.14 暂停线程

​ 暂停线程意味着此线程还可以恢复运行,在Java多线程中,可以使用suspend方法暂停线程,使用resume方法恢复线程的执行,但是该方法已经被废弃了。

stop方法作用是销毁线程对象,如果想继续运行线程,则必须使用start方法重新启动线程,但是已经是作废了,在日常使用中不建议使用stop方法。因为如果暴力性的强制让线程停止,则一些清理工作可能得不到完成,或者数据添加不完整;调用stop方法会抛出java,lang.ThreadDeath异常

suspend方法用户让线程不再执行任务,线程对象并不销毁,在当前所执行的代码处暂停,未来还可以恢复运行

如果suspend方法和resume方法使用不当,极易造成公共同步对象被独占,其他线程无法访问公共同步对象的结果。

1、代码演示1如下:

public class SychroinzedObject {

    synchronized public void print() {
        System.out.println("do some thing.......");
        Thread.currentThread().suspend();
        //当前线程挂起了,但是还占用着对象锁,如果是多线程情况下,其他线程会一直阻塞在对象锁外面。
    }
}

2、代码演示2如下:

PrintStream对象的println函数被当前暂停,锁未释放,导致其他线程的打印不能执行

MyThread类

public class MyThread extends Thread {

    private long i = 0;

    @Override
    public void run() {
        while (true) {
            i++;
            System.out.println(i);
        }
    }

}

Main类

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.suspend();
        System.out.println("main thread is end...");
    }
}

控制台打印:

...........
34968
34969
34970
34971
34972
34973
34974
34975
34976
///程序还在执行,未终止,因为main线程抢不到PrintStream的打印锁。

​ 上面的情况是因为程序运行到System.out.println(i);的内部时停止了,同步锁不释放。当前PrintStream对象的println()方法一直呈“暂停”的状态,并且锁未被释放,main函数中的System.out.println(“main thread is end…”);也需要这把锁,但是锁被挂起的线程独占了,所以不能输出。显然suspend的作废是有必要的。

​ suspend方法还会造成数据的不完整,比如一个线程对对象进行赋值,分成了好几步,在中间的一步挂起了,这时候就造成了数据的不完整性(赋值没有完成)。

​ suspend和resume已经作废了,要想实现对线程的暂停与恢复的处理,可以使用wait和notify方法。

2.15 synchronized线程锁

0、synchronized修饰的代码块是串行化的,各个线程之间互斥。

1、synchronized修饰函数上,同步的是this锁,如果一个类里面多个函数用synchronized修饰,多个函数之间也会抢占(互斥)。多个线程调用同一对象中的不同名称的sychronized同步方法或sychronized(this)同步代码块时,调用的效果是按顺序执行(同步)。

2、static和synchronized修饰函数,同步的是类Class锁,如果一个类里面多个函数用static和synchronized修饰,则会产生类锁的互斥。

3、synchronized关键字实现同步的原理是使用了flag标记的ACC_SYN_CHRONIZED,当调用方法时,调用指令会检查方法的ACC_SYN_CHRONIZED访问标志是否设置,如果设置了,执行线程先持有同步锁,然后执行方法,最后在方法完成时释放锁。

测试代码:

public class SychroinzedObject {

    synchronized public void print() {
        System.out.println("do some thing.......");
        Thread.currentThread().suspend();//当前线程挂起了,但是还占用着对象锁
    }

    public void synchronizedBlock() {
        synchronized (this) {
            System.out.println("do some thing...");
        }
    }
}

编译指令:

javap -c -v stage1.chapter5.SychroinzedObject

字节码:

Classfile /G:/Java/IdeaProject/Thread/target/classes/stage1/chapter5/SychroinzedObject.class
  Last modified 2020-9-9; size 866 bytes
  MD5 checksum ec3f60ec1363e72073da1b13d3ad8a26
  Compiled from "SychroinzedObject.java"
public class stage1.chapter5.SychroinzedObject
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#25         // java/lang/Object."":()V
   #2 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #28            // do some thing.......
   #4 = Methodref          #29.#30        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Methodref          #31.#32        // java/lang/Thread.currentThread:()Ljava/lang/Thread;
   #6 = Methodref          #31.#33        // java/lang/Thread.suspend:()V
   #7 = String             #34            // do some thing...
   #8 = Class              #35            // stage1/chapter5/SychroinzedObject
   #9 = Class              #36            // java/lang/Object
  #10 = Utf8               
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               LocalVariableTable
  #15 = Utf8               this
  #16 = Utf8               Lstage1/chapter5/SychroinzedObject;
  #17 = Utf8               print
  #18 = Utf8               synchronizedBlock
  #19 = Utf8               StackMapTable
  #20 = Class              #35            // stage1/chapter5/SychroinzedObject
  #21 = Class              #36            // java/lang/Object
  #22 = Class              #37            // java/lang/Throwable
  #23 = Utf8               SourceFile
  #24 = Utf8               SychroinzedObject.java
  #25 = NameAndType        #10:#11        // "":()V
  #26 = Class              #38            // java/lang/System
  #27 = NameAndType        #39:#40        // out:Ljava/io/PrintStream;
  #28 = Utf8               do some thing.......
  #29 = Class              #41            // java/io/PrintStream
  #30 = NameAndType        #42:#43        // println:(Ljava/lang/String;)V
  #31 = Class              #44            // java/lang/Thread
  #32 = NameAndType        #45:#46        // currentThread:()Ljava/lang/Thread;
  #33 = NameAndType        #47:#11        // suspend:()V
  #34 = Utf8               do some thing...
  #35 = Utf8               stage1/chapter5/SychroinzedObject
  #36 = Utf8               java/lang/Object
  #37 = Utf8               java/lang/Throwable
  #38 = Utf8               java/lang/System
  #39 = Utf8               out
  #40 = Utf8               Ljava/io/PrintStream;
  #41 = Utf8               java/io/PrintStream
  #42 = Utf8               println
  #43 = Utf8               (Ljava/lang/String;)V
  #44 = Utf8               java/lang/Thread
  #45 = Utf8               currentThread
  #46 = Utf8               ()Ljava/lang/Thread;
  #47 = Utf8               suspend
{
  public stage1.chapter5.SychroinzedObject();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lstage1/chapter5/SychroinzedObject;

  public synchronized void print();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED // ACC_SYNCHRONIZED关键字标识sychronized
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String do some thing.......
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

         8: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Threa
d;
        11: invokevirtual #6                  // Method java/lang/Thread.suspend:()V
        14: return
      LineNumberTable:
        line 6: 0
        line 7: 8
        line 8: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lstage1/chapter5/SychroinzedObject;

  public void synchronizedBlock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter  // 同步代码快的起始标识
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #7                  // String do some thing...
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

        12: aload_1
        13: monitorexit  // 同步代码快的结束标识
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
      LineNumberTable:
        line 12: 0
        line 13: 4
        line 14: 12
        line 15: 22
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   Lstage1/chapter5/SychroinzedObject;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class stage1/chapter5/SychroinzedObject, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "SychroinzedObject.java"

4、sychronized锁String的时候要小心,String是存在常量池的,编译的时候会在常量池创建字符串,之后在使用与常量池相等的字符串都是使用的一个地址的数据。

String str = "123";
sychronized(str){
	//这样的话锁的是常量池
}

5、sychronized是可重入锁(自己可以再次获取自己的内部锁),当一个线程得到一个对象锁后,再次请求此对象锁时是可以得到该对象锁的。锁重入支持继承,当存在父子类继承关系时,子类是完全可以通过锁重入调用父类的同步方法。

public class widget{
    public sychronized void dosomething(){
        ...
    }
}
public class LoggingWdget extends Widget{
    public sychronized void dosomething(){
        // do something ....
        super.dosomething(); //如果不是可重入锁,那么这里会死锁。
    }
}

6、一个线程执行的代码出现异常时,其持有的锁会自动释放。

7、重写方法不会继承sychronized,子类继承父类的函数并重写,重写后的函数不会继承sychronized。

8、System.out.println();的println方法也是同步的

public void println() {
        newLine();
}

private void newLine() {
        try {
            synchronized (this) {
                ensureOpen();
                textOut.newLine();
                textOut.flushBuffer();
                charOut.flushBuffer();
                if (autoFlush)
                    out.flush();
            }
        }
        catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        }
        catch (IOException x) {
            trouble = true;
        }
}

9、sychronized也会防止指令重排序

10、sychronized具有可见性、原子性和禁止指令重排序

2.16 死锁

不重复造轮子,这位大哥关于死锁的博客很棒:https://www.cnblogs.com/xiaoxi/p/8311034.html

1、我持有一个锁,还想要你的锁。而且你我的锁都不愿意放弃。

2、三个人形成一个环,大家都持有自己的锁,想要右边人的锁。A持有自己的锁,想要B的锁;B持有自己的锁,想要C的锁;C持有自己的锁,想要A的锁,形成一个环。

3、如何检测死锁

jps //查看Java的进程ID
jstack Id //即可查看死锁情况

2.17 线程间的通信

2.17.1 API

wait、notify、notifyAll都是Object类的函数

并且这三个函数必须结合sychronized使用。This method should only be called by a thread that is the owner of this object’s monitor

必须执行完notify()方法所在的sychronized代码块才释放锁。

wait和sleep的区别:

1、wait是object的方法,sleep是Thread的方法

2、sleep不会释放监视器锁,而wait可以释放监视器锁,并且会加入到锁的等待队列。

3、wait的使用依赖于监视器(sychronizad),sleep不依赖

4、sleep不需要去唤醒,但是wait需要。

java.lang.Object public final void wait()
throws InterruptedException
//造成当前线程等待,直到另外的线程调用notify或者notifyAll方法。
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).
    
//当前线程必须拥有该对象的监视器,这个线程放弃了她的监视器的所有权。
The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
           synchronized (obj) {
               while (<condition does not hold>)
                   obj.wait();
               ... // Perform action appropriate to condition
           }
//这个方法应该仅在该对象的监视器持有线程下调用。也就是wait函数必须和synchronized锁一起使用。    

This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.

Throws:
IllegalMonitorStateException – if the current thread is not the owner of the object's monitor.
InterruptedException – if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
java.lang.Object public final void notify()
    
///唤醒一个等待该对象监视器的线程,如果有很多线程正在等待,那么就从他们里面选择一个进行唤醒。
    
Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.
The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.
This method should only be called by a thread that is the owner of this object's monitor. A thread becomes the owner of the object's monitor in one of three ways:
By executing a synchronized instance method of that object.
By executing the body of a synchronized statement that synchronizes on the object.
For objects of type Class, by executing a synchronized static method of that class.
Only one thread at a time can own an object's monitor.

Throws:
IllegalMonitorStateException – if the current thread is not the owner of this object's monitor
java.lang.Object public final void notifyAll()
///唤醒监视该对象的所有线程。
Wakes up all threads that are waiting on this object's monitor. A thread waits on an object's monitor by calling one of the wait methods.
The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object. The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object.
This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.

Throws:
IllegalMonitorStateException – if the current thread is not the owner of this object's monitor.
2.17.2 单生产者单消费者
public class ProduceConsumer {

    private Object LOCK = new Object();
    private boolean isConsumed = true;
    private int i = 0;

    public void produce() throws InterruptedException {
        synchronized (LOCK) {
            if (isConsumed) {
                i++;
                System.out.println("produce->" + i);
                isConsumed = false;
                LOCK.notify();
            } else {
                LOCK.wait();
            }

        }
    }

    public void consume() throws InterruptedException {
        synchronized (LOCK) {
            if (!isConsumed) {
                i--;
                System.out.println("consume->" + i);
                isConsumed = true;
                LOCK.notify();
            } else {
                LOCK.wait();
            }
        }
    }

    public static void main(String[] args) {
        ProduceConsumer produceConsumer = new ProduceConsumer();
        Thread produce = new Thread(()->{
            while (true){
                try {
                    produceConsumer.produce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread consume = new Thread(()->{
            while (true){
                try {
                    produceConsumer.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        produce.start();
        consume.start();
    }
}

以上代码如果用在多生产者多消费者情况下,会出现所有线程都阻塞的情况。因为你在唤醒线程的时候不一定是唤醒的哪一个线程,如果一个消费者线程A结束,又唤醒一个消费者线程B,B持有锁但是啥也做不了,也不能唤醒其他线程,那么所有的线程都会阻塞,B消费者等生产者生产,但是生产者线程阻塞。

2.17.3 多生产者多消费者
public class MutilProduceConsumer {

    private Object LOCK = new Object();
    private boolean isConsumed = true;
    private int i = 0;

    public void produce() throws InterruptedException {
        synchronized (LOCK) {
            while (!isConsumed) { ///如果改成if会出现重复生产
                LOCK.wait();//因为notifyAll之后,阻塞的线程从这的下一行开始执行。
            }
            i++;
            System.out.println(Thread.currentThread().getName() + "->produce:" + i);
            isConsumed = false;
            LOCK.notifyAll();
        }
    }

    public void consume() throws InterruptedException {
        synchronized (LOCK) {
            while (isConsumed) { ///如果改成if会出现重复消费
                LOCK.wait();
            }
            i--;
            System.out.println(Thread.currentThread().getName() + "->consume:" + i);
            isConsumed = true;
            LOCK.notifyAll();
        }
    }

    public static void main(String[] args) {
        MutilProduceConsumer produceConsumer = new MutilProduceConsumer();
        Stream.of("P1", "P2", "P3").forEach(name -> {
            new Thread(() -> {
                while (true) {
                    try {
                        produceConsumer.produce();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, name).start();
        });


        Stream.of("C1", "C2", "C3").forEach(name -> {
            new Thread(() -> {
                while (true) {
                    try {
                        produceConsumer.consume();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, name).start();
        });
    }
}

控制台打印

P1->produce:1
C1->consume:0
P3->produce:1
C1->consume:0
P1->produce:1
C2->consume:0
P3->produce:1
C1->consume:0
P1->produce:1
.....

2.18 自定义lock锁

2.19 钩子和异常

1、钩子,程序出现异常的时候执行的线程,以便于后期的资源通知和释放。所以说一些程序比如tomcat在关闭的时候不会立即关闭,因为他会进行后续的通知和资源关闭。

public class Hook {
    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            ///程序出现异常、Ctrl+C、程序内部退出、Kill的时候触发。
            ///kill -9 * 强制退出程序不会触发这个时间。
            System.out.println("程序出现错误,有机会实现一些后续的处理操作");
        }));
        后续代码...
    }
}

2、给线程添加异常处理函数

public class UncaughtException {

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println(9 / 0);
        });
        thread.setUncaughtExceptionHandler((t, e) -> {
            System.out.println("exception in thread " + t.getName());
            System.out.println("exception " + e);
        });
        thread.start();
    }
}

控制台

exception in thread Thread-0
exception java.lang.ArithmeticException: / by zero

2.20 自定义线程池

1、版本1,实现基本功能。固定大小的线程池对任务进行处理。

public class SimpleThreadPool {

    private static final int DEFAULT_MAX_THREAD_SIZE = 10;

    private static final LinkedList<Runnable> TASK_QUEUE = new LinkedList<Runnable>();

    private static final String THREAD_POOL_PREFIX = "SIMPLE_THREAD_POOL-";

    private static final int DEFAULT_MAX_TASK_SIZE = 2000;

    private final List<WorkerThread> THREAD_QUEUE = new ArrayList<WorkerThread>();

    private static final DiscardPolicy DEFAULT_DISCARD_POLICY = () -> {
        throw new DiscardException("Discard this Task...");
    };

    private int seq = 0;

    private int threadSize;

    private int taskSize;

    private DiscardPolicy discardPolicy;

    private ThreadGroup threadGroup = new ThreadGroup("simpleThreadGroup");

    private volatile boolean isDestory = false;

    public SimpleThreadPool(int threadSize, int taskSize, DiscardPolicy discardPolicy) {
        this.threadSize = threadSize;
        this.taskSize = taskSize;
        this.discardPolicy = discardPolicy;
        init();
    }

    public SimpleThreadPool() {
        this(DEFAULT_MAX_THREAD_SIZE, DEFAULT_MAX_TASK_SIZE, DEFAULT_DISCARD_POLICY);
    }

    private void init() {
        for (int i = 0; i < threadSize; i++) {
            WorkerThread WorkerThread = new WorkerThread(threadGroup, THREAD_POOL_PREFIX + seq++);
            WorkerThread.start();
            THREAD_QUEUE.add(WorkerThread);
        }
    }


    public void submit(Runnable runner) throws Exception {
        if (isDestory) {
            throw new RuntimeException("The thread pool is already destoryed and not allow to submit");
        }
        synchronized (TASK_QUEUE) {
            if (TASK_QUEUE.size() > taskSize)
                discardPolicy.discard();
            TASK_QUEUE.addLast(runner);
            TASK_QUEUE.notifyAll();
        }
    }

    public void shutdown() throws InterruptedException {
        System.out.println("shutdown");
        while (!TASK_QUEUE.isEmpty()) {
            Thread.sleep(10);
        }
        int size = THREAD_QUEUE.size();
        while (size > 0) {
            for (WorkerThread task : THREAD_QUEUE) {
                if (task.TASK_STATE == TaskState.BLOCK) {
                    task.interrupt();
                    task.close();
                    size--;
                } else {
                    Thread.sleep(10);
                }
            }
        }
        this.isDestory = true;
        System.out.println("The Thread Pool shutdown...");
    }

    public int getThreadSize() {
        return threadSize;
    }

    public int getTaskSize() {
        return taskSize;
    }

    private enum TaskState {FREE, RUNNING, BLOCK, DEAD}

    private static class DiscardException extends RuntimeException {
        public DiscardException(String message) {
            super(message);
        }
    }

    private static interface DiscardPolicy {
        public void discard() throws DiscardException;
    }


    private static class WorkerThread extends Thread {
        private volatile TaskState TASK_STATE = TaskState.FREE;

        public WorkerThread(ThreadGroup threadGroup, String threadName) {
            super(threadGroup, threadName);
        }

        @Override
        public void run() {
            OUTER:
            while (TASK_STATE != TaskState.DEAD) {
                Runnable runner = null;
                synchronized (TASK_QUEUE) {
                    while (TASK_QUEUE.size() == 0) {
                        try {
                            TASK_STATE = TaskState.BLOCK;
                            TASK_QUEUE.wait();
                        } catch (InterruptedException e) {
                            //e.printStackTrace();
                            break OUTER;
                        }
                    }
                    runner = TASK_QUEUE.removeFirst();
                }
                if (runner != null) {
                    TASK_STATE = TaskState.RUNNING;
                    runner.run();
                    TASK_STATE = TaskState.FREE;
                }
            }
        }

        public void close() {
            TASK_STATE = TaskState.DEAD;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SimpleThreadPool simpleThreadPool = new SimpleThreadPool();
        for (int i = 0; i < 40; i++) {
            final int j = i;
            try {
                simpleThreadPool.submit(() -> {
                    System.out.println("The runnable " + j + "be served as " + Thread.currentThread().getName() + " start");
                    try {
                        Thread.sleep(1000);
                        System.out.println("The runnable " + j + "be served as " + Thread.currentThread().getName() + " end");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            } catch (Exception e) {
//                e.printStackTrace();
                System.out.println(e);
            }
        }
        Thread.sleep(9000);
        simpleThreadPool.shutdown();
        try {
            simpleThreadPool.submit(() -> {
                System.out.println("尝试再次提交...");
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

你可能感兴趣的:(Java,java)