目录
进程和线程
一、进程的定义
进程是程序的一次执行,进程是一个程序及其数据在处理机上顺序执行时所发生的活动,进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
进程是系统进行资源分配和调度的独立单位。每一个进程都有它自己的内存空间和系统资源
二、程序并发执行
为使程序能并发执行,系统必须进行以下的一系列操作:
(1)创建进程,系统在创建一个进程时,必须为它分配其所必需的、除处理机以外的所有资源,如内存空间、I/O设备,以及建立相应的PCB;
(2)撤消进程,系统在撤消进程时,又必须先对其所占有的资源执行回收操作,然后再撤消PCB;
(3)进程切换,对进程进行上下文切换时,需要保留当前进程的CPU环境,设置新选中进程的CPU环境,因而须花费不少的处理机时间。
三、线程
引入线程主要是为了提高系统的执行效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理。使OS具有更好的并发性。
线程的例子:
音乐软件:既播放着音乐,点击菜单栏仍会响应。
四、进程和线程总结
-
进程作为资源分配的基本单位
-
线程作为资源调度的基本单位,是程序的执行单元,执行路径(单线程:一条执行路径,多线程:多条执行路径)。是程序使用CPU的最基本单位。
-
线程有3个基本状态:执行、就绪、阻塞
线程状态图:
-
线程有5种基本操作:派生、阻塞、激活、调度、结束
-
线程的属性:
(1)轻型实体;
(2)独立调度和分派的基本单位;
(3)可并发执行;
(4)共享进程资源。 -
线程的两个基本类型:
(1)用户级线程:管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。
(2)系统级线程(核心级线程):由操作系统内核进行管理。操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行以及撤消线程。
多线程不是提高程序的执行速度,其实是为了提高应用程序的使用率,程序的执行其实都是在抢CPU的资源,CPU的执行权。多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权
五、使用多线程提高效率需要考虑的因素
(1)所有线程执行时并发+并行
(2)线程创建、销毁时比较耗时
(3)线程的调度由系统决定(线程越多,系统调度越频繁;线程就绪态转变为运行态,也是有性能及时间消耗)
(4)单个线程运行的任务量
并发和并行
一、并发
并发性是指同一时间间隔内发生两个或多个事件。
并发是在同一实体上的多个事件。
并发是针对线程的。
二、并行
并行性是指同一时刻内发生两个或多个事件。
并行是在不同实体上的多个事件。
并行是针对进程的。
创建线程的方法
一、继承 Thread 类
可以通过继承 Thread 来创建一个线程类,该方法的好处是 this 代表的就是当前线程,不需要通过Thread.currentThread() 来获取当前线程的引用。
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
1.Thread的常见构造方法:
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即是线程组 |
代码示例:
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
2.Thread 的几个常见属性:
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority |
是否后台线程 | isDaemon |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到
状态表示线程当前所处的一个情况
3.从下面的源代码中可以发现,Thread类实现了Runnable接口,它们之间具有多态关系。
public class Thread implements Runnable
4.调用的随机性:
代码的运行结果与代码执行顺序或调用顺序是无关的。
public class MyThread extends Thread {
@Override
public void run(){
System.out.println("MyThread");
}
}
class Test{
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("运行结束!");
}
}
/*
打印结果
运行结束!
MyThread
*/
线程是一一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run
方法,所以就会出现先打印“运行结束!”后输出“MyThread" 这样的结果了。
5.start();启动线程:
start()方法首先启动了线程,然后再由JVM去调用该线程的run方法。
run方法直接调用,不会启动线程,只是在当前main线程中调用了run方法
6.创建线程:
(1)定义一个类,其继承Thread类。
(2)重写Thread类的run()方法,并将该线程要执行的操作声明在该方法中。
(3)创建该Thread子类对象的实例。
(4)调用start()方法,启动该线程。
class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());//打印该线程的名称
}
}
public class TestDemo1 {
public static void main(String[] args) {
MyThread my1 = new MyThread();//创建对象
my1.start();//启动该线程
MyThread my2 = new MyThread();
my2.start();//启动该线程
}
}
/*
打印结果:
Thread-0
Thread-1
*/
二、实现 Runnable 接口
通过实现 Runnable 接口,并且调用 Thread 的构造方法时将 Runnable 对象作为 target 参数传入来创建线程对象。
该方法的好处是可以规避类的单继承的限制;但需要通过 Thread.currentThread() 来获取当前线程的引用。
(1)定义一个类,实现Runnable接口;
(2)重写其run()方法,跟Thread类中的run()方法一样,将线程要执行的操作声明在该run()方法中;
(3)创建该Runnable实现类的实例;
(4)将该实例作为Thread的target来创建Thread对象,这个Thread对象即真正的线程对象;
(5)调用start()方法,启动该线程。
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class TestDemo1 {
public static void main(String[] args) {
MyThread my = new MyThread();//创建Runnble()实现类的实例
Thread t1 = new Thread(my);//将该实例传到Thread类的构造器中创建Thread对象
t1.start();
Thread t2 = new Thread(my);
t2.start();
}
}
三、Callable的使用
import java.util.concurrent.*;
/**
* Callable创建线程:
* Future/FutureTask
*/
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> c = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 123;
}
};
FutureTask<Integer> task = new FutureTask<>(c);
//Thread使用Callable
new Thread(task).start();
System.out.println("main");
Integer r = task.get();//直到线程执行完毕(join效果差不多),但可以获取线程的返回值
System.out.println(r);
//线程池中使用Callable
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<Integer> future = pool.submit(c);
System.out.println("main");
Integer r2 = future.get();//阻塞等待
System.out.println(r2);
}
}
四、run()和start()方法区别
start()方法首先启动了线程,然后再由JVM去调用该线程的run()方法。
run()方法直接调用,不会启动线程,只是在当前main线程中调用了run()方法。
两种创建多线程的方法,我们一般使用实现 Runnable 接口。因为它可以避免java中的单继承的限制。