程序、进程、线程的区别是什么,在Java中又该如何创建线程。
程序,进程,线程的区别
-
运行在操作系统上的软件我们一般称之为程序,其实程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
-
进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预的。
-
线程是CPU调度和执行的的单位。可以理解为在一个进程中包含有多个线程,是进程的一个执行单位。线程的执行是根据CPU的调度来的,而CPU是随机进行调用线程的
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。
线程的创建
线程就是独立的执行路径,在Java程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程(main()方法),gc线程(垃圾回收器);
1. 继承Thread类
- 自定义一个线程类来继承Tread类
- 重写run()方法,并编写线程执行体
- 创建线程对象,调用start()方法启动线程
程序例子:
public class ThreadTest{
public static void main(String[] args){
//创建线程对象
MyStread myStread=new MyStread();
myStread.start();
}
}
class MyStread extends Thread{
//线程入口点
@Override
public void run() {
//线程执行体
for (int i = 0; i < 5; i++) {
System.out.println("这是一个Thread线程...");
}
}
}
运行出来的结果就是打印了五遍的"这是一个Thread线程…"。
接下来看一下使用Thread方式创建多个线程…
public class ThreadTest{
public static void main(String[] args){
//创建线程对象
MyStread myStread1=new MyStread();
MyStread myStread2=new MyStread();
MyStread myStread3=new MyStread();
myStread1.start();
myStread2.start();
myStread3.start();
}
}
class MyStread extends Thread{
//线程入口点
@Override
public void run() {
//线程执行体
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"这是一个Thread线程...");
//Thread.currentThread().getName()意思是:得到当前这个线程的对象名称
}
}
}
Thread-0这是一个线程...
Thread-1这是一个线程...
Thread-0这是一个线程...
Thread-1这是一个线程...
Thread-0这是一个线程...
Thread-1这是一个线程...
Thread-0这是一个线程...
Thread-1这是一个线程...
Thread-0这是一个线程...
Thread-1这是一个线程...
Thread-2这是一个线程...
Thread-2这是一个线程...
Thread-2这是一个线程...
Thread-2这是一个线程...
Thread-2这是一个线程...
运行结果:可以看到三个线程相互抢占资源执行,这也是CPU调度的结果…
学过java基础的我们不难发现,这是继承了Thread类,而Java的特点是单继承和多实现,所以这种方法其实存在着改进的可能性。
前面说到线程的调用是根据CPU随机调用来进行运行的,其实可以通过setPriority()方法来进行相应的优先变化,优先级分为1-10,1优先级最低,10优先级最高,而默认创建的线程优先级为5。
2. 实现Runnable接口
既然上面提到了单继承和多实现,线程可以进一步的改进,所以Runnable接口出现了。
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
看上去和继承Thread类的方式大同小异,但从代码来看,还是会有一些不同的。
public class RunnableTest{
public static void main(String[] args) {
//创建线程对象
MyRunnanble myRunnanble=new MyRunnanble();
new Thread(myRunnanble).start();
}
}
class MyRunnanble implements Runnable{
//线程入口点
@Override
public void run() {
//线程执行体
for (int i = 0; i < 5; i++) {
System.out.println("这是一个Runnable线程...");
}
}
}
**注意不同:**在创建了Runnable接口实现类的对象后,需要将对象放入Thread中进行实现,这其实是在Thread类中有实现Runnable这个接口,因此才可以将Runnable接口实现对象放入Thread中调用start()来启动线程。
运行出来的结果就是打印了五遍的"这是一个Runnable线程…"。
例2:
public class ThreadTest{
public static void main(String[] args) {
//创建线程对象
MyRunnanble myRunnanble=new MyRunnanble();
new Thread(myRunnanble1,"小明").start();
new Thread(myRunnanble1,"小红").start();
new Thread(myRunnanble1,"小华").start();
}
}
class MyRunnanble implements Runnable{
//线程入口点
@Override
public void run() {
//线程执行体
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"这是一个Runnable线程...");
//Thread.currentThread().getName()意思是:得到当前这个线程的对象名称
}
}
}
可以看到Runnable接口的同一个实现对象被多个线程使用,运行就会出现三个线程相互抢占资源的情况
现给出一种运行的情况我们来分析一下
小明这是一个Runnable线程...
小明这是一个Runnable线程...
小明这是一个Runnable线程...
小红这是一个Runnable线程...
小明这是一个Runnable线程...
小红这是一个Runnable线程...
小明这是一个Runnable线程...
小红这是一个Runnable线程...
小红这是一个Runnable线程...
小红这是一个Runnable线程...
小华这是一个Runnable线程...
小华这是一个Runnable线程...
小华这是一个Runnable线程...
小华这是一个Runnable线程...
小华这是一个Runnable线程...
可以看到,这次的情况是**小明这个线程先启动了,但是运行for循环三次后,小红线程得到了调度,因此小红线程运行,而运行一次后,小明线程又得到了调度,一次后又是小红…小华最后才得到cpu的调度。**下一次运行可能会变成另外的情况。
由此也证明了我们前面的结论.线程的调度是由cpu随机调用的,当然可以做到先让小红线程走完,再让小明走,再让小华走,这我们之后会给出解决的办法。
这就是普通常用的两种线程的创建方式,因为Java单继承的局限性,推荐使用实现Runnable接口的方式来创建线程。
下一篇博客有更为详细的讲到Runnable接口是如何实现的以及如何更为简化代码的写法,链接为:JAVA中静态代理和Lamda表达式
3.实现Callable接口
Callable接口是新出现一种实现方式,其使用稍微有点复杂,但是也相比于Runnable接口的方法有了进步。
实现方式如下:
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(Callable接口实现对象);
- 获取结果:boolean r1 = result1.get()
- 关闭服务:ser.shutdownNow();
现在给出一种返回值为Boolean类型的例子:
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建MyCallable对象
MyCallable myCallable=new MyCallable();
//创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(1);
//这里使用到了线程池,之后会说到
//提交执行:
Future<Boolean> result=ser.submit(myCallable);
//获取结果
boolean flag = result.get();
//输出
System.out.println(flag);
//关闭服务
ser.shutdown();
}
}
class MyCallable implements Callable{
@Override
public Boolean call() throws Exception {
return true;
}
}
解释:这个例子,只是简单的了解Callable接口的使用,这里是创建了一个线程,且,在重写call()中直接返回了一个true,在执行体中没有过多的加入复杂代码…
可以看到,Callable接口的方式需要开启服务,并且用到了线程池,之后还要关闭服务,相比于之前提到的两种方式略显繁琐,但Callable接口方式会返回一个对象,而且call()方法会抛出异常,这使得相比于前两种方式有所进步和改善。