在Typora上写完之后,发布到简书上,很多样式都变了
一、线程与进程
进程:进程是操作系统对一个正在运行的程序的一种抽象,因此可以说每个应用程序就是一个进程,它是操作系统进行资源分配和调度的一个独立单位。
线程:每个进程中可以拥有多个线程,运行在进程的上下文中,并享有同样的代码和全局数据.线程是CPU调度和分配的基本单位,它是比进程更小的独立运行体。
二、多线程创建方式
Java线程创建方式主要有三种:
-
实现Runnable接口
package cn.wuxiaoer.thread; public class ThreadDemo implements Runnable{ public static void main(String[] args) { new Thread(new ThreadDemo(),"线程1").start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
-
继承Thread类
不推荐这种方法,主要是因为Java是单继承的,如果继承了Thread,那么该类就不能再继承其他的类。
package cn.wuxiaoer.thread; public class ThreadDemo extends Thread{ public static void main(String[] args) { new Thread(new ThreadDemo(),"线程1").start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
-
实现Callable接口(这个是有返回值的)
Callable接口实际上是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更加强大的功能.
实现Callable接口有以下好处:
- Callable可以在任务结束的时候提供一个返回值,Runnable无法提供这个功能
- Callable的call方法可以抛出异常,而Runnable的run方法不能抛出异常。
package cn.wuxiaoer.thread; import java.util.concurrent.*; public class ThreadDemo implements Callable<String> { public static void main(String[] args) { ExecutorService threadPool=Executors.newSingleThreadExecutor(); Future<String> future=threadPool.submit(new ThreadDemo()); try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }finally { threadPool.shutdown(); } } @Override public String call() throws Exception { return "线程1"; } }
注意上面代码中的**ExecutorService**类和**Executors**类,这两个类都属于**JUC**中的类,我会在后面的文章中介绍,同时会介绍如何手动创建线程池,阿里巴巴的Java手册中也不建议使用上面的方法创建线程池,上面的例子只是为了演示如何创建线程。
下图来至于《阿里巴巴Java开发手册终极版》
三、线程状态
- 线程的状态
Jdk源码中线程的状态如下:
public enum State {
NEW,//新创建
RUNNABLE,//可运行
BLOCKED,//被阻塞
WAITING,//等待
TIMED_WAITING,//计时等待
TERMINATED;//被终止
}
- 获取线程的状态:
package cn.wuxiaoer.thread;
public class ThreadDemo implements Runnable {
public static void main(String[] args) {
Thread thread=new Thread(new ThreadDemo());
System.out.println(thread.getState());//NEW
thread.start();
System.out.println(thread.getState());//RUNNABLE
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getState());//TIMED_WAITING
}
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-
这六个状态之间的关系如下:
图片来自于《Java核心技术卷一》第14章
四、线程优先级、守护线程、线程组
-
线程的优先级
在Java程序中每个线程都有一个优先级,默认情况下,一个线程继承它的父线程的优先级。
可以通过Thread的setPriority方法设置线程的优先级,一定在start之前设置。
package cn.wuxiaoer.thread; public class ThreadDemo implements Runnable { public static void main(String[] args) { Thread thread=new Thread(new ThreadDemo()); thread.setPriority(Thread.MAX_PRIORITY); } @Override public void run() { } }
Thread中有三个个静态常量,我们可以将优先级设置为MIN_PRIORITY和MAX_PRIORITY之间的任意值,当设置完线程的优先级以后,每当线程调度有机会调度线程时,会优先选择优先级比较高的线程。
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
-
守护线程
守护线程主要是为其它线程提供服务,比如说GC线程。
可以通过Thread的setDaemon方法设置线程为守护线程。当只有守护线程时,虚拟机就退出了,所以下面的代码"守护线程完成!"永远也打印不出来。
package cn.wuxiaoer.thread; public class ThreadDemo implements Runnable { public static void main(String[] args) { Thread thread=new Thread(new ThreadDemo()); thread.setDaemon(true); thread.start(); System.out.println("主线程完成!"); } @Override public void run() { try { Thread.sleep(3000); System.out.println("守护线程完成!"); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
未捕获异常处理器和线程组
未捕获异常处理器
在上面我们知道线程的run方法不能抛出任何受查异常,但是非受查异常会导致线程终止。这样就有可能导致一些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等。
那么如何在线程终止之前来处理这些未捕获的run方法抛出的异常呢?
- Thread中有一个叫做UncaughtExceptionHandler的处理器接口,通过实现这个接口创建一个异常处理类。
- 通过使用setUncaughtExceptionHandler方法为线程安装一个默认的处理器。
package cn.wuxiaoer.thread; public class UncaughtExceptionHandlerImpl implements Thread.UncaughtExceptionHandler { /** * @param t 当前抛出异常的线程 * @param e 未捕获的异常 */ @Override public void uncaughtException(Thread t, Throwable e) { System.out.println(t.getName()+"抛出了异常:"+e.getStackTrace()); } }
package cn.wuxiaoer.thread; public class ThreadDemo implements Runnable { public static void main(String[] args) { Thread thread= new Thread(new ThreadDemo(),"线程1"); thread.setUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl()); thread.start(); } @Override public void run() { int i=10/0; System.out.println(i); } }
如果不为线程设置一个处理器,那么该线程的处理器就是该线程的线程组(ThreadGroup)对象。
private ThreadGroup group; public UncaughtExceptionHandler getUncaughtExceptionHandler() { return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; }
线程组
线程组(ThreadGroup)是一个可以统一管理的线程集合。它是一个实现Thread的UncaughtExceptionHandler接口的类。默认情况下创建的所有线程都属于相同的线程组。也可以手动设置线程的线程组。(建议不要在自己的程序中使用线程组!)
如果当前线程的线程组为空,则使用父线程的线程组:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ................
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
//................
}
手动设置线程组:
package cn.wuxiaoer.thread;
public class ThreadDemo implements Runnable {
public static void main(String[] args) {
ThreadGroup group=new ThreadGroup("group1");
new Thread(group,new ThreadDemo()).start();
}
@Override
public void run() {
}
}
五、线程中断
没有可以强制线程终止的方法,可以使用线程的interrupt方法来请求终止线程。
interrupt:向线程发送中断申请,可能会抛出InterruptedException异常。
isInterrupted:判断当前线程(正在执行这个命令的线程)是否被中断,会将当前线程的中断状态设置为false。
Thread.interrupted:判断当前线程是否被中断。
package cn.wuxiaoer.thread;
public class ThreadDemo implements Runnable {
public static void main(String[] args) {
Thread thread=new Thread(new ThreadDemo(),"线程1");
thread.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
thread.interrupt();
}
@Override
public void run() {
//检测线程是否被终止
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName());
}
System.out.println("线程被终止了!");
}
}
六、Sleep、join、yield
-
Sleep与wait
- Sleep属于Thread类,是指让线程“暂停被调度一段时间”,或者说叫“挂起”一段时间,整个sleep过程除了修改“挂起“状态之外,不会动任何其他的”资源“。这些资源包括任何持有的任何形式的锁。
- wait属于Object类,当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
-
join
官方的说明:Waits for this thread to die.
- Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
package cn.wuxiaoer.thread; public class ThreadDemo implements Runnable { public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(new ThreadDemo(),"线程1"); t1.start(); //在主线程中调用了线程t1的join方法,那么必须要等t1线程执行完。 t1.join(); Thread t2=new Thread(new ThreadDemo(),"线程2"); t2.start(); } @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+i); } } }
-
yield
- 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
- 调用了
yield()
方法后,线程依然处于RUNNABLE
状态,线程不会进入堵塞状态。 - 可以使用yield实现自旋锁
package cn.wuxiaoer.thread; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class CharacterPrint implements Runnable { private int state = 1; private Lock lock = new ReentrantLock(); @Override public void run() { for (int i = 1; i <= 3; i++) { createThread(i); } } private void createThread(final int i) { new Thread(()->{ try { while (state != i) { Thread.yield(); }; lock.lock(); for (int j = 1; j <= 50; j++) { System.out.println(Thread.currentThread().getName()+":"+j); } state = i+1; } finally { lock.unlock(); } }, "thread" + i).start(); } public static void main(String[] args) { new CharacterPrint().run(); } }
线程状态图,图片来自于《知乎》
七、ThreadLocal、WeakReference
-
线程局部变量(ThreadLocal)
ThreadLocal为每个使用该变量的线程分配一个**独立的变量副本**。所以每一个线程都可以独立地改变自己的副本,而**不会影响**其他线程所对应的副本。
ThreadLocal主要有以下四个核心的方法:
- get():返回此线程局部变量的当前线程副本中的值。
- withInitial():创建一个局部变量,其初始值通过给定的Supplier生成。默认的是初始值是空。
protected T initialValue() { return null; }
- remove():移除此线程局部变量当前线程的值,移除之后,get方法将返回null
- set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
package cn.wuxiaoer.thread; import java.util.concurrent.ConcurrentHashMap; public class ThreadDemo implements Runnable { private static final ThreadLocal<Integer> number=ThreadLocal.withInitial(()->{return 0;}); public static void main(String[] args) throws InterruptedException { for (int i = 1; i <=3; i++) { new Thread(new ThreadDemo(),"线程-"+i).start(); } } @Override public void run() { System.out.println(Thread.currentThread().getName()+"初始值:"+number.get()); for (int i = 1; i <=10; i++) { number.set(i); System.out.println(Thread.currentThread().getName()+":"+number.get()); } } }
备注:想更深入的理解可以参考:https://blog.csdn.net/zhoudaxia/article/details/37397575
-
java弱引用(WeakReference)
当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收
首先看一下垃圾回收对象的条件:
- 没有任何引用指向它
- GC被运行
package cn.wuxiaoer.thread; public class WeakReferenceDemo { public static void main(String[] args) { //强引用,在垃圾回收执行后依然存在 User user=new User(); user.setName("new"); System.gc(); System.out.println(user.getName()); //弱引用,在垃圾回收后就被回收 java.lang.ref.WeakReference<User> weakReference = new java.lang.ref.WeakReference<>(new User()); weakReference.get().setName("weakReference"); System.gc(); System.out.println(weakReference.get().getName()); } }
备注:深入理解可以参考:https://www.cnblogs.com/zjj1996/p/9140385.html;https://www.jianshu.com/p/964fbc30151a
-
ThreadLocal与WeakReference的关系
可以参考:
上面只是WeakReference的一些简单使用和说明,至于ThreadLocal中为什么使用WeakReference,或者不使用有什么问题,我也没看明白。
八、线程同步
条件对象和锁是线程同步的强大工具。
-
为什么会有线程同步问题?
当两个或者多个线程同时访问共享数据并且它们尝试同时更改它们时,就会发生竞争条件,当发生竞争条件时就可能会导致共享数据错误,而为了避免多线程引起的对共享数据的错误,就需要采用同步机制来解决这种问题。
线程同步的两个重要元素:条件对象和锁。
-
条件对象
要用一个条件来管理那些已经获得了一个锁确不能做有用工作的线程,它就是条件对象。
-
锁
在HotSpot JVM实现中,锁有个专门的名字:**对象监视器(Object Monitor)**。**锁主要有以下作用:**
- 保护代码片段,任何时刻只能有一个线程执行被保护的代码。
- 锁可以管理视图进入被保护代码段的线程。
- 锁可以拥有一个或多个相关的条件对象。
- 每个条件对象管理那些已经进入被保护的代码段但是还不能运行的线程。
Java中常见的锁:
- synchronized
- java.util.concurrent.lock.ReentrantLock
- java.util.concurrent.lock.ReentrantReadWriteLock
锁的分类:
-
可重入锁
不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。
可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。设计了加锁次数,以在解锁的时候,可以确保所有加锁的过程都解锁了,其他线程才能访问。
-
公平锁 / 非公平锁
公平锁:多个线程按照申请锁的顺序来获取锁。可以在创建锁的时候设置为公平锁,但是一般不建议这么干。例如:Lock lock=new ReentrantLock(true);
非公平锁:指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程先获得锁。如 synchronized、Lock等默认的都是非公平锁。
-
独享锁 / 共享锁
独享锁:指锁一次只能被一个线程所持有。synchronized、java.util.concurrent.locks.ReentrantLock 都是独享锁
共享锁:指锁可被多个线程所持有。ReadWriteLock 返回的 ReadLock 就是共享锁
-
悲观锁 / 乐观锁
悲观锁:一律会对代码块进行加锁,如 synchronized、java.util.concurrent.locks.ReentrantLock
乐观锁:默认不会进行并发修改,通常采用 CAS 算法不断尝试更新。
-
自旋锁
自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环占有、浪费 CPU 资源。
-
synchronized
synchronized关键字可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,同时synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能),它的的作用主要有三个:
- 原子性:是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行;
- 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
- 有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”;
synchronized关键字最主要有以下3种应用方式:归根结底它上锁的资源只有两类:一个是对象,一个是类。
修饰实例方法,作用于当前实例(this)加锁,进入同步代码前要获得当前实例的锁。
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。
-
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
如下图:
备注:深入了解synchronized可以参考:https://blog.csdn.net/qq_36934826/article/details/95978700;
https://juejin.cn/post/6844903600334831629
-
Lock
Lock是java.util.concurrent.locks下的接口,它的代表实现类是ReentrantLock(可重入锁)。
Lock接口中有以下方法:
public interface Lock { // 获取锁,如果锁已被其他线程获取,则进行等待。 void lock(); //当通过这个方法去获取锁时,如果线程 正在等待获取锁,则这个线程能够 响应中断,即中断线程的等待状态。 void lockInterruptibly() throws InterruptedException; // 它表示用来尝试获取锁,如果获取成功,则返回true;如果获取失败(即锁已被其他线程获取),则返回false,不会进行阻塞,带参数的方法表示拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。 boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 释放锁 void unlock(); // 返回绑定到此 Lock 实例的新 Condition 实例 Condition newCondition(); }
Lock与synchronized的区别:
- Synchronized持有了锁,除非执行完或者遇到异常时,才能释放锁,无法收到释放锁。
- Lock可以使用手动调用unlock()方法进行解锁。
- Synchronized读和读也会发生冲突,无法区分共享锁和独占锁。
- Lock可以使用ReadWriteLock解决读写锁的问题。
- Synchronized会自动释放锁,但是Lock必须手动释放锁,并且lock()方法和unlock方法必须一一对应。
Condition用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效.
Condition接口包含以下方法:
public interface Condition { //使用该方法将线程await后,调用thread.interrupt()不会报错 void awaitUninterruptibly(); //将当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态,在返回时,该方法返回了所剩毫微秒数的一个估计值,以等待所提供的 nanostimeout 值的时间,如果超时,则返回一个小于等于 0 的值。 long awaitNanos(long nanosTimeout) throws InterruptedException; //使用该方法将线程await后,此时调用thread.interrupt()会报错. void await() throws InterruptedException; //使用该方法将线程await后,此时调用thread.interrupt()会报错,可指定时间单位 boolean await(long time, TimeUnit unit) throws InterruptedException; //使用该方法将线程await后,此时调用thread.interrupt()会报错,指定截止时间 boolean awaitUntil(Date deadline) throws InterruptedException; //对应Object的notify(),唤起一个线程 void signal(); //对应Object的notifyAll(),唤起所有的线程 void signalAll(); }
注意:调用Condition的await()和signal()方法,都必须在lock保护之内。
package cn.wuxiaoer.thread; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreadDemo { private static int i=0; private static int sum=0; static Lock lock=new ReentrantLock(); static Condition condition=lock.newCondition(); public static void main(String[] args) throws InterruptedException { new Thread(()->{ add(); },"线程1").start(); new Thread(()->{ add(); },"线程2").start(); new Thread(()->{ minus(); },"线程3").start(); } private static void minus(){ lock.lock(); try { while (true&sum<=10){ if (i<=0){ condition.awaitUninterruptibly(); } i--; sum++; System.out.println(Thread.currentThread().getName()+"减少了1:"+i); condition.signalAll(); } }finally { lock.unlock(); } } private static void add(){ lock.lock(); try { while (true&sum<=10){ if (i>0){ condition.awaitUninterruptibly(); } i++; sum++; System.out.println(Thread.currentThread().getName()+"增加了1:"+i); condition.signalAll(); } }finally { lock.unlock(); } } }
-
Volatile
volatile通常被比喻成"轻量级的synchronized",volatile是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块,并且volatile只能保证有序性和可见性,不能保证原子性的。
有序性:由于处理器优化和指令重排等,而volatile可以禁止指令重排优化,保障程序执行的顺序按照代码的先后顺序执行。
-
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
常见使用场景:
- 状态量标记
- DCL懒汉式单例模式(双重检验锁)
九、生产者与消费者
生产者和消费者问题是典型的线程通信的问题。解决这类问题主要有两种方法:管程法和信号灯法。
线程通信可以使用synchronized + wait + notify,也可以使用Lock+ Condition的await+signal
-
管程法
管程法的主要思想是生产者 : 负责生产数据,消费者 : 负责处理数据,但是消费者不能直接使用生产者的数据 , 他们之间有个 “ 缓冲区生产者将生产好的数据放入缓冲区 , 消费者从缓冲区拿出数据。
package cn.wuxiaoer.thread; public class ThreadDemo { public static void main(String[] args) throws InterruptedException { SynContainer synContainer=new SynContainer(); new Thread(()->{ for (int i = 0; i < 100; i++) { synContainer.push(new Message("消息标题-"+i,"消息内容")); } },"生产者线程").start(); new Thread(()->{ for (int i = 0; i < 100; i++) { synContainer.pop(); } },"消费者线程").start(); } } class SynContainer{ private Message[] messages=new Message[10]; private int num=-1; public synchronized void push(Message message){ if (num>=messages.length-1){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num++; messages[num]=message; System.out.println("生产者生产了消息:"+message.getTitle()); this.notifyAll(); } public synchronized Message pop(){ if (num<0){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } Message message=messages[num]; num--; this.notifyAll(); System.out.println("消费者消费了消息:"+message.getTitle()); return message; } } class Message{ private String title; private String content; Message(String title,String content){ this.title=title; this.content=content; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
-
信号灯法
信号灯法利用标识控制线程等待和唤醒,类似于信号灯,红灯性绿灯停。
package cn.wuxiaoer.thread;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Question question=new Question();
new Thread(()->{
for (int i = 0; i < 100; i++) {
question.ask("问题-"+i);
}
},"生产者线程").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
question.answer("答案:"+i);
}
},"消费者线程").start();
}
}
class Question{
private boolean flag=false;
private String content;
public synchronized void ask(String content){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.content=content;
flag=!flag;
this.notifyAll();
System.out.println("问出来了问题:"+content);
}
public synchronized void answer(String answer){
if ((!flag)||content==null){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
flag=!flag;
this.notifyAll();
System.out.println("回答了问题:"+content+"-"+answer);
}
}