深入理解并发编程 -- 多线程(一)
并发编程 -- 多线程(一)
作者 : Stanley 罗昊
【转载请注明出处和署名,谢谢!】
进程
在理解多线程之前,我们先需要了解什么是进程?
进程说白了就是在你的内存空间中开辟出的一个独立的空间;
如果还不理解的话,我再解释一下;
想必各位之前都安装过软件吧,你安装完软件之后,在你的软件安装包里面是不是有一个.exe文件,那你双击exe文件的时候,在你的任务管理器,在里面就有一个进程选项卡,就是说,每当你打开一个exe文件的时候,它都会显示在任务管理器的进程当中,所以就可以把运行中的任意一款软件,都可以把它看做一个进程;
当然,以上的操作方式是在windows系统的操作的,也就是说,想查看Windows的进程,只需要在任务管理器中查看即可;
在linux下使用命令 ps 或 pstree、ps -eflgrep;
如果想在linux系统下查看java的相关进程,命令为:jps;
那么问题就来了,当你打开QQ的时候,是不是就是开启了一个进程,当你开始使用它并且聊天的时候,比如你是a,你现在要跟b聊天然后再去跟c聊天,那么这样的操作是不是相互独立的呢?也就是说,你现在要跟b发送你的游戏密码,这个时候c问你晚上吃啥饭,你发的密码c知道吗?肯定不会啦,所以你跟b聊天的时候,是不会影响你跟c聊天的,因为你跟b c 是相互独立的;
那么,在这个里面,你跟他们每个人产生的通话底层是怎么实现的呢?
底层就是靠线程去实现的;
线程
什么是线程呢?
线程是指程序在执行过程中,能够执行程序代码的一个执行单元,在Java语言中,线程有四种状态:运行,就绪,挂起,结束。一般情况下,一个操作系统是有多个进程,那么每个进程都要对应多个线程;
线程与进程有区别吗?
有,进程是一段正在运行的程序,而线程有时也被称为轻量级进程,它是进程的执行单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间,但是,各个线程拥有自己的栈空间。
一个进程可以有很多线程,每条线程并行执行不同的任务;
实现单个线程
在之前的一些简单的java练习中,我们运行的时候,是不是都是在main方法中测试运行啊,那么,在这之前,我仅仅编写了一些非常简单的java代码,甚至就在main方法中输出了一句话,就可以直接完成运行,在这期间,我并没有创建有关线程方面的方法以及程序,那么就怎么实现运行了呢?
其实很简单,Main方法既然能运行你的程序,那么必然就会有一个线程,那么这个线程就是单线程,那,我们如何查看本次运行线程的线程名呢?
我们仅需在main方法里面输出以下即可:
System.out.println(Thread.currentThread());
打印出来后,我们就可以看到线程名师Main,因为就一个线程,所以main就是主线程;
那么就一个线程,就表明,在这之前,我们所做的一些练习程序都是单线程的;
线程的创建方式
两种:
1.继承(extends) Thread
继承完Thread类之后需要重写run方法;
2.实现(implem) Runnable接口
也许需要重写run方法;
继承Thread类创建线程
讲了那么多,那么就开始上手实操一下,我们将要练习的是继承Thread来创建一个线程;
首先,我们创建一个类(MyThread)然后继承Thread类;
继承完Thread后,实现run方法;
run方法的作用就是,相当于这个线程对用户提供的一个接口;
所以,用户有什么业务逻辑,都需要写在run方法里面:
public class MyThread extends Thread{ public void run(){ //作用:相当于这个线程向用户提供的接口,用户有什么样的业务逻辑,写在这个方法中 } }
那我们现在就让这个run方法就实现一个简单的打印;
这个就是我使用了第一种方式来创建了一个线程;
那么,你这个线程创建完成后,如果你想调用它怎么办?
调用线程
我再建一个类(TestThread);
然后提供一个Main方法;
在main方法中创建一个线程,语法:
//创建一个线程 MyThread mt = new MyThred();
这个就是创建线程,创建完后,下一步我们需要调用start方法;
public class TestThread{ public static void main (String [] args){ //创建线程 MyThread mt = new MyThred(); //启动线程 mt.start(); } }
为什么要调用start,而不是run?
其实很简单,mt.start就是启动你的线程,那么启动后,它底层就会去调用你的run方法;
这个就是线程的启动式start方法;
我们运行一下后,看一下控制台打印:
就是一些输出,感觉就跟调用方法一样,对吧;
那么,接下来,我们看一下第二种创建方式
实现(implem) Runnable接口创建线程
这种方法跟上面的那种,大同小异,只不过上面那个是继承,这个是实现接口;
实现Runnable接口后,需要实现它的run方法;
public class MyRunnableThread implements Runable{ public void run(){ //作用:相当于这个线程向用户提供的接口,用户有什么样的业务逻辑,写在这个方法中 } }
现在,我们用第二种方式创建了一个线程,当然,业务逻辑跟上面的那个相同,因为举例,所以没有深究别的;
第二种方式调用线程就有所不同
因为运行线程永远需要Thread里面的start方法来启动线程,所以需要把Thread创建出来,再将创建出来的线程放进去;
所以打印出来的结果是跟上面的结果是一样的,这里就不再放图上去了;
线程关键字分析
start,是线程启动的方法;
run方法是线程执行过程中调用的方法(默认调用),在上面的例子我们也看到了,你并没有手动去调用run方法,是他自动调用的,就跟你创建对象的时候,默认调用构造方法一样;
深究run与start
那,启动线程一定是要用staet方法启动,我试试不用它,我直接调用Thread中的run方法可行吗?
可行,因为抛开线程,你本身就是实例化了Thread这个类,并调用该类中的run方法是没有问题的,但是,不纳入线程中!!
我们直接调用run方法后,发现,方法可以正常打印,因为,仅仅完成了普通方法的调用,实际上并没有启动线程;