Java语言学习(十二):多线程

    Java中给多线程编程提供了内置的支持,多线程是多任务的一种特别形式,它使用了更小的资源开销。这里须要知道两个术语及其关系:进程和线程。html

    进程:进程是系统进行资源分配和调度的一个独立单位。一个进程包括由操做系统分配的内存空间,包含一个或多个线程。java

    线程:线程是进程的一个实体,是CPU调度和分派的基本单位。它可与同属一个进程的其余的线程共享进程所拥有的所有资源。数据库

    (一)线程的生命周期编程

    线程是一个动态执行的过程,从产生到死亡,这个过程称为线程的生命周期。线程的状态有:新建状态、就绪状态、运行状态、阻塞状态、死亡状态,以下图所示,注意整个执行过程的实现:安全

  • 新建状态(New):当线程对象对建立后,即进入了新建状态;
  • 就绪状态(Runnable):当调用线程对象的start()方法,线程即进入就绪状态,等待CPU调度执行;
  • 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态;
  • 阻塞状态(Blocked):处于运行状态中的线程因为某种缘由,中止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态;
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期;

    (二):线程的建立多线程

    Java提供了三种建立线程的方法:并发

  • 实现Runnable接口
  • 继承Thread类
  • 经过Callable和Future建立; 

    在开发中,前两种是经常使用的线程建立方式,下面来简单说下:ide

     (1)经过实现Runnable接口建立线程函数

public class RunnableDemo implements Runnable{
    private Thread t;
    private String threadName;

    //构造方法
    public RunnableDemo(String name) {
        this.threadName = name;
        System.out.println("建立线程:"+threadName);
    }
    //重写run()方法
    @Override
	public void run() {
        System.out.println("运行线程:"+threadName);
        try {
            for(int i=4;i>0;i--){
                System.out.println("Thread: "+threadName+","+i);
                Thread.sleep(50);
            }
        }catch (Exception e) {
             System.out.println("Thread "+threadName+" 阻塞");
        }
        System.out.println("Thread "+threadName+" 终止"); 
    }
    //调用方法(为了输出信息,能够忽略)
    public void start(){
        System.out.println("启动线程:"+threadName);
        if(t == null){
           t = new Thread(this,threadName);
           t.start(); 
        }  
    }
}

    测试类:工具

public class RunnableTest {
    public static void main(String[] args) {
        RunnableDemo r1 = new RunnableDemo("T1");
        r1.start();
        RunnableDemo r2 = new RunnableDemo("T2");
        r2.start();
    }   
}

    输出为:

建立线程:T1
启动线程:T1
建立线程:T2
启动线程:T2
运行线程:T1
Thread: T1
运行线程:T2
Thread: T2,4
Thread: T2,3
Thread: T1,3
Thread: T2,2
Thread: T1,2
Thread: T2,1
Thread: T1,1
Thread T2 终止
Thread T1 终止

    从上面的实例能够看出,经过实现Runnable接口建立线程的几个要点:

  • 构造方法来建立线程对象,有参或无参看本身须要;
  • 重写run()方法,这里写入本身须要实现的代码;
  • 启动start()方法,这里能够直接调用;
  • run()方法是线程的入口点,必须经过调用start()方法才能执行;

    (2)经过继承Thread类建立线程

    它本质上也是实现了 Runnable 接口的一个实例,因此这里就不贴出代码了,能够按照上面的实例,更改class为继承便可,以下:

