欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

Java 多线程,

来源: javaer 分享于  点击 22170 次 点评:228

Java 多线程,


多线程

1. 线程与进程的关系

   (1) 进程是运行中的程序,具有如下特点:

       ① 独立性:拥有自己的资源和独立的内存区,通常来说,一个程序的内存空间是不允许其它进程访问的;

       ② 动态性:程序是静止的,运行起来才称作进程;

       ③ 并发性:操作系统可以同时“并发(concurrent)”运行多个进程。

   (2) 线程:进程中的“并发”执行流;

   (3) 进程与线程的区别:进程(Process)是有独立的内存空间的,因而创建进程的代价比创建线程大;

   (4) 多线程的优势:

       ① 线程功能上类似于进程,但创建代价比进程小,效率更高;

       ② 所有线程共享进程的内存,因此线程间的通信非常方便;

       

2. 线程的创建和启动

   (1) Thread ——就是线程,Java 程序默认有主线程,主线程的线程执行体(线程将要做的事情)就是 main()方法;

   (2) Java 创建线程的3种方法和启动方法:

      ① 继承 Thread,重写一个 run() 方法 ,这个run()方法就是线程执行体;

         —— 该run()方法没有返回值,不能抛出异常

         —— 创建Thread 的子类的实例就可以启动

      示例程序:

       public class FirstThread extends Thread

    {

@Override 

//run()方法重写父类的run()方法,没有返回值也不能声明抛出异常

public void run()

{

for(int i=0;i<10;i++)

{

// currentThread()用于返回当前正在运行的线程

// getName()方法用于获得线程的名称

System.out.println(FirstThread.currentThread().getName()+"----"+i);

}

}

public static void main(String[] args)

{

//********************************

//FirstThread ft = new FirstThread();

//ft.start();

//等同下面的写法

//创建Thread 的子类的实例调用start()来启动线程

  new FirstThread().start();

//以下作为主线程的线程执行体

for(int i=0;i<10;i++)

{

// currentThread()用于返回当前正在运行的线程

// getName()方法用于获得线程的名称

System.out.println(FirstThread.currentThread().getName()+"----"+i);

}

}

运行结果:(一般不同机器多次运行结果可能会不同)




      ② 实现Runnable接口,重写一个 run() 方法 ,这个run()方法就是线程执行体;

 —— 该run()方法没有返回值,不能抛出异常

 —— 需要将 Runnable对象包装成 Thread对象之后再启动

示例程序:

public class SecondThread implements Runnable

{

@Override 

//run()方法重写父类的run()方法,没有返回值也不能声明抛出异常

public void run()

{

for(int i=0;i<10;i++)

{

// currentThread()用于返回当前正在运行的线程

// getName()方法用于获得线程的名称

System.out.println(Thread.currentThread().getName()+"----"+i);

}

}

public static void main(String[] args)

{

        //需要将Runnable包装成 Thread对象才能启动

SecondThread st = new SecondThread();

new Thread(st).start();

//以下作为主线程的线程执行体

for(int i=0;i<10;i++)

{

// currentThread()用于返回当前正在运行的线程

// getName()方法用于获得线程的名称

System.out.println(Thread.currentThread().getName()+"----"+i);

}

}

        运行结果:(一般不同机器多次运行结果可能会不同)


      ③ 实现 Callable 接口(相当于是 Runnable的增强版)

         —— 重写call()方法,该方法有返回值,可以声明抛出异常

         —— 需要先将Callable包装成RunnableFutureTask),再把Runnable包装成Thread对象后再启动;FutureTask 还可以用于获取线程的返回值 。

         示例程序:

import java.util.concurrent.*;

public class ThirdThread implements Callable

{

@Override

    //该 call()方法有发返回值、可以声明抛出异常,还可以使用泛型

public Integer call() throws InterruptedException

{

for(int i=0;i<10;i++)

{

// currentThread()用于返回当前正在运行的线程

// getName()方法用于获得线程的名称

System.out.println(Thread.currentThread().getName()+"----"+i);

}

return 0;

}

public static void main(String[] args)

{

ThirdThread tt = new ThirdThread();

//Callable包装成 FutureTaskRunnable接口的实现类)

//可以使用泛型

FutureTask<Integer> ft = new FutureTask<>(tt);

new Thread(ft).start();

        

//以下作为主线程的线程体

for(int i=0;i<10;i++)

{

// currentThread()用于返回当前正在运行的线程

// getName()方法用于获得线程的名称

System.out.println(Thread.currentThread().getName()+"----"+i);

}

}

}

运行结果:(一般不同机器多次运行结果可能会不同)


   (3) 线程的启动:调用 Thread对象的 start()方法,注意不是直接调用run()方法,直接调用run()方法只是普通的方法调用,不会启动多线程。

   (4) 创建线程的方法比较

      ① 创建线程的方法可以分为2类:

         — 继承 Thread

         — 实现 Runnable 和 Callable 接口

      ② 实际中,实现接口的方法比较好,原因是:实现接口之后依然可以继承其它类,但是继承了 Thread 类就无法再继承其它类;实现接口时,可以让多个线程共享同一个 Runnable 对象,可以更好地实现代码与数据的分离,形成更清晰的逻辑,唯一的不足时编程相对略微的复杂。 

3. 线程的状态

   —— 当调用 start()方法后,只是启动线程,线程并不会立即执行。线程的状态变迁图如下:


4. 控制线程的方法

   (1)  join 线程 

        —— 让加一个线程等待另一个线程的方法,当某个程序在执行流中调用其他线程的 join()方法时,调用线程将会被阻塞,直到被 join方法假如的join线程完成为止,简单的说就是调用join方法的线程必须等到被 joined 的线程结束才能再次执行。

   (2)  后台线程

        —— Daemon Thread ,即守护线程或者精灵线程,JVM的垃圾回收线程就是典型的后台线程;

        —— 特点是所有的前台线程结束,后台进程会自动死亡(注意主线程结束,子线程并不一定结束);

        —— 调用Thread对象的 setDaemontrue)可将指定线程是指成为后台线程。

   (3)  线程等待(暂停)

        —— Thread.sleeptime):让线程等待(暂停)time  ms,并进入阻塞状态。

   (4)  线程让步

        —— Thread.yield() :让线程让出CPU,进入就绪状态,所以可能出现某进程让出CPU进入就绪状态后,线程调度器自此将其调度出来执行。

   ★ sleep 和 yield 的区别

      ① sleep方法暂停当前进程后,会给其他线程执行机会,不会理会其它进程的优先级,但是yield 方法只会给优先级相同或者更高的线程执行机会;

      ② sleep 方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态,而yield方法是强制当前进程进入就绪状态;

      ③ sleep 方法声明抛出了InterruptedException 异常,所以调用sleep方法是要么捕捉该异常,要么显示声明抛出该异常,而yield方法没有声明抛出异常;

      ④ sleep方法比yield方法有更好的可移植性,通常不要依靠yield来控制并发线程的执行。

