一、定义
(一)进程
进程是程序在一个数据集上的一次动态执行的过程,是系统进行资源分配和调度的一个独立单位。
(二)线程
是进程的一个实体,是cpu调度和分派的基本单位,他是比进程更小的能够独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源。
(三)区别
- 一个线程只能属于一个进程,而一个进程可以拥有多个线程。
- 线程是进程工作的最新单位。
- 一个进程会分配一个地址空间,进程与进程之间不共享地址空间。即不共享内存。
- 同一个进行下的不同的多个线程,共享父进程的地址空间。
- 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
- 每个进程互相独立,不影响主程序的稳定性,子进程崩溃不影响其他进程。但一个线程的崩溃可能影响到整个程序的稳定性。
(四)并发与并行
并发是指一个处理器同时处理多个任务。
并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。
二、多线程
(一)java线程的状态
(1)创建
当new了一个线程,并没有调用start之前,线程处于创建状态;
(2)就绪
当调用了start之后,线程处于就绪状态,这是,线程调度程序还没有设置执行当前线程;
(3)运行
线程调度程序执行到线程时,当前线程从就绪状态转成运行状态,开始执行run方法里边的代码;
(4)阻塞
线程在运行的时候,被暂停执行(通常等待某项资源就绪后在执行,sleep、wait可以导致线程阻塞),这是该线程处于阻塞状态;
(5)死亡
当一个线程执行完run方法里边的代码或调用了stop方法后,该线程结束运行
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
//创建
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
//运行
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
//阻塞
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
//等待
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
//超时等待
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
//终止
TERMINATED;
}
(二) Java实现多线程有哪几种方式。
(1)继承Thread类创建线程
定义Thread类的子类,并重写Thread类的run()方法,创建子类对象(即线程对象),调用线程对象的start()方法来启动该线程
(2)实现Runnable接口创建线程
定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法同样是该线程的执行体。创建该Runnable实现类的实例,并将此实例作为Thread的target来创建Thread对象。最后调用thread对象的start()方法来启动该线程。
多个Thread可以同时加载一个Runnable
(3)实现Callable接口创建新线程
创建Callable接口的实现类,并实现call()方法,该方法有返回值;创建Callable实现类的实例,使用FutureTask来包装Callable对象和call()方法的返回值;使用FutureTask作为Thread类的target创建并启动线程;调用FutureTask对象的get()方法返回子线程执行结束后的返回值。
两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
(三)Callable和Future
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果
Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
(四)sleep和wait的区别。
1,这两个方法来自不同的类分别是Thread和Object
2,最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
4,sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
(五)ThreadLocal的了解,实现原理。
ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。
每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。
在该类中,我觉得最重要的方法就是两个:set()和get()方法。当调用ThreadLocal的get()方法的时候,会先找到当前线程的ThreadLocalMap,然后再找到对应的值。set()方法也是一样。
(六)volitile关键字的作用,原理。
保证内存可见性
使用volatile修饰的变量会强制将修改的值立即写入主存, 每次从内存中取值,不从缓存中什么的拿值。这就保证了用 volatile修饰的共享变量,每次的更新对于其他线程都是可见的。volatile只能保证可见性和有序性,不具备原子性。也不能保证线程安全。
还是以最常用的i++来说吧,包含3个步骤
1,从内存读取i当前的值
2,加1
3,把修改后的值刷新到内存
所以这里有两个问题,可见性和原子性,viloate只能保证可见性,即步骤1每次都重新读,步骤3每次都立即刷新到主内存。但1,2之间仍然会被中断,多个线程交叉修改,所以仍然不安全
5. synchronized关键字的用法,优缺点。
在Java中,每一个对象都拥有一个锁(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
三、线程池
线程池中有很多可用线程资源,如果需要就直接从这个池子里拿。当不用的时候,放入池子中,线程池会自动帮我们管理。
使用线程池的优势:
- 降低资源消耗
- 提高响应速度
- 提高管理性
(一) 线程池的参数
corePoolSize | 核心线程数量 |
maximumPoolSize | 线程最大线程数 |
workQueue | 阻塞队列 |
keepAliveTime | 线程没有任务时最多保持多久时间终止 |
unit | keepAliveTime的时间单位 |
threadFactory | 线程工厂 |
rejectHandler | 拒绝策略 |
系统默认的拒绝策略有以下几种:
- AbortPolicy:为线程池默认的拒绝策略,该策略直接抛异常处理。
- DiscardPolicy:直接抛弃不处理。
- DiscardOldestPolicy:丢弃队列中最老的任务。
- CallerRunsPolicy:将任务分配给当前执行execute方法线程来处理。
(二) 线程池的执行流程:
提交任务后,线程池先判断线程数是否达到了核心线程数(corePoolSize)。如果未达到线程数,则创建核心线程处理任务;如果达到了就判断任务队列是否满了。如果没满,则将任务添加到任务队列中;否则,就判断线程数是否达到了最大线程数。如果未达到,则创建非核心线程处理任务;否则,就抛出异常。
(三)常见线程池:
newCacheThreadPool():可缓存线程池
newScheduledThreadPool(int n):定时线程池 可以控制线程池内线程定时或周期性执行某任务的线程池
newSingleThreadExecutor():单线程化的线程池
newFixedThreadPool(int n):指定线程数量的线程池
(四)线程池数量如何设置
如果是CPU密集型应用,则线程池大小设置为N+1
如果是IO密集型应用,则线程池大小设置为2N+1
四、线程通信
Java中线程通信协作的最常见的两种方式:
一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
线程间直接的数据交换:
三.通过管道进行线程间通信:1)字节流;2)字符流
1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。如果当前线程没有这个对象的锁就调用wait()方法,则会抛出IllegalMonitorStateException.
3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;
这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。
上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。
(一)ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。
- Condition是个接口,基本的方法就是await()和signal()方法;
- Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
- 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
Conditon中的await()对应Object的wait();
Condition中的signal()对应Object的notify();
Condition中的signalAll()对应Object的notifyAll()
(二)通过管道进行线程间通信:1)字节流;2)字符流
Java中有各种各样的输入、输出流(Stream),其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据。
一个线程发送数据到输出管道,另一个线程从输入管道读数据。
(三)进程同步的方法
五、锁
(一)Lock接口的实现类
(1)Lock接口中定义的方法
1、void lock()
获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态
2、void lockInterruptibly()
如果当前线程未被中断,则获取锁。
3、Condition newCondition()
返回绑定到此 Lock 实例的新 Condition 实例。
4、boolean tryLock()
仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true
。如果锁不可用,则此方法将立即返回值 false
。
5、boolean tryLock(long time, TimeUnit unit)
如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
6、void unlock()
释放锁。在等待条件前,锁必须由当前线程保持。调用 Condition.await()
将在等待前以原子方式释放锁,并在等待返回前重新获取锁。
Lock接口有三个实现类分别是 ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock。后面两个是内部类。
(2)ReentrantLock
ReentrantLock是一个可重入的互斥锁 Lock.
ReentrantLock的构造方法接受一个可选的公平 参数。当设置为 true
时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。也就是说这里可以设置锁的类型为公平锁还是非公平锁。但是,需要注意的是公平锁的情况下,也不能完全确保公平,它总是趋向公平的情况。
ReentrantLock类中还定义了Lock接口之外的方法,例如int getHoldCount() 、boolean hasQueuedThreads() 、boolean hasWaiters(Condition condition)等等,这些方法可以查询当前锁的状态。
(3)ReentrantReadWriteLock实现类
ReentrantReadWriteLock 望文生义就是可重复进入读写锁,读取锁 和 写入锁 ,
读取锁含义:在同一时刻可以被多个读线程访问,线程之间不存在阻塞;
写入锁含义: 在多个写入线程同一时刻进行访问写入时,只有一个线程可以写入,其他写入线程和所有读取线程都会被阻塞。
(二)可重入锁的用处及实现原理
Synchronized和ReentrantLock两者都是可重入锁.
通俗来说:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。
重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁.
(三)ABC三个线程如何保证顺序执行。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ABC { private static Lock lock = new ReentrantLock();//通过JDK5中的锁来保证线程的访问的互斥 private static int state = 0; static class ThreadA extends Thread { @Override public void run() { for (int i = 0; i < 10;) { lock.lock(); if (state % 3 == 0) { System.out.print("A"); state++; i++; } lock.unlock(); } } } static class ThreadB extends Thread { @Override public void run() { for (int i = 0; i < 10;) { lock.lock(); if (state % 3 == 1) { System.out.print("B"); state++; i++; } lock.unlock(); } } } static class ThreadC extends Thread { @Override public void run() { for (int i = 0; i < 10;) { lock.lock(); if (state % 3 == 2) { System.out.print("C"); state++; i++; } lock.unlock(); } } } public static void main(String[] args) { new ThreadA().start(); new ThreadB().start(); new ThreadC().start(); } }
六、JUC
(一)JUC辅助类
(1)CountDownLatch
减法计数器。
(2)CyclickBarrier
加法计数器。
(3)Semaphore
信号量,用于多个共享资源的互斥应用。
(二)阻塞队列
(1)BlockingQueue
常用于多线程并发处理、线程池。
(2)SynchronousQueue同步队列
同步队列 没有容量,也可以视为容量为1的队列;
七、AQS —— AbstractQueuedSynchronizer
ReentrantLock、CountDownLatch等源码中都有使用。实现了一个FIFO的队列。底层实现的数据结构是一个双向链表。
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求的线程设置为有效,并且将共享资源设置为锁定状态。如果共享资源被占用,就通过一个基于一个双向链表的队列阻塞等待。
AQS的同步状态——State。AQS中维护了一个名为state的字段,意为同步状态,是由Volatile修饰的,用于展示当前临界资源的获锁情况。
AQS的大致实现思路
AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。 接着会不断的循环尝试获取锁,条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。