Java基础2--多线程

来源:互联网 发布:欧洲神话 巨人 知乎 编辑:程序博客网 时间:2024/04/26 18:26

在编程的时候,经常会遇到一个问题,我该什么时候用多线程?为什么用多线程?

在此讲讲自己的理解,同时也参考了网上诸多资料。。。


一、概念

为了直观对比单线程与多线程,举一个简单例子:

假设我要做3件事:烧开水,举杠铃100下,洗衣服 

烧开水这件事,我要做的有:准备烧开水(1分钟)、等开水烧开(8分钟)、关掉烧水机(1分钟)

举杠铃这件事,我要做的有:举杠铃100个(10分钟)

洗衣服这件事,我要做的有:准备洗衣服(1分钟)、等待衣服洗好(5分钟)、关掉洗衣机(1分钟)


单线程情况下,我是执行完一件事情再继续另外一件,

那么我需要的时间为:1+8+1+10+1+5+1=27分钟


如果用多线程的做法,在最理想的情况下:

线程1  准备烧开水1  sleep  1          sleep 5            sleep 1          sleep 2          关开水 1分钟 exit
线程2    sleep 1      sleep  1            举杠铃50 5分钟    sleep 1          举杠铃20 2分钟  sleep1      举杠铃30下 3分钟      
线程3    sleep  1      准备洗衣服1 分钟    sleep 5            关洗衣机1分钟    exit

最后用了14分钟。但这是最理想的情况下,实际上运行的时候,线程不一定会这样切换。但是对比于单线程,相对更好的利用了cpu资源。


然后再讲讲并发并行的概念,套用Erlang 之父 Joe Armstrong 的一幅讲解图


并发是两个队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机。

并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥

并行:在单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特种;在多处理器系统中,进程不仅可以交替执行,而且可以重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。


以上概念,并发对多线程更重要,多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行

换句话解释,比如现在有进程A运行需要10s,结束后运行进程B需要10s。在实现多线程机制以后,进程A细化成10个线程,每个线程只需1s,然后多线程会在线程直接切换执行,当然进程B也是同理。所以多线程是同步并发的。


这里说一下同步的意思:进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具有同步关系的一组并发进程相互发送的信息称为消息或事件。


对于上面的多线程同步并发,以代码形式展示

class ThreadTest extends Thread{private String name;public ThreadTest(String name) {this.name=name;}public void run() {for (int i = 0; i <5; i++) {System.out.println(name+"运行了"+i);try {                  sleep((int) Math.random() * 10);              } catch (InterruptedException e) {                  e.printStackTrace();              } }}}public class Test {public static void main(String[] args) {ThreadTest thread1=new ThreadTest("小明");ThreadTest thread2=new ThreadTest("老师");thread1.start();thread2.start();}}
sleep方法目的是不让当前线程独霸该进程所获取的cpu资源。

输出:

小明运行了0
老师运行了0
小明运行了1
小明运行了2
小明运行了3
小明运行了4
老师运行了1
老师运行了2
老师运行了3
老师运行了4
再运行一次:

小明运行了0
小明运行了1
小明运行了2
小明运行了3
小明运行了4
老师运行了0
老师运行了1
老师运行了2
老师运行了3
老师运行了4

从结果中很直观的可以看出,多线程的切换是随机的。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了。。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。


至于多线程的优点,自己感悟并不深,各位还是参考网上的论点,上面多核cpu的算是一点,可以更加充分发挥多核cpu的优势,提高cpu利用率等等;还有可以防止阻塞。至于多线程切换线程的调度策略。。太深奥,以后再看看


二、生命周期

简单讨论一下线程的生命周期


引用网上一张经典图示

java线程主要有五种基本状态

新建状态(new):当用new操作符创建一个新的线程对象时,该线程进入新建状态。处于此状态的线程只是一个空的线程对象,系统不为它分配资源

就绪状态(Runnable):当调用线程对象的start方法,线程即进入就绪状态,该状态的线程位于可运行线程池中,变得可运行,等待CPU调度执行。但并不是说调用了start方法以后,线程就会立即执行。

运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行的线程执行wait()方法,导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。(想再次执行,等待wait时间到,或者notify()、notifyAll()唤醒线程,如上图所示)

2.同步阻塞:线程在获取synchronized同步锁,若该同步锁被其它线程所占用,它会进入同步阻塞状态

3.其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

三、java多线程的创建和启动

基本线程类

Thread类、Runnable接口

继承Thread类写的多线程代码,在上面已经提及,所以略过不提


实现Runnable接口

class RunnableTest implements Runnable{@Overridepublic void run() {System.out.println("子线程名字:"+Thread.currentThread().getName()+"\n子线程ID:"+Thread.currentThread().getId());}}public class Test{public static void main(String[] args) {RunnableTest test=new RunnableTest();Thread thread=new Thread(test);thread.start();}}

RunnableTest类通过实现Runnable接口,并重写该接口的run()方法,然后我们创建了一个Thread对象,交给他们去执行,Thread构造对象的方法中有Thread(Runnable target),这样我们可以把RunnableTest对象引入,然后再通过Thread对象启动start()方法。

做个测试,如果RunnableTest不调用Thread类的方法,直接用自己重写后的run()方法看看能不能创建线程

public static void main(String[] args) {RunnableTest test=new RunnableTest();RunnableTest test1=new RunnableTest();test.run();test1.run();System.out.println("===========");new Thread(test).start();new Thread(test1).start();}
结果是:

子线程名字:main
子线程ID:1
子线程名字:main
子线程ID:1
===========
子线程名字:Thread-0
子线程ID:8
子线程名字:Thread-1
子线程ID:9
很明显,两个RunnableTest的对象,即便调用了run()方法也没有创建线程(之所以有线程,是因为main方法也是一个线程,所以线程名字为main)

后面两个就很明显创建了线程了


实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。


Thread与Runnable的优劣:

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类



参考链接:

https://zhidao.baidu.com/question/395363469.html

http://blog.csdn.net/cqkxboy168/article/details/9026205/-----并发 并行 同步 异步 多线程的区别

https://www.cnblogs.com/lwbqqyumidi/p/3804883.html




原创粉丝点击