Java学习之多线程

前言:日常编程工作当中经常听到“线程”,“线程池”,“多线程”的概念,以前对这些总是傻傻分不清楚,闲暇之余将这些知识点进行一个系统的学习和总结。在介绍线程之前,首先必须了解“进程”的概念,下面就“进程”、“线程”、“多线程”等等关于线程的知识做一个归纳,方便以后查阅。

一、进程;

       进程是一个正在进行中的程序,每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。或者说,一个进程就是一个应用程序,都有独立的内存空间。打个比方,Windows 打开任意一个应用软件就意味着windows打开了一个进程,通常在Java的开发环境下启动JVM 就相当于开启了java进程,在一个操作系统当中可以同时启动多个进程。

二、线程

      线程是指进程当中的一个执行场景(即执行流程),也是进程中的一个独立控制单元,控制则进程的执行。一个进程至少有一个线程。就像启动JVM的时候就会有一个java.exe的应用程序被启动。该进程当中有一个线程负责java程序的运行,这个线程存在于java的入口mian()函数当中,也被称为java的主线程,与此同时运行的还有其他线程,比如垃圾回收机制的线程。

三、多线程

       首先来描述一个场景,一台计算机同时开启了两个进程,一个游戏,一个音乐。此时对于单核计算机来讲,在某一个时间节点时,两个进程并非同时进行的,因为CPU此刻只能做一件事情,我们之所以能感到两个程序在同时运行,其实就是计算机在这两个进程之间高频率的切换。多线程的存在其实就是为了提高CPU的使用率。

       线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈。

       引申介绍;java命令会启动JVM ,此时就相当于启动了一个应用程序(或者说是进程)。该程序就会启动一个主线程,然后主线程会调用某个类的main方法。可见main 函数运行在主线程当中,此前所有的线程都是单线程。

四、线程的生命周期及五种基本状态;

                      

                                                                 图  4-1 

             基本状态;

        1、新建状态(new):当线程对象创建后,即进入了新建状态;如  Thread  t  = new Thread();

        2、就绪状态(Runnable):当调用线程的start()方法(t.start();),线程即进入就绪状态,处于就绪状态的线程,只能说此线程已经做好了准备,随时等待CPU调度执行,并不是t.start();之后线程就会立即执行,

        3、运行状态(Running):当CPU开始调度处于就绪状态的线程的时候,此线程在能正式执行,此时正式进入运行状态。

        4、阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权从而停止执行,进入阻塞状态,知道其进入到就绪状态,才有机会被CPU再次调用然后进入到运行状态,根据阻塞原因的不同,阻塞又分为三种:

              4.1、等待阻塞:运行状态中的线程执行wait()方法,使本线程进入阻塞状态。 

              4.2、同步阻塞:线程在获取Synchronized同步锁失败(因为锁被其他线程所占用),会进入到同步阻塞状态。

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

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

五、java多线程的创建及启动;

       常见的创建多线程有三种方法;

       1、继承Thread类,重写run方法。

public class MyThreadTest extends Thread {
	private int i=0;
	@Override
	public void run(){
		for(i=0;i<100;i++){
			System.out.println(Thread.currentThread().getName() + "-自创线程- " + i);
		}
	}
}


public class NotInitialization {
	public static void main(String[] args) {
	    for(int i=0;i<100;i++){
	    	System.out.println(Thread.currentThread().getName() + " -主线程- " + i);
	    	if(i==30){
	    		Thread  t1=  new MyThreadTest();
	    		t1.run();
	    	} else  if(i==60){
	    		Thread  t2=  new MyThreadTest();
	    		t2.run();
	    	}
	    }
	}
}

   如上所示:继承Thread类通过重写run()方法定义了一个新的线程类MyThreadTest,其中run()方法体代表了线程需要完成任务,称之为线程执行体,当创建此线程类对象时,一个新的线程得以创建,并进入到线程新建状态。通过调用线程对象引用的start()方法,使得线程进入到就绪状态,此时这个线程并一定马上执行,执行的时机取决于CPU的条用时机。

        2、实现Runnable接口,并重写接口的run(),该run()方法同样作为线程的执行主体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

public class MyThreadTest implements Runnable {
	
	private int i=0;
	@Override
	public void run(){
		for(i=0;i<100;i++){
			System.out.println(Thread.currentThread().getName() + "-自创线程- " + i);
		}
	}
}