5.线程同步(线程安全)

   (1) 竞争资源(共享资源)

   —— 如果有多条线程需要并发访问,并修改某个对象,该对象就是“竞争资源”,为了避免多个线程“自由竞争”修改共享资源所导致的不安全问题,可以考虑加锁。

   (2) 线程同步方法:

     ① 用同步代码块:需要显示指定同步监视器;

     ② 用同步方法:相当于使用方法调用者作为同步监视器,如果该方法是实例方法,相当于用 this作为同步监视器,如果该方法是类方法,相当于以类作为同步监视器;

 ★ 二者的实现机制是完全相同的,从语法角度来看,任意对象都可作为同步器,从程序逻辑看,选择“竞争资源”做同步监视器。

6. 线程通信

   —— 当一个程序中有多条并发线程执行时,线程之间是互不干扰的,有些时候需要在线程之间进行通信,让多条线程按照某种交替进行。

   Object 提供了如下方法:

— wait() :让当前正在执行的线程“等待”,进入阻塞状态,同时会释放对同步监视器的锁定;

— motify() :唤醒处于“等待”的线程,让其进入就绪状态; 

— motifyAll() :唤醒处于“等待”的所有线程,让其进入就绪状态; 

★ 以上3种方法,表面上属于Object类,任何方法都可以调用,但实际是上只有同步器监听器才能调用。

7. 线程组

   —— Java使用ThreadGroup代表线程组,在创建一个Thread实例时,通过传入的ThreadGroup对象,即可将该线程放入指定的线程组。Java通过线程组来对一批线程进行整体的管理。

   —— JDK 1.5 之前,如果 线程出现了异常,系统会自动回调其所在的线程组的uncaughtException()方法来修复异常;在JDK1.5之后,线程允许自行设置“异常处理器”,无需线程组:

        Thread.setDefaultUncaughtExceptionHandler  为所有线程(包括主线程)设置默认的异常处理器

实例.setUncaughtExceptionHandler  为当前线程实例设置异常处理器

★ 实例设置的异常处理器会覆盖默认的异常处理器

8. 线程池(Pool

  —— 池的本质就是一种“缓存”技术,是否缓存一个对象,取决于该对象的创建成本,同时还要考虑到系统的内存大小;

       缓存的本质是牺牲空间换取时间,线程对象的创建成本相对普通的Java对象依然较大,但是比创建进程的成本小。为了解决这个问题,就要用到线程池。

  —— Java对线程池的良好支持:

       Executors  —创建线程池、线程工厂的工具类

       ExecutorService  — 代表线程池

       ScheduledExecutorService  —  ExecutorService 的子接口,可以周期性的调度任务

  —— 线程池的编程步骤:

       ① 通过 Executors 的静态工厂方法创ExecutorService 或 ScheduledExecutorService 

       ② 调用 ExecutorService 的方法提交线程即可

       ③ 调用线程池的showdown() 关闭线程池

   

相关文章

    暂无相关文章
相关栏目:

用户点评