进程与线程
- 进程: 进程是程序的一次动态执行的过程,它经历了从代码加载,执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。程序是一个静态的概念,进程是一个动态的概念。
- 线程:线程依附于指定的进程,并且可以快速启动以及并发执行。一个进程中可以包含多个进程,至少包含一个,每个线程就是这个进程的一条执行路径,每个JAVA程序默认包含有一个主线程(运行main方法的)。
- 进程可以是线程的容器,进程不完成具体的指定执行,Java指令都是通过线程去执行的,线程是CPU进行调度的最小单位。
- 进程之间的数据是不能共享的,但是同一个进程的多个线程之间可以共享数据。比如电脑上面的记事本和画图工具,这两个进程一旦启动,只通过这两个进程无法进行数据共享,想要数据共享那么必须依附其他进程,并通过其上面的线程进行数据传输。
并行和并发
- 并行: 在多核CPU下每个核心单独负责一个进程,也就就是能够同时处理多个事情的能力。
- 并发: 在电脑中CPU一般会将执行时间分成很多个很小的时间片,然后将这些时间片一次分给不同的线程去执行,并且在多个线程中来回快速的切换,在切换的过程中由于时间片很小,人一般感受不到,就会感觉到任务在同时进行。总的来说并发就是拥有处理多个事情的能力,但不是一起处理,宏观上是并行,微观上是串行。
- 在多核CPU下电脑的运行都会在伴有并行的同时夹杂着并发,因为电脑已启动就会有几千个线程,几百个进程,八个CPU肯定是处理不过来的。
多线程
多线程编程是Java语言最为重要的特性之一,利用多线程技术可以提升单位时间内的程序处理性能,也是现代程序开发中高并发的主要设计形式。
1. 多线程的优点
- 充分利用多核CUP的优势,提高程序的执行效率(可以将多个任务分给多个不同的CPU进行并行,这样就能节省很多时间)。但对于单核CPU执行效率没有提高,因为在并发中,是将多个时间片分给多个线程,又由于切换时间片分配时间片给每个线程需要时间,所以线程的总执行时间肯定不减反增。
- 对于一些非常耗时的IO操作,可以减少用户的等待时间。
- 从方法调用者的角度来说可以实现异步调用。
3.1 同步调用: 被调方法如果没有执行完毕,主调方法一直处于等待的状态。
3.2 异步调用: 无需等待被调用的方法执行完毕,调用方可以在这个过程中继续执行后续的代码。
2. 线程常见的三种创建方式
1. Thread类实现多线程
java.lang.Thread是一个负责线程操作的类,任何类只要继承了Thread类就可以成为一个线程的主类。同时线程类中需要明确重写父类中的run()方法(方法定义: public void run()),当产生了若干个线程类对象时,这些对象就会并发执行run()方法中的代码。
实现方式:创建一个类并从Thread这个父类继承即可,然后创建一个对象调用线程对象的start方法。
举例代码:
package com.qianfeng.day16;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 101; i <= 200; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i);
}
}
}
2. Runnable接口实现多线程
出现原因:在面向对象的程序设计通过接口实现标准定义与解耦操作,由于在继承子父类体系中联系太紧密,为了减少耦合,所以多线程提供了Runnable接口实现多线程开发。
实现方式:创建一个类并实现Runnable接口,并实现接口中唯一的方法(run方法),然后通过Thread类中带参构造器(public Thread(Runnable target))创建对象调用start()方法。
代码举例:
package com.qianfeng.day16;
public class MyThread1 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 101; i <= 200; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i);
}
}
}
3. Callable接口实现多线程
产生原因:Java中针对多线程执行运用Runnable接口有许多弊端,比如重写的run()方法没有返回值,不能提供throws往外申明异常类型只能通过try-catch解决异常,基于上述两种原因,提供了Callabe接口。
实现方式:创建一个类并实现Callable<String>接口,并实现接口中唯一的方法(call方法),将一个Callable接口的实例转成Runnable接口,然后通过Thread类中带参构造器(public Thread(Runnable target))创建对象调用start()方法。
代码举例:
package com.qianfeng.day16;
import java.util.concurrent.Callable;
public class MyThread2 implements Callable<String>{
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
System.out.println("子线程正在执行!!!");
Thread.sleep(5000);
return "hello world";
}
}
测试上述三种实现多线程方式
package com.qianfeng.day16;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test01 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//第一种创建线程的方式
MyThread t1 = new MyThread();
t1.start();
//第二种创建线程的方式
MyThread1 te = new MyThread1();
Thread t2 = new Thread(te);
t2.start();
//第三种线程的创建和启动方式
MyThread2 t3 = new MyThread2();
FutureTask<String> task = new FutureTask<String>(t3);
Thread t31 = new Thread(task);
//可以获得接口的实现类重写方法的返回值
String str = task.get();
System.out.println(str);
}
}
4. 三种实现多线程的注意事项
- 使用Thread类和Runnable接口都可以以同一功能的方式来实现多线程,但是从
Java实际开发的角度来说,推荐使用Runnable接口,因为采用这种方式可以有效的避免单继承的局限。- 在使用Callable接口的时候通过调用未来任务类对象的get方法获取子线程执行返回的结果,在此期间,如果子线程还没有执行完成,此时主线程会处于等待状态。
3. 线程的常用方法介绍
- setName(String name) 设置当前线程的名称,系统默认为每个线程取一个名字:Thread-num,后期程序中就可以通过getName方法获取当前线程的名称
- currentThread() 这个方法不是线程对象的方法,属于线程类的一个静态方法 Thread.currentThread(),始终获取的是调用这个方法所在的线程对象,一般可以通过此对象调用getName()获得线程名称
- sleep(long 毫秒数) 也是一个线程类的静态方法,让调用该方法的线程进入到休眠的状态,不再和其他的线程一起抢占CPU的时间片
此方法有两种解除休眠的情况:一种是睡够了时间 另一种是被打断了(打断的时候会抛出一个异常)- yield()是一个本地方法,让当前线程放弃CPU的执行权,重新回到执行的等待队列
- join()线程合并,还可以指定等待的时间,传入一个毫秒数
- setPriority(Thread.MIN_PRIORITY);设置线程的优先级 1 5 10
问题:在一个程序中添加一个监控系统,该线程不会结束,除非强制线程结束
解决方案:
1.第一种方式:调用线程对象的stop方法,但是这个方法不建议使用,没有给结束的线程一个缓冲的时间,直接将线程对象销毁,过于粗暴
2.第二种方式:调用线程对象的interrupt()方法打断当前线程(本质是让这个线程对象的状态isInterrupted置为true),解决方法是在catch块中手动调用线程对象的interrupt方法将标记置为true
package com.qianfeng.day16;
public class Monitor implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
Thread thread = Thread.currentThread();
while(true) {
System.out.println("正在监控系统...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("睡眠过程被打断了...");
thread.interrupt();
}
if(thread.isInterrupted()) {
System.out.println("进行监控数据的保存....");
break;
}
}
}
}
class TestThreadStop {
public static void main(String[] args) throws InterruptedException {
Monitor monitor=new Monitor();
Thread t=new Thread(monitor);
t.start();
System.out.println("主线程干自己的事...");
Thread.sleep(5000);
//1.结束某个子线程
//t.stop();
//2.正确的终止线程执行的方式
t.interrupt();
System.out.println("main方法结束");
}
}
此问题的细节:
如果我们打断的是正常执行的代码,打断标记会被置为true,但是如果打断的是正在休眠的部分,此时线程对象中的是否被打断的标记依然为false,所以需要在打断的时候再次将打断标记设为true
守护线程
描述:只要程序中其他所有的线程全部执行完毕,那么守护线程自动结束,例如 垃圾回收器就是一个守护线程
在Java中GC(垃圾回收机制)就是一个守护线程
代码:thread.setDeamon(true);意思上当所有线程执行结束那么它也自动结束进程
3. 多线程的运行状态
- 创建状态:在程序中用构造方法创建一个线程对象后,新的线程对象便处于新建状态,此时他已经有了相应的内存空间和其他资源,但还处于不可运行状态,新建一个线程对象可采用Thread类造方法来实现,例如Thread thread = new Thread();
- 就绪状态:新建线程对象后,调用该线程的start()方法就可以启动线程,当线程启动时线程进入就绪状态,此时线程将进入线程队列排队,等待CPU调度服务,这表明它已经具有了运行条件。
- 运行状态:当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态,此时将自动调用该线程对象的run()方法,run()方法定义了该线程的操作和功能。
- 阻塞状态: 一个正在运行的线程,在某些特殊情况下,如被人为挂起或需要运行耗时的输入/输出操作时,将让出cpu并暂时中止自己的运行,进入阻塞状态,在可运行状态下,如果调用sleep()、suspend()、wait()等方法,线程都将进入阻塞状态,阻塞时,线程不能进入队列,只有当引起阻塞的原因被消除好。线程才可以转入就绪状态。
- 终止状态:当线程体中的run()方法运行结束后,线程即处于终止状态,处于终止状态的线程不具有继续运行的能力。