public class NotInitialization {
	public static void main(String[] args) {
	    for(int i=0;i<100;i++){
	    	System.out.println(Thread.currentThread().getName() + " -主线程- " + i);
	    	Runnable r= new MyThreadTest();
	    	if(i==30){
	    		Thread  t1=  new Thread(r);
	    		t1.run();
	    	} else  if(i==60){
	    		Thread  t2=  new Thread(r);
	    		t2.run();
	    	}
	    }
	}
}

              通过上面的创建线程结合Thread底层发现,Thread类实现了Runnable接口,如下图;

                 

              问题:此时程序中的Run()方法,到底是属于Thread中的还是重写的Runnable之中的,请自行查看底层实现。

         3、使用Callable和 Futrue接口创建线程,具体是创建Callable接口的实现类,并使用call()方法,并使用FutrueTask类来包装Runnable类的对象,且以此FutrueTask对象作为Thread对象的target来创建线程。

              此方法不做深入研究。

        4、那么创建线程通过继承Thread类和实现Runnable接口有何区别。

              4.1、单继承具有局限性,比如某个类应该继承过其他父类。

              4.2、继承Thread类线程代码存放在Thread子类中run()方法中。实现Runnaable接口线程代码则存放在接口子类的方法中。

         5、Thread类的start() 与 run()

               5.1、start():先看API给的解释;

                        时线程开始执行,Java虚拟机开始调用线程的run()方法。结果是两个线程并发的执行  。如下;

                        

                        当前线程(执行的start()函数)和另一个线程(执行run())多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

                        使用start()方法来启动线程,真正意义上的实现了多线程,这是无需使用run()方法体执行。通过Thread类的start()方法,来启动的线程处于就绪(可运行)但未运行状态,一旦得到CPU调度就开始执行run()方法(即线程主体),它包含了要执行线程的内容,run方法结束,则此线程终止。

               5.2、run():如果该线程是使用独立的Runnable对象构造的,则调用runnable对象的Run方法,否则该方法不执行操作并返回。Thread的子类应该重写该方法。run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

               5.3、总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。

六、Java多线程的就绪,运行和死亡状态

        就绪状态转换为运行状态:当此程序得到处理器资源;

        运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

        就绪状态转换为死亡状态:当此线程执行体执行完毕或者发生异常。

        理解此处的状态换换描述参考四、线程的生命周期及五种基本状态中图 4-1。

        注:当线程调用yield()函数时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。(了解即可)

七、多线程的安全问题

       7.1、多线程出现安全问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了部分,剩下的部分由其他线程参与进来,此时有可能导致数据共享出现错误。

       7.2、解决思路:对多条操作共享数据的语句,只能让一个线程都执行完,否则其他线程不能参与执行。

       7.3、解决办法:java对于多线程的安全问题提供了专业的解决办法,就是同步代码块。Synchronized(对象){需要被同步的代码},对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取cpu执行权,也进不去,因为没有获取锁。使用同步锁相应的增加了一个判断同步锁的线程,耗费资源。

       7.4、同步锁的使用前提:1、必须有两个或者两个以上的线程。2、这些线程必须使用同一个同步锁。(定义同步函数(即同步锁)只需要在方法用synchronized修饰即可   这里不再赘述)注:多线程死锁。同步中嵌套同步会出现死锁。

       7.5、多线程的单例模式(懒汉模式):懒汉式与饿汉式的区别:懒汉式能延迟实例的加载,如果多线程访问时,懒汉式会出现安全问题,可以使用同步来解决,用同步函数和同步代码都可以,但是比较低效,用双重判断的形式能解决低效的问题,加同步的时候使用的锁是该类锁属的字节码文件对象。

八、Java线程池;

        线程池简介:所谓线程池就是将多个线程放在池子里(池化技术),然后在需要线程的时候不再是创建线程,而是从线程池里变直接取一个可用的线程执行任务。线程池的关键在于其为我们管理了多个线程,无需我们去一一创建线程,只需要关系核心业务,然后去线程池里直获取线程,在任务执行完成后线程不会被销毁,而是会被重新放回到线程池当中,等待机会再去执行任务。

        线程池的作用:首先是线程池为们提高了一种简易的多线程编程方案,无需认为去管理这些线程。其次,在使用线程的时候创建和销毁线程的代价是很高昂的,甚至我们创建和销毁线程所消耗的资源要远大于程序本身运行所需的资源,在线程池接管了这一部分业务之后,这种矛盾就得到了缓解。最后,一个程序当中的线程过多的话,可能会出现由于线程之间的频繁切换,造成的安全等问题。学完理论,要看实践

   下面介绍创建四种线程池的方法:

    1、newCachedThreadPool:可缓存线程池,如果线程池超过处理需要,可灵活回收空闲线程,如果无空闲线程,则创建。线程池为无限大,当第二个任务执行时第一个任务已经完成,则会复用这个线程,不会重复创建。

    2、newFixedThreadPool :定长线程池,可控制线程最大并发数,超出线程则会在队列等待。

    3、ScheduledThreadPool:定数线程池,支持定时和周期性的执行任务。

    4、newSingleThreadExecutor:单个线程线程池,使用唯一的工作线程来执行任务,保证所有的任务按照指定的顺序执行。

 Java中线程池的顶级接口是Executor,但其实Executor并不是一个线程池,只是也执行线程的工具而已,真正的线程池接口是ExecutorService ,下面介绍几个线程池重要的类;

       ExceutorService:真正的线程池接口;

       ScheduledExecutorService:能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。

       ThreadPoolExecutor:ExceutorService的默认实现。

       ScheduledThreadPoolExecutor:继承自ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

       

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值