博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 多线程原理
阅读量:6767 次
发布时间:2019-06-26

本文共 6037 字,大约阅读时间需要 20 分钟。

   Java 中多线程部分是Java 开发中的重要组成部分。创建java 多线程常用有三个方法:

   1、继承Thread 类创建线程。

   2、实现Runnable 接口创建线程。

   3、实现Callable和Future 创建线程。

------------------------继承Thread类创建线程---------------------

通过继承Thread类来创建并启动多线程的一般步骤如下:

1)创建Thread 的子类,重写run()方法,该方法的方法体是线程要完成的任务。run()也称为线程执行体。

2)创建Thread的子类的实例,也就是创建了线程对象。

3)  启动线程,调用线程的start()方法。 run()方法是运行当前线程,start()是启动一个新的线程。

实例:

  public class  subThread extends Thread{

  public void run(){

    //重写run方法

  }

}

 public class ThreadTest{

 

       public static void main(String[] args){

      new SubThread().start(); //创建并启动线程

        }

 

  }

------------------------实现Runnable接口创建线程---------------------

 通过实现Runnable 接口来创建并启动多线程的一般步骤如下:

  1) 定义Runnable 接口的实现类,一定要重写Run()方法,这个run()方法和Thread 中的run() 一样是线程的执行体。

  2) 创建Runnable 实现类的实例,并用这个实例作为Thread的target 来创建Thread对象,这个Thread对象才是真正的线程对象。

  3) 通过调用线程对象的start()方法来启动线程。

 

  public class SubThread2{

  public void  run(){

       ///重写run() 方法

     }

 

 }

 

  public class TestMain(){

    public static void main(String[] args){

        SubThread2 subThrad=new SubThread2();

                         Thread thread=new Thread(subThrad);

                          thread.start(); 

             } 

   }

 

------------------------使用Callable和Future创建线程---------------------

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

》call()方法可以有返回值

》call()方法可以声明抛出异常

Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

>boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

>V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

>V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

>boolean isDone():若Callable任务完成,返回True

>boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

代码实例:

public class Main {

  public static void main(String[] args){

   MyThread3 th=new MyThread3();

   //使用Lambda表达式创建Callable对象

   //使用FutureTask类来包装Callable对象

   FutureTask<Integer> future=new FutureTask<Integer>(

    (Callable<Integer>)()->{

      return 5;

    }

   );

   new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程

   try{

    System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回

   }catch(Exception e){

    ex.printStackTrace();

   }

  }

}

--------------------------------------三种创建线程方法对比--------------------------------------

实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。

2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。

4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

注:一般推荐采用实现接口的方式来创建多线程

 

-----------------------------------------线程池原理-----------------------------------------------------------------------------------------

JAVA  ExecutorService 中线程池主要有四种,

Java通过Executors提供四种线程池,分别为:

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

(1). newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int index = i;try {Thread.sleep(index * 1000);} catch (InterruptedException e) {e.printStackTrace();}  cachedThreadPool.execute(new Runnable() {  @Overridepublic void run() {System.out.println(index);}});}

 

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

缺点:当线程无法控制使用时,容易造成内存溢出。

(2). newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

for 
(
int 
i = 
0
; i < 
10
; i++) {
final 
int 
index = i;
fixedThreadPool.execute(
new 
Runnable() {
  
@Override
public 
void 
run() {
try 
{
System.out.println(index);
Thread.sleep(
2000
);
catch 
(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
 

因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。

定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()

 

(3) newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);scheduledThreadPool.schedule(new Runnable() {  @Overridepublic void run() {System.out.println("delay 3 seconds");}}, 3, TimeUnit.SECONDS);

表示延迟3秒执行。

定期执行示例代码如下:

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {  @Overridepublic void run() {System.out.println("delay 1 seconds, and excute every 3 seconds");}}, 1, 3, TimeUnit.SECONDS);

表示延迟1秒后每3秒执行一次。

ScheduledExecutorService比Timer更安全,功能更强大,后面会有一篇单独进行对比。

(4)、newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {final int index = i;singleThreadExecutor.execute(new Runnable() {  @Overridepublic void run() {try {System.out.println(index);Thread.sleep(2000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});}

 

果依次输出,相当于顺序执行各个任务。

线程池的作用:

线程池作用就是限制系统中执行线程的数量。

     根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排 队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池 中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要用线程池:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

比较重要的几个类:

ExecutorService

真正的线程池接口。

ScheduledExecutorService

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

ThreadPoolExecutor

ExecutorService的默认实现。

ScheduledThreadPoolExecutor

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

 

转载于:https://www.cnblogs.com/shunxiyuan/p/8589985.html

你可能感兴趣的文章
用javcscript记住用户名和密码保存在本地储存中,然后实现前端获取
查看>>
css中样式的优先级简单总结
查看>>
端口聚合配置
查看>>
易学笔记--程序猿踩过的十个最典型的坑
查看>>
Systemstate Dump分析经典案例(上)
查看>>
Win7+Ubuntu11
查看>>
克隆centos7后如何改网卡配置文件生效?
查看>>
Razor Components启用服务器渲染 更提升低速网络浏览体验
查看>>
豆瓣的账号登录及api操作
查看>>
python 高阶函数:sorted(排序)
查看>>
前端与移动开发之vue-day1(3)
查看>>
网络osi七层复习,未复习整理完,后续补齐
查看>>
python--004--函数定义
查看>>
在中国,有多少程序员干到40了?那么其他人去干什么了?
查看>>
C盘里的文件夹都是干什么用的?
查看>>
PHP商城 Composer 以及PSR规范
查看>>
一个线程罢工的诡异事件
查看>>
嵌入式培训大纲 看看具体的课程学习内容有哪些
查看>>
华三模拟器telnet远程登陆
查看>>
带外监控
查看>>