Java并发编程—基础知识
文章目录
什么是线程
线程是操作系统调度的最小单元,多个线程同时执行,能够提高程序的性能。
线程和进程
现代操作系统在运行一个程序时,会为其启动一个进程;在一个进程里可以创建多个线程,这些线程都拥有各自的线程私有内存:程序计数器、Java虚拟机栈、本地方法栈,且可以访问线程共享内存的共享变量。一个程序进程中多个线程的切换(或多核并行)执行,提高了程序的性能。
线程的状态和切换
Java线程在运行时的生命周期中有6种状态进行切换,在同一时刻,线程只能处于其中一个状态:
- NEW:新建状态,通过某种方式创建的,还未启动(调用
Thread.start()
方法进行启动)的线程处于该状态; - RUNNABLE:运行状态,包括操作系统的 运行中和准备就绪两种状态;
- BLOCKED:阻塞状态,在临界区等待获取锁;
- WAITING:无限时等待状态,等待其他线程显示唤醒,使用
Object.wait()
等方法进入该状态,在该状态下不会被分配CPU时间片; - TIME_WAITING:超时等待状态,同WAITING状态,不过可以在指定时间内自我唤醒,使用
Object.wait(long ms)
等方法进入该状态; - TERMINATED:终止状态,线程已经结束执行。
线程的创建
Java中有多种方法可以实现线程的创建:
- 继承
Thread
类;
public class ThreadCreate1 extends Thread{
private int count;
@Override
public void run() {
super.run();
System.out.println("实现了Thread的线程,这里是线程具体实现");
}
}
- 实现
Runable
接口;
public class ThreadCreate2 implements Runnable{
private int count;
@Override
public void run() {
System.out.println("继承了Runnable的线程,这里是线程具体实现");
}
}
- 实现
Callable
接口;
public class ThreadCreate3 implements Callable<String> {
private int count;
@Override
public String call() throws Exception {
System.out.println("继承了Callable的线程,这里是线程具体实现");
return "Callable";
}
}
- 继承
FutureTask
类。
ppublic class ThreadCreate4 extends FutureTask<String> {
private int count;
//演示用,实际中不会这么写
public ThreadCreate4() {
super(() -> {
System.out.println("实现了FutureTask的线程,这里是线程具体实现");
return "Callable";
});
}
}
演示类:
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
ThreadCreate1 threadCreate1 = new ThreadCreate1();
ThreadCreate2 threadCreate2 = new ThreadCreate2();
ThreadCreate3 threadCreate3 = new ThreadCreate3();
ThreadCreate4 threadCreate4 = new ThreadCreate4();
threadCreate1.start();
new Thread(threadCreate2).start();
Future<String> future3 = executor.submit(threadCreate3);
Future<?> future4 = executor.submit(threadCreate4);
}
结果:
实现了Thread的线程,这里是线程具体实现
继承了Runnable的线程,这里是线程具体实现
继承了Callable的线程,这里是线程具体实现
实现了FutureTask的线程,这里是线程具体实现
线程的启动和执行
线程类(Thread)有两个方法 :
- start():启动,该方法将线程加入线程组,由新建状态(NEW)转变为可执行状态(RUNABLE),当线程分配到CPU时间片时,会执行该线程的run()方法;新建线程必须调用该方法,才能被CPU执行run()方法;或者是加入线程池框架,由框架进行执行。
- run():执行,线程运行时方法,Thread.run()方法实际上是调用Runnable的run()方法。
线程的结束
当线程执行完成或者被中断时,会转变成终止状态(TERMINATED)。
线程的中断
Java的中断机制是一种线程协作机制,一个线程调用另一个线程的中断方法,并不能使该线程立即结束,即并不能使用中断直接终止一个线程。
每个线程对象里都有一个Boolean类型的中断标识,可以调用指定的方法来设置该标志位,线程在合适的时机通过判断中断标识位来处理中断请求,当然也可以选择不处理。
线程关于中断的方法如下:
public static boolean interrupted()
:静态方法,检测当前线程是否已经中断,且清除中断标识位,即将Boolean值设为false;public boolean isInterrupted()
:检测当前线程是否已经中断,和上面的方法一样都是调用native方法private native boolean isInterrupted(boolean ClearInterrupted)
,只不过上面的方法传入true,本方法传入false;public void interrupt()
:中断线程,将中断标识位设为true的唯一方法。
响应中断的方式:
- 抛出InterruptedException异常,交由调用栈上层处理;
- 屏蔽中断请求,不做处理;
- 响应中断,在任务处理前判断中断标识位,提前结束任务。
何时使用中断:
- 点击某个桌面应用中的取消按钮时;
- 某个操作超过了一定的执行时间限制需要中止时;
- 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
- 一组线程中的一个或多个出现错误导致整组都无法继续时;
- 当一个应用或服务需要停止时。
守护线程(Daemon线程)
守护线程是一致支持型线程,主要被用作程序中后台调度和支持下工作。
当JVM中不存在非Daemon线程时,虚拟机会退出。
通过Thread.setDaemon(boolean)方法将线程设置为守护线程。
线程安全和线程不安全
线程安全性:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么这个类就是线程安全的,否则即为线程不安全
线程的安全问题都是由全局变量或静态变量引起的,即对共享变量的访问(读或者写)会引起线程安全问题。
以下几种情况是线程安全的:
- 无状态的方法,即方法不访问共享变量,只使用方法栈的局部变量;
- 正确加锁同步的方法;
- 无锁同步的方法,亦作非阻塞同步。
多线程优缺点
多线程:
将任务分解成多个线程交由单核CPU分时处理,或多核CPU并行处理。
优点:
- 提高多核CPU的利用率:如果单线程执行,那么在一个CPU运行时,其他CPU干瞪眼不会做出一丝贡献;
- 提高应用处理性能;
缺点:
- 增加了上下文切换的开销:从任务的保存到再加载就是一次上下文切换;
- 线程安全性:因为多线程同时访问共享变量,如果没有做好临界区的同步,会导致结果错误,如脏数据等;
- 占用更多的内存空间:每个线程都需要方法栈、程序计数器等堆栈内存。
线程间通信
线程间通信是指:线程之间通过何种机制进行信息的交换。
在命令式编程中,线程间通信方式有两种:
- 共享内存:线程对共享内存进行读写;
- 消息传递
同步: 指程序中用于控制不同线程之间操作发生的相对顺序的机制。
- 共享内存的通信机制中,程序必须显式地指定某个方法或代码需要在线程之间进行互斥访问,即显式同步;
- 消息传递的通信机制中,由于消息的发送在接收之前,所以是隐式同步
Java采用共享内存的并发通信模型,整个通信过程对程序员完全透明。
线程相关的常用方法
- Thread.currentThread(): 获取当前正在执行的线程;
- Thread.sleep(long):使调用该方法的线程休眠指定的时间,继续持有原有的锁;
- threadA.join():调用该方法的线程等待threadA线程的结束;必须在线程启动后调用才有效,因为该方法需要判断threadA线程isActive();
实例:
public static void main(String[] args) {
Thread test1 = new Thread(() -> System.out.println("test1线程执行完毕"));
test1.start();
Thread test2 = new Thread(() -> {
try {
test1.join();//线程2等待线程1执行结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2线程执行完毕");
});
test2.start();
try {
test2.join();//主线程等待线程2执行结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程执行完毕");
}
结果:
test1线程执行完毕
test2线程执行完毕
main线程执行完毕
- Object.wait()/wait(long):使调用该方法的线程进入WAITING状态,进入该对象的等待集合,并释放该对象的锁;只有当该对象的notify()/notifyAll()方法被调用或线程被中断才会返回,从等待队列中移除;
- Objetc.notify():通知一个在该对象上等待的线程(任意选择,具体方式由是实现者决定),从wait()方法上返回,参与对象锁的竞争;只有竞争到了该锁才能真正返回,并该对象锁;只有持有该对象锁的线程(有且只有一个)才能调用该方法;
- Object.notifyAll():通知该对象上等待的所有线程进行以上操作。