一、概念
进程和线程
进程是程序的一次执行,是程序在内存的一个数据集合上运行的过程,是系统进行资源分配和调度的独立单元。每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。
线程是进程中一个单一顺序的控制流,是操作系统进行运算调度的最小单位,包含在进程之中,进进程中的实际运作单位。一个进程可以并发多个线程,每个线程执行不同的任务
线程自己基本不拥有系统资源,除了一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是可与属于同一进程的其他线程共享进程所拥有的系统资源
二、Java线程创建
Java中有三种创建线程的方式
1、继承Thread类
- 定义Thread类的子类,并重写该类的run方法
- 创建该子类的实例
- 调用实例的start()方法来启动线程
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0;i<50;i++){
if(i==29&&"Thread-0".equals(Thread.currentThread().getName())){
try{
Thread.sleep(3000);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
2、实现Runnable接口创建线程
- 定义Runnable接口的实现类,并重写run方法
- 创建实现类实例,并作为实例化Thread时的构造参数
- 调用Thread实例的start方法来启动线程
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0;i<50;i++){
if(i==29&&"Thread-0".equals(Thread.currentThread().getName())){
try{
Thread.sleep(3000);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
public static void main(String[] args) {
Runnable r = new MyRunnable();
new Thread(r).start();
new Thread(r).start();
}
}
3、实现Callable接口来创建线程
- 定义Callable接口的实现类,并重写call方法
- 创建Callable实现类的实例,并作为实例化FutureTask的构造参数
- 将FutureTask实例作为实例化Thread的构造参数
- 调用Thread实例的start()方法来启动线程
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int ans = 0;
for(int i=0;i<50;i++){
if(i==29&&"Thread-0".equals(Thread.currentThread().getName())){
try{
Thread.sleep(3000);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
ans += i+1;
System.out.println(Thread.currentThread().getName()+"-"+i);
}
return ans;
}
public static void main(String[] args) {
Callable<Integer> c = new MyCallable();
FutureTask<Integer> ft1 = new FutureTask<>(c);
FutureTask<Integer> ft2 = new FutureTask<>(c);
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
t1.start();
t2.start();
while(t1.getState() != Thread.State.TERMINATED
||t2.getState() != Thread.State.TERMINATED){
}
try {
System.out.println(ft1.get() + "-"+ ft2.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、线程创建的三种方式对比
1、继承Thread类
优点:编写简单,只需要继承Thread,重写run方法,并实例化即可启动线程,如果要访问当前的线程,不需要调用Thread.currentThread(),使用this即可
缺点:由于子类继承了Thread类,那就不能再继承其他类了
2、实现Callable和Runnable接口
优点:只是实现了接口,所以还可以继承其他类,除此之外,这种方式可以共享一个target对象,即可实现多个线程来处理同一份系统资源
缺点:编程稍微有点复杂,如果要访问当前线程,需要调用Thread.currentThread()
四、Callable和Runnable的区别
Callable | Runnable | |
---|---|---|
重写方法 | call() | run() |
返回值 | 有 | 无 |
异常 | 可以抛出异常 | 不可以抛出异常 |
五、Java线程的状态
通过java.lang.Thread.State
枚举类的源码可以看出,Java线程有如下几个状态
public enum State {
/**
* 新建状态 实例化Thread后,启动之前的状态.
*/
NEW,
/**
* 运行状态 分为就绪和正在运行. 线程启动之后,首先转化为就绪状态
* 位于可运行线程池中,等待获取CPU使用权,转化为正在运行状态
*/
RUNNABLE,
/**
* 阻塞状态 线程进入synchronized块或方法获取不到锁.
* 或者调用对象的wait方法得到唤醒之后重新进入synchronized块或方法
* 获取不到锁时转化为阻塞状态
*/
BLOCKED,
/**
* 等待状态 调用以下方法后转化为等待状态.
* Object#wait() Object.wait} with no timeout
* #join() Thread.join} with no timeout
* LockSupport#park() LockSupport.park
*
* 等待状态的线程需要另一个线程执行一些操作来唤醒当前线程
*
* 比如, 一个线程调用了某个object的wait方法后,等待另一个线程
* 调用该object的notify或者notifyAll方法来唤醒当前线程
*
* 在当前线程调用某个线程的join方法后,等待这个线程执行完成后自动唤醒当前线程
*/
WAITING,
/**
* 超时等待状态 用一个指定的时间参数调用以下方法后转化为超时等待状态.
* sleep Thread.sleep
* Object#wait(long) Object.wait} with timeout
* #join(long) Thread.join} with timeout
* LockSupport#parkNanos LockSupport.parkNanos
* LockSupport#parkUntil LockSupport.parkUntil
* 等待时间超过指定的时间后自动唤醒当前线程
*/
TIMED_WAITING,
/**
* 终止状态 线程执行完成或者异常终止后的状态.
*/
TERMINATED;
}
Java线程的状态转化图如下
六、几个方法的比较
1、Thread#sleep(long):当前线程调用此方法,进入TIMED_WAITING状态,但是并不释放已获取的锁,指定的时间过后进入就绪状态
2、Thread#yield():当前线程调用此方法,立即交出CPU使用权,不释放锁资源,进入就绪状态,等待系统的调度,并不保证其他线程一定会拿到CPU的时间片,有可能当前线程进入就绪状态后又获取到CPU时间片,该方法与sleep方法类似,但是该方法达不到让步的目的。
3、thread#join()/join(long):当前线程中调用线程thread的join方法,当前线程进入WAITING或者TIMED_WAITING状态,且不会释放已获取的锁,直到线程thread执行完成或者时间到后,当前线程进入就绪状态。
4、object#wait()/wait(long):当前线程获取object对象锁后,调用object的wait方法,释放object对象锁,转化为TIMED_WAITING状态,进入等待队列,依靠其他线程调用该object对象的notify或者notifyAll来唤醒,或者是wait(long)时间到后自动唤醒当前线程
5、object#notify()/notifyAll():notify方法唤醒在此对象监控器上等待的单个线程,具有单一任意性,notifyAll方法唤醒在此对象监控器上等待的所有线程