实验楼-Java简明教程-多线程

多线程实现

Java 中的 Thread 类就是专门用来创建线程和操作线程的类。

创建线程

创建线程的方法:

1. 继承 Thread 类并重写它的 run() 方法,然后用这个子类来创建对象并调用 start() 方法。

2. 定义一个类并实现 Runnable 接口,实现 run() 方法。

总的来说就是线程通过 start() 方法启动而不是 run() 方法,run() 方法的内容为我们要实现的业务逻辑。

线程变量

ThreadLocal,即线程变量,是一个以 ThreadLocal 对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个 ThreadLocal 对象查询到绑定在这个线程上的一个值。 可以通过 set(T) 方法来设置一个值,在当前线程下再通过 get() 方法获取到原先设置的值。

线程同步

当多个线程操作同一个对象时,就会出现线程安全问题,被多个线程同时操作的对象数据可能会发生错误。线程同步可以保证在同一个时刻该对象只被一个线程访问。

Synchronized

关键字 synchronized 可以修饰方法或者以同步块的形式来进行使用,它确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,保证了线程对变量访问的可见性和排他性。它有三种使用方法:

* 对普通方式使用,将会锁住当前实例对象。

* 对静态方法使用,将会锁住当前类的 Class 对象。

* 对代码块使用,将会锁住代码块中的对象。

java.util.concurrent

java.util.concurrent 包是 java5 开始引入的并发类库,提供了多种在并发编程中的适用工具类。包括原子操作类,线程池,阻塞队列,Fork/Join 框架,并发集合,线程同步锁等。

Lock 与 Unlock

JUC 中的 ReentrantLock 是多线程编程中常用的加锁方式,ReentrantLock 加锁比 synchronized 加锁更加的灵活,提供了更加丰富的功能。

死锁

在多线程环境下,锁的使用非常频繁,但是它会带来一下问题,比如死锁。当死锁发生时,系统将会瘫痪。比如两个线程互相等待对方释放锁。

饥饿

饥饿是指一个可运行的进程尽管能继续执行,但被调度器无限期地忽视,而不能被调度执行的情况。

比如当前线程处于一个低优先级的情况下,操作系统每次都调用高优先级的线程运行,就会导致当前线程虽然可以运行,但是一直不能被运行的情况。

线程生命周期

线程的声明周期共有 6 种状态,分别是:新建 New、运行(可运行)Runnable、阻塞Blocked、计时等待Timed Waiting、等待Waiting 和终止Terminate。

当你声明一个线程对象时,线程处于新建状态,系统不会为它分配资源,它只是一个空的线程对象。

调用 start() 方法时,线程就成为了可运行状态,至于是否是运行状态,则要看系统的调度了。

调用了 sleep() 方法、调用 wait() 方法和 IO 阻塞时,线程处于等待、计时等待或阻塞状态。

当 run() 方法执行结束后,线程也就终止了。

ArrayBlockingQueue

ArrayBlockingQueue 是由数组支持的有界阻塞队列。位于 java.util.concurrent 包下。

首先看看其构造方法:

构造方法

描述

public ArrayBlockingQueue(int capacity)

构造大小为 capacity 的队列

public ArrayBlockingQueue(int capacity, boolean fair)

指定队列大小,以及内部实现是公平锁还是非公平锁

public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)

指定队列大小,以及锁实现,并且在初始化是加入集合 c

入队常用方法:

入队方法

队列已满

队列未满

add

抛出异常

返回 true

offer

返回 false

返回 true

put

阻塞直到插入

没有返回值

出队常用方法:

出队方法

队列为空

队列不为空

remove

抛出异常

移出并返回队首

poll

返回 null

移出并返回队首

take

阻塞直到返回

移出并返回队首

生产者消费者模式

生产者消费者模式是多线程编程中非常重要的设计模式,生产者负责生产数据,消费者负责消费数据。生产者消费者模式中间通常还有一个缓冲区,用于存放生产者生产的数据,而消费者则从缓冲区中获取,这样可以降低生产者和消费者之间的耦合度。

举个例子来说吧,比如有厂家,代理商,顾客,厂家就是生产者,顾客就是消费者,代理商就是缓冲区,顾客从代理商这里买东西,代理商负责从厂家处拿货,并且销售给顾客,顾客不用直接和厂家打交道,并且通过代理商,就可以直接获取商品,或者从代理商处知道货物不足,需要等待。

线程池


线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。


由于 Java 创建和销毁线程都会带来资源上的销毁,所以线程池可以帮助我们复用线程,减少资源消耗。


 


 


Java 线程池可以通过 Executors 工具类创建,Executors 常用方法:


* newFixedThreadPool(int nThreads): 创建一个固定大小为 n 的线程池


* newSingleThreadExecutor(): 创建只有一个线程的线程池


* newCachedThreadPool(): 创建一个根据需要创建新线程的线程池


 


总结

本节主要内容是对 Java 多线程进行讲解,主要包含以下知识点:

* 多线程的实现

* 线程变量

* 线程同步

* Lock 与 Unlock

* 死锁

* 线程生命周期

* ArrayBlockingQueue

* 生产者消费者模式

* 线程池