进程与线程
进程是程序的动态的执行过程,比如我们在一个linux系统下跑一个Java程序时比如用java -jar xx.jar,那它就是一个进程。一般情况下,我们需要关心的仅仅是Java这一个进程,对于linux系统下自带的进程无需关心,所以我们本质上更关注的是更细粒度的单位–线程。
操作系统在启动一个进程时,会给它分配指定的内存空间,也就是分配资源给它。如果把进程比喻一个大工厂的话,那线程就是在工厂里干活的工人,每个工人都被规划好自己的工作,并且分配资源的使用权限,这个权限是分共享与私有的,比如厕所就是共享的,而工牌就是私有的。
下面就是线程使用资源的权限:
共享 | 私有 |
---|---|
数据段,代码段 | 线程id |
文件描述符表 | 上下文,(包含各种计数器的值、程序计数器和栈指针) |
每种信号的处理方式 | 每个线程都有自己的栈结构 |
当前工作的目录 | errno变量(当线程异常退出时的错误退出码) |
用户id和线程组id | 信号屏蔽字 |
调度优先级 |
Java的线程
为了更好的利用CPU,Java线程是与内核级线程绑定的(在JDK1.2前的线程,早期的Classic虚拟机中,基于一种被称为绿色线程的用户线程来实现。但在JDK1.3之后,商用Java虚拟机普遍开始使用内核线程模型
),因为操作系统创建与回收系统的成本比较大,所以我们的程序会通过复用线程来达到减少开销成本。
Java的线程是用Thread类来代表的,我们主要分析这个类的情况。首先看他的类构造与相关的接口:
public class Thread implements Runnable {
}
public interface Runnable {
void run()
}
很明显Runnable是开放给使用者使用的接口,而在创建并启用一个线程时,我们不仅仅需要关心用这个线程做什么,还需要关心这个线程有什么状态,Thread类有个枚举类State,它一共有以下状态:
- NEW: 至今尚未启动的线程处于这种状态。
- RUNNABLE:正在 Java 虚拟机中执行的线程处于这种状态。
- BLOCKED:受阻塞并等待某个监视器锁的线程处于这种状态。
- WAITING:无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
- TIMED_WAITING:等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
- TERMINATED:已退出的线程处于这种状态。
在给定时间点上,一个线程只能处于一种状态,而这些状态只是Java规定的状态,它们并没有反映所有操作系统线程状态。
我们通过对Thread类的方法来控制状态的改变,常用的方法如下:
- start(): 未调用该方法时,是NEW的状态。当调用后,代表启动该线程,状态变成RUNNABLE,并且调用run()方法。
- run():通常要重写Thread类中的此方法,将创建的线程要执行的操作写在此方法中。
- yield():释放当前CPU的执行权,暂停当前正在执行的线程对象,并执行其他线程。因为只是重新回归到RUNNABLE,让出CPU的时间轮片,所以它可能还是第一个抢到执行权的,并不会有暂停的过程。
- join():在线程a中调用线程b的join(),此时线程a就进入BLOCKED状态,直到线程b完全执行完以后,线程a才结束BLOCKED状态,重新进入RUNNABLE。
- sleep(long millitime):让当前线程"睡眠"指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是BLOCKED状态。
- interrupt()、interrupted(): 设置线程的中断状态,让用户自己选择时间地点去结束线程。后者判断是否已经中断。
- stop():
- wait()、notify()、notifyAll(): 这些是Object类的方法,wait让线程进入WAITING状态,而后者是让线程重新进入RUNNABLE。当给wait()设上时间后,就是进入TIMED_WAITING
- isAlive():判断当前线程是否存活。
juc
前面铺垫了一下基本的知识,进入正题。juc就是jdk下的java.util.concurrent包下的内容,主要分为以下模块:
- tools(工具类):又叫同步工具类。
- CountDownLatch(闭锁) 是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
- CyclicBarrier(栅栏) 之所以叫barrier,是因为是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 ,并且在释放等待线程后可以重用。
- Semaphore(信号量) 是一个计数信号量,它的本质是一个“共享锁“。信号量维护了一个信号量许可集。线程可以通过调用 acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。
- executor(执行者):是Java里面线程池的顶级接口,但它只是一个执行线程的工具,真正的线程池接口是ExecutorService。
- ThreadPoolExecutor:ExecutorService的实现类
- ScheduledExecutorService 解决那些需要任务重复执行的问题
- ScheduledThreadPoolExecutor 周期性任务调度的类实现
- atomic(原子性包):是JDK提供的一组原子操作类,包含有AtomicBoolean、AtomicInteger、AtomicIntegerArray等原子变量类,他们的实现原理大多是持有它们各自的对应的类型变量value,而且被volatile关键字修饰了。这样来保证每次一个线程要使用它都会拿到最新的值。
- locks(锁包):是JDK提供的锁机制,相比synchronized关键字来进行同步锁,功能更加强大,它为锁提供了一个框架。
- ReentrantLock 它是独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。
- ReentrantReadWriteLock 它包括子类ReadLock和WriteLock。ReadLock是共享锁,而WriteLock是独占锁。
- LockSupport 它具备阻塞线程和解除阻塞线程的功能,并且不会引发死锁。
- collections(集合类):主要是提供线程安全的集合。
- ArrayList对应的高并发类是CopyOnWriteArrayList等等。
- HashSet对应的高并发类是 CopyOnWriteArraySet等等
- HashMap对应的高并发类是ConcurrentHashMap等等
下面将会按照这个顺序来讲解系列。