多线程
一、线程与进程
进程
- 是指一个内存中运行的应用和程序,每个进程都有一个独立的内存空间
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
- 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程
二、多线程的优点
- 提高应用程序的响应
- 提高计算机CPU的利用率
- 改善程序结构,将将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
三、线程的调度
分时调度
- 分时调度是指所有的线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
- CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对于我们的感觉要快,看上去就是在同一时刻运行,其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
四、同步与异步
- 同步 :排队执行,效率低但是安全
- 异步 :同时执行。效率高但是数据不安全
五、并发与并行
- 并发 :指两个或多个事件在同一个时间段内发生。
- 并行 :指两个或多个事件在同一时刻发生(同时发生)。
六、多线程的实现方式
1.Thread的实现
先创建线程类MyThread,注意要继承并且重写run方法
public class MyThread extends Thread {
/**
* run方法就是线程要执行的任务方法
*/
@Override
public void run() {
//这里的代码就是一条新的执行路径,里面要执行的任务
//这个执行路径的触发方式,不是调用run方法,而是通过thread对象的start()方法来启动任务
for(int i = 0; i < 10; i++){
System.out.println("锄禾日当午"+i);
}
}
}
在主线程中创建MyThread类的对象然后调用start方法
public class Txt {
public static void main(String[] args) throws InterruptedException {
//抢占式分配 谁先走完是随机的
MyThread m = new MyThread();
m.start();
for(int i = 0; i < 10; i++){
System.out.println("汗滴禾下土"+i);
}
}
}
Thread常见应用场景
可以结合匿名内部类使用,使得写法比较简洁
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("疑是地上霜"+i);
}
}
}.start();
也可以在里面放入任务
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
2.Runnable任务实现
创建MyRunnable类,实现Runnable接口,同时也要重写run方法
public class MyRunnable implements Runnable {
@Override
public void run() {
//线程的任务
for (int i = 0; i < 10; i++){
System.out.println("床前明月光"+i);
}
}
}
创建MyRunnable类的对象,将其作为Thread构造器的参数,创建一个新的Thread对象t,调用t的start方法
// 实现Runnable
//1. 创建一个任务对象
MyRunnable r = new MyRunnable();
//2. 创建一个线程,并为其分配一个任务
Thread t = new Thread(r);
//3. 执行这个线程
t.start();
实现Runnable 接口 与 继承Thread父类相比有如下优势:
- 通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同的任务的情况。
- 可以避免(单)继承所带来的局限性,java中只有单继承,但是允许多实现并且可以去实现其他的类,还能再去继承一个
- 通过创建任务,然后给线程分配的方式任务与线程本身是分离的,提高了程序的健壮性
- 更重要的点:
线程池技术,接受Runnable类型的任务,而不接收Thread类型的线程
Callable使用
public class callable {
/**
* 第三种线程结构
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
//判断线程是否完毕
futureTask.isDone();
//取消子线程,不想等待 得传入参数 传入true就是取消
futureTask.cancel(true);
new Thread(futureTask).start();
Integer j = futureTask.get();
System.out.println("返回值为"+j);
for (int i = 0; i < 10; i++){
Thread.sleep(1000);
System.out.println(i);
}
}
static class MyCallable implements Callable{
@Override
public Integer call() throws Exception {
for (int i = 0; i < 10; i++){
Thread.sleep(1000);
System.out.println(i);
}
return 100;
}
}
}
Runnable 与 Callable
接口定义 :
- //Callable接口
public interface Callable<V> {
V call() throws Exception;
}
- //Runnable接口
public interface Runnable {
public abstract void run();
}
Runnable 与 Callable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
Runnable 与 Callable的不同点
-
Runnable没有返回值;Callable可以返回执行结果
-
Callable接口的call()允许抛出异常;Runnable的run()不能抛出
Callable获取返回值
- Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
七、线程方法
- 获取线程的名称
得到当前线程的名称
Thread.currentThread().getName();
- 线程休眠
静态方法可以直接调用,使线程休眠,可以输入毫秒或者毫秒加纳秒
Thread.sleep(millis:1000);
- 线程阻塞
线程阻塞不仅仅包括休眠,可以理解为所有消耗时间的操作,例如中间读取文件的时间
- 线程中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定
在早期的jdk版本中,Thread有stop方法,但直接stop无法对其占用的资源进行合理的释放。
线程是否关断应当由线程自身来决定,通过中断标记的形式,在某些情况下查看标记,抛出异常。
在调用Interrupt方法时,如果出现下述情况进入catch块中的InterruptException异常
实列:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0 ;i<10;i++){
System.out.println("锄禾日当午"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//主函数
public class MyInterrupted {
public static void main(String[] args) {
MyRunnable tr = new MyRunnable();
Thread t = new Thread(tr);
t.start();
t.interrupt();
}
}
线程:分为守护线程和用户线程
- 用户线程:
当一个进程不包含任何的存活的用户线程时,进行结束直接创建的线程就是用户线程
- 守护线程:
守护用户线程,当最后一个用户线程结束时,所有用户线程自动死亡
- 设置守护线程:
在启动前设置
可以通过setDaemon(true)将子线程设置为守护线程
八、线程安全
方法一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程共同操作的变量
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁
补充:在实现runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
方法二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
Runnable接口:
synchronized void 方法名(){ //同步监视器:this
//需要被同步的代码
}
Thread继承:
static synchronized void 方法名(){ //同步监视器: object.class
//需要被同步的代码
}
-
同步方法本身仍然涉及到同步监视器,只是不需要我们显示的声明
-
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
九、线程的六种状态
- 尚未启动 new
- 执行中 Runnable
- 排队时 Blocked
- 无期限等待 waiting
- 等待有休眠时间 waiting
- 终止 trminated
十、线程池 Executors
线程池的好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。