public class ThreadDemo extends Thread{}

    Thread类的经常使用且重要的方法有:

  • public void start():使该线程开始执行;Java虚拟机调用该线程的 run 方法,对象调用;
  • public void run():对象调用;
  • public static void sleep(long millisec):在指定的毫秒数内让当前正在执行的线程休眠,静态方法,直接调用;

     注意:Java虚拟机容许应用程序并发的运行多个执行线程,利用多线程编程能够编写高效的程序,但线程太多,CPU 花费在上下文的切换的时间将多于执行程序的时间,执行效率反而下降,因此,线程并非建立的越多越好好,通常来讲小到1个,大到10左右基本就够用了。

    固然,关于线程的其余知识,如优先级、休眠、终止等,这里就不作介绍了。

    (三)synchronized关键字

    Java提供了不少方式和工具来帮助简化多线程的开发,如同步方法,即有synchronized关键字修饰的方法,这和Java的内置锁有关。每一个Java对象都有一个内置锁,若方法用synchronized关键字声明,则内置锁会保护整个方法,即在调用该方法前,须要得到内置锁,不然就处于阻塞状态。一个简单的同步方法声明以下:

public synchronized void save(){}

    synchronized关键字也能够修饰静态方法,此时若调用该静态方法,则会锁住整个类。下面经过实例来讲明下具体的使用:

    同步线程类:

public class SyncThread implements Runnable {
   //定义计数变量并在构造函数中初始化
   private static int count;
   public SyncThread(){
	   count = 0;
   }
   @Override
   public synchronized void run() {
       for(int i=0;i<5;i++){
          //打印当前count值并进行累加操做,可分开写
          System.out.println(Thread.currentThread().getName() +":"+ (count++));
          try {
             Thread.sleep(100);
          }catch (InterruptedException e) {
             e.printStackTrace();
          }
       }
   }
   public int getCount(){
		return count;
   }  
}

    测试类:

public class SyncTest {
    public static void main(String[] args) {
         SyncThread sThread = new SyncThread();
         //建立线程对象的同时初始化该线程的名称
         Thread t1 = new Thread(sThread,"SThread1");
         Thread t2 = new Thread(sThread,"SThread2");
         t1.start();
		 t2.start();
    }
}

    输出为:

SThread1:0
SThread1:1
SThread1:2
SThread1:3
SThread1:4
SThread2:5
SThread2:6
SThread2:7
SThread2:8
SThread2:9

    从上面能够看出:一个线程访问一个对象中的synchronized同步方法时,其余试图访问该对象的线程将被阻塞。固然,你们能够去掉synchronized关键字,看看会有什么不一样。这里必需要注意:是访问同一个对象的不一样方法,如上面的对象sThread,如果不一样的对象,则不受阻塞。这里不作介绍了,你们能够参考:Java中synchronized的用法,好好理解下。

    (四)volatile关键字

    相比较synchronized而言,volatile关键字是Java提供的一种轻量级的同步机制,为域变量的访问提供了一种免锁机制,使用volatile修饰域至关于告诉虚拟机该域可能会被其余线程更新,所以每次使用该域就要从新计算,而不是使用寄存器中的值。

    若是读操做的次数要远远超过写操做,与锁相比,volatile 变量一般可以减小同步的性能开销。简单的定义以下:

private volatile int count = 0;

    volatile不具有原子特性,也不能用来修饰final类型的变量。要使volatile修饰的变量提供理想的线程安全,必须知足两个条件:

  • 对变量的写操做不依赖于当前值;
  • 变量没有包含在具备其余变量的不变式中;

    这里不作详述了,但须要注意一点:避免volatile修饰的变量用于复合操做,如 num++,这个复合操做包括三步(读取->加1->赋值),因此,在多线程的环境下,有可能线程会对过时的num进行++操做,从新写入到主存中,而致使出现num的结果不合预期的状况。

    线程间还能够实现通讯,这里不作介绍。

    Java中的对象使用new操做符建立,若建立大量短生命周期的对象,则性能低下。因此才有了池的技术,如数据库链接有链接池,线程则有线程池。

    使用线程池建立对象的时间是0毫秒,说明其高效性。你们感兴趣的可自行查看、了解该块的知识点。

    好了,以上归纳的就是多线程的基本知识点了,但愿帮到你们。

相关文章
相关标签/搜索