线程是什么
首先得从程序,进程的概念讲起
计算机程序是指一组指示计算机或其他具有消息处理能力设备每一步动作的指令,通常用某种程序设计语言编写,运行于某种目标体系结构上
进程,是指计算机中已运行的程序
也就是说,程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程<进程≈程序,一个进程中的多个线程可以共享相同的内存单元
创建方式
在java中,把线程抽象为Thread对象,创建线程有三种方式
- 继承Thread类,重写run方法
class MyThread extends Thread {
@Override
public void run() {
//方法体
}
}
- 实现Runnable或Callable接口
class MyThread implements Runnable {
@Override
publicvoid run() {
//方法体
}
}
class MyThread implements Callable {
@Override
public Object call() throws Exception {
return null;
}
}
这两种方式的优劣对比:
- 方式1,线程类已经继承了Thread类,不能再继承其他类
- 方式2,线程类只实现了Runnable接口或者Callable接口,还可以继承其他类
- 方式2,多个线程可以共享一个target对象,非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,体现了面向对象的编程思想
综合来说,还是推荐使用方式2
3.线程池
即使采用方式2创建线程有很多优势,在实际生产中还是不推荐使用,最好的创建方式就是--不用自己手动创建,而是使用线程池管理线程
相对于手动创建线程,线程池的优势在于减少创建新线程的时间和重复利用线程池中的线程
传送门--线程池
线程生命周期
先来看线程的状态
JDK文档上面明确指定,Thread有6种状态:
- NEW
尚未启动的线程的线程状态
也就是Thread对象刚被创建时但还未执行start()时的状态
- RUNNABLE
可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待操作系统中的其他资源,比如处理器
执行了start()方法后的状态,注意RUNNABLE表示操作系统中定义的线程状态中的READY+RUNNNING
- BLOCKED
等待监视器锁的阻塞线程的线程状态。处于阻塞状态的线程正在等待监视器锁进入同步块/方法,或者在调用Object.wait后重新进入同步块/方法
这是jdk文档的解释,也就是说线程在等待监视器锁的情况就是阻塞态,或者在调用Object.wait后重新进入同步块/方法这句话的意思是, 在调用wait()后,线程状态从RUNNABLE->WAITING,当被其他线程调用notify()或notifyAll()唤醒后,先进入RUNNABLE状态,这个时候可能拿不到锁,所以就会进入BLOCKED阻塞态
- WAITING
等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
Object.wait
with no timeout
Thread.join
with no timeout
LockSupport.park
WAITING状态只能从RUNNABLE状态变过来
- TIMED_WAITING
具有指定等待时间的等待线程的线程状态。线程由于调用下列方法之一,并指定了正等待时间,因此处于定时等待状态:
Thread.sleep
Object.wait
with timeout
Thread.join
with timeout
LockSupport.parkNanos
LockSupport.parkUntil
- TERMINATED
终止线程的线程状态。线程已经完成执行
下面这张图很好地描述了java线程状态之间的转换
线程间的可见性
虽然多个线程间是共享数据的,但不一定一个线程修改了共享数据,另一个线程马上就能感知,这就需要volatile关键字来保证线程间的可见性,详情请参考volatile关键字
线程同步机制
多个线程并发访问共享数据,必然会出现数据不一致的情况,那么就需要线程同步机制来保证数据一致性
内置锁
内置锁是java从语法级别上支持的一种同步机制,主要由synchronized关键字实现,有两种实现方式:
1.同步代码块
实现方式如下
synchronized(监视器对象) {
...
}
//常见写法有两种
synchronized(this) {
...
}
synchronized(this.getClass()) {
...
}
监视器对象可以是任意对象,但必须是同一个对象才能达到同步效果
- 同步方法
同步方法又分为普通同步方法和静态同步方法
- 普通同步方法
public synchronized void method() {
}
//相当于
synchronized(this) {
...
}
- 静态同步方法
public static synchronized void method() {
}
//相当于
synchronized(this.getClass()) {
...
}
显式锁
显式锁是Java5中并发工具包提供的,详情请参考线程八锁
CAS
CAS是一种无锁算法,类似于乐观锁,同时兼顾并发性能和数据一致性,详情请参看CAS
死锁问题
线程通信
线程之间靠通信来协作,主要是靠这三个方法
-
wait
使拥有当前监视器对象的线程进入WAITING状态,并释放监视器所有权,直到同一监视器对象调用notify()或notifyAll()才可以唤醒,重新持有监视器,也可以调用wait(long time)使进入TIMED_WAITING,时间到了就唤醒.被唤醒后进入RUNNABLE状态,很有可能拿不到监视器,从而进入BLOCKED状态
虽然,wait()和Thread.sleep()有着看似相同的效果,都会让当前线程进入WAITING状态,但注意和Thread.sleep()的区别:
1.Thread.sleep()不会释放监视器,而wait()会释放监视器
2.wait()只能在同步方法或同步代码块里调用,Thread.sleep()可以在任意地方调用
还有注意可能出现的虚假唤醒问题,这个方法应该总是在循环中使用
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
-
notify
唤醒正在此对象监视器上等待的单个线程。如果有任何线程正在等待这个对象,则选择其中一个线程被唤醒。选择是任意的,由实现决定.在唤醒该线程后,该线程进入RUNNABLE状态,但因为在唤醒时,唤醒线程持有监视器,所以很有可能等待线程即使被唤醒也拿不到监视器,从而进入BLOCKED状态 -
notifyAll
唤醒正在此对象监视器上等待的所有线程
需要注意的是,这三个方法都需要在同步方法或同步代码块里调用,因为调用的是同步监视器对象的方法;而且是由相同的监视器对象调用.
juc里的线程通信工具类
juc包提供了线程通信的工具类,开箱即用,传送门--juc里的线程通信工具类
网友评论