1.进程和线程
提到线程,就不得不提及和它相关的另一个概念:进程。进程是程序在一个数据集合上的一次动态执行的过程,是系统进行资源调度和分配的独立单位。进程对应代码从加载、执行至执行完毕的完整过程。我们可以将一个正在操作系统中运行的exe程序理解成是一个进程。打开“Windows任务管理器”就可以查看到正在运行的各个进程。
线程是比进程更小的执行单位,它是进程中独立运行的子任务。比如对于kugou.exe,既可以播放音乐,同时又可以搜索歌曲,把歌曲加入播放列表等。把kugou.exe看做一个进程,那播放音乐,搜索歌曲,歌曲放入歌单等就是kugou.exe对应的线程。对于进程来说一个应用程序就是一个进程;对于线程,一个进程在执行过程中可以产生多个线程,每个线程也有它自身的产生、存在和消亡的过程。
引入进程就是为了让程序能够并发执行。在这里,需要区分并发和并行的概念,并发就是一段时间可以处理多个任务,但是同一时间只能处理单个的任务,这些任务交替执行;但是并行可以同一时间同时处理多个任务。为了提高CPU利用率,多道批处理系统可以一次性载入多个作业到内存中让程序并发执行,但是这会造成一系列的问题。因为程序本身是不能并发执行的,程序在并发执行时资源是共享的,程序并发执行会由于数据共享产生间断性,失去封闭性,不可再现性等问题,导致多个进程并发执行时会改变资源的状态,从而影响到最终的结果。而这些问题在引入进程这个概念后,都得到了很好的解决。对每个进程来说它是独立功能的一个程序,进程之间的数据状态也是完全独立的,所以就不会存在数据共享而引发的问题。
那么引入线程的目的是减少程序在并发执行时所付出的时空开销, 使操作系统具更好的并发性,从而提高CPU的利用率和程序的吞吐量,提升系统运行效率。使用多线程技术可以在同一段时间内运行多种不同的任务。CPU在这些任务之间不停切换,由于切换很频繁,给人的感觉是这些任务在同时执行。“同时”执行是给人的感觉,在线程之间实际上这些任务是轮换执行的,轮换执行也即线程的执行是异步的。
线程优点的例子如:现在有两个任务,任务1和任务2是两个互相独立,互不相干的任务:他们两个的执行顺序完全不会受到对方的影响。任务1需要运行10秒钟;任务2需要运行1秒钟。假如是顺序执行,任务1先于任务2执行,那么任务2就要等到任务1执行完毕才能执行,需要等待很长时间。而且任务1是等待远程服务器返回数据,不需要CPU资源,但是任务2却因为任务1的执行无法获取到CPU。这时如果创建两个线程分别负责任务1和任务2,这两个线程交替执行,那么任务2就不用等待那么长时间,CPU的利用率也得到了提升。
2.使用线程
了解了线程的优点之后我们就可以尝试着来使用它。
在Java中,每个Java程序都有一个默认的主线程。当JVM加载代码发现public static void main()方法之后,就会立即启动一个线程,这个线程就称为主线程。
通过代码可以验证:
package first;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName());
}
}
程序运行结果:
程序执行结果表示在main( )方法中执行的线程的名字为main,这个线程就是主线程。控制台输出的线程名称main和mian( )方法没有关系,只是名称相同。主线程是产生其他子线程的线程,因为多线程异步执行的特征,主线程不一定是最后完成执行的线程。
如果main( )方法中没有创建其他的线程,那么main( )方法执行完最后一个语句,JVM就会结束Java应用程序,这是单线程的情况。如果main方法中又创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,JVM要等程序中所有线程都结束之后才能结束程序,这是多线程。一个进程运行时至少会有一个线程在运行。
那么我们怎么使用线程呢?
Java中创建线程的方式的方式主要有两种。
(1)继承Thread类。
Thread类的源码的结构定义如下:
public class Thread implements Runnable
从源码中可以看出Thread类实现了Runnable接口。并且Thread类中定义了一个run( )方法。
接下来我们可以创建一个线程类。通过继承Thread类实现多线程的步骤如下:
• 重写run( )方法。
• new一个线程对象。
• 调用Thread对象的start( )方法启动线程。
按照步骤进行操作。首先创建线程类,重写run( )方法。
package first;
public class MyThread extends Thread {
@Override
public void run(){
super.run();
System.out.println("MyThread");
}
}
在main( )函数中新建一个线程对象并调用对象的start( )方法:
package first;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread thread = new MyThread();
thread.start();
System.out.println("运行结束!");
}
}
最后,程序的运行结果如下:
从程序的运行结果可以看出,MyThead.java类中的run( )方法执行的比较晚。说明在多线程中代码的执行结果与代码的执行顺序是没有关系的,Thread类启动start( )方法后调用线程中的run( )方法的时间是随机的,不确定的,所以可能首先执行了下面的输出代码,这也说明多线程的工作方式是异步执行的。
(2) 实现Runnable接口。
接下来我们可以创建一个Runnable接口类。通过实现Runnable接口实现多线程的步骤如下:
• 实现run( )方法。
• 创建一个Runnable类的对象runnable,new MyRunnable。
• 创建Thread类的对象并将Runnable对象作为参数,new Thread(runnable)。
• 调用Thread对象的start( )方法启动线程。
按照步骤进行操作。首先创建线程类,实现run( )方法。
package first;
public class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("运行中");
}
}
要使用MyRunnable.java类,首先在main( )函数中创建MyRunnable类,然后再创建Thread类,创建Thread类的构造方法可以有好几种,官方的API中Thread类的构造方法显示如下:
在Thread.java类中有8个构造函数,有两个构造函数Thread(Runnable target)和Thread(Runnable target,String name)可以传递Runnable接口,说明构造函数支持传入Runnable接口的对象,由此就可以使用到上述定义的MyRunnable.java类了。其中,向Thread类中传入Runnable对象作为参数是常用的方法。main( )方法中的代码如下:
package first;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束!");
}
}
运行结果如下:
根据构造方法Thread(Runnable target,String name),我们在新建一个线程时也可以这么写:
Thread thread = new Thread(new Runnable(){
public void run(){
//run( )方法中的内容
}
},"threadname");
继承Thread类和实现Runnable接口这两种方法实现线程方式的区别:
如果想要创建的线程类已经有一个父类了,因为Java单继承的特性,就不能再继承Thread类了,这时就必须要实现Runnable接口。所以为了可以继承其他类,推荐使用实现Runnable接口的方式创建线程类。但是这两种方式创建的线程在工作时的性质是一样的,没有本质的区别。
上面提到了Thread类的源码的结构定义:
public class Thread implements Runnable
通过定义可以看出Thread类实现了Runnable接口。那么使用Thread类时Thread类的构造方法Thread(Runnable target)中不仅可以传入Runnable接口的对象,还可以传入一个Thread类的对象。这样做完全可以将Thread类中的run( )方法交由其他线程进行调用。
另外,创建线程的另外一种方法是使用线程池。关于线程池方面的知识,有兴趣的同学以自行搜索了解。
多线程基础的内容到这里就结束了,这里只是涉及到了多线程基础的一小部分。
参考书目:《Java多线程编程核心技术》 --高洪岩 Chapter 1(1.1 1.2)