线程基础

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)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值