多线程

来源:互联网 发布:java开发工程师累不累 编辑:程序博客网 时间:2024/05/16 05:38


 

Java中,程序都是在JVM上运行的,一个JVM占一个进程,所以多进程的概念,应该不存在。

 

1、线程与进程

 

1.1进程

 

狭义上讲:正在执行的程序,由线程组成,可包含多个线程在运行。

广义上讲:进程是一个具有一定独立功能的程序有关于某个数据集合的一次运行的活动。它可以申请或者拥有系统资源,是一个动态的概念。

进程的概念主要两点:1.进程是一个实体,每一个进程都有自己的地址空间,一般情况下包括文本区域,数据区和堆栈。

这里写图片描述

 

1.2线程

 

为进程中执行的程序片段。

一个线程由线程ID,当前指令针,寄存器和堆栈组成,另外线程是进程的实体,是被系统独立调试的分派的基本单元。

线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程

 

2线程中有两种启动方法:

1)一种是继承Thread类,重写run方法

2)另一种是实现Runnable接口,实现run方法。

由于Java是单继承的,继承一个类后不能继承其他类。用实现runnable接口启动线程,弥补了java单继承。

 

3、线程的状态(线程的生命周期)

1)创建 : 创建线程也就是new线程;

2)就绪:调用了start()方法,等待cpu调度;

3)运行:执行了run()方法;

4)阻塞:也就当前线程处于是停止状态,cpu调用其他线程执行。

5)消亡:线程销毁。在线程里面的代码执行完毕后,线程自动销毁。

 

4、线程的常用方法

线程的常用方法基本上都在Thread

 (1)得到当前线程名称:Thread.currentThread().getName()

 (2)取得当前线程对象:currentThread();

 (3)线程是否执行:isAlive()

 (4)线程的强行运行:join();

 (5)线程休眠:sleep();

 

5、线程同步

理解synchronized()关键字

   用来给对象,方法,或者代码加锁,当它锁定一个方法或者代码块的时候,同一个时间只能有一个线程执行这段代码。通常用于多个线程访问一个对象的时候使用。它确保了其他线程必须等待当前线程执行完毕后才能执行这个代码块或者方法。

 

 

6线程池

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

1)线程池的原因

     对于java来说频繁的创建和销毁对象是比较耗费内存资源的,每次创建一个线程也是一个对象,因此就产生了线程池。

2)应用情况:需要大量的线程来完成的工作,并且每个工作需要的时间比较短。

3)线程池中内部工作:

可以规定线程池中线程的数量(也就是最多创建线程的数量),当一个线程执行完一个任务的时候,会接着执行另一个任务,如果这个线程发生异常,则由另一个线程池中的线程来代替,如果任务数量多于线程数量,则没有完成的任务处于等待阶段。

 

6.1创建线程池的几种方法

 

java5以后,新增了一个Executors工厂类来创建线程池,在java5在以前都是通过自己实现创建线程池。

Executors中提供了以下几个静态方法来创建线程池。

1newCachedThreadPool()创建一个具有缓存功能的线程池。

public static ExecutorService newCachedThreadPool()

2newFixedThradExecutor(int Threads)创建一个具有固定线程数量的线程池。

public static ExecutorService newFixedThreadPool(int nThreads,ThreadFactory threadFactory)

3newSingleThreadExecutor()创建一个只有一个线程的线程池(一直为thread-1)。

public static ExecutorService newSingleThreadExecutor()

4newScheduledThreadPool()创建具有指定线程数量的线程池,这个线程池中的线程可以指定延迟时间来执行。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

 

6.2创建线程池的步骤:

(1)调用Executors类的静态方法创建一个ExecutorService对象,得到一个线程池。

(2)创建RunnbalerThread的实现类。

(3)通过submit()或execute()方法向线程池提交runnbale线程

(4)通过shutdown()方法关闭线程池

 

6.3实例:

package com.thrad.demo;

import java.util.concurrent.Executors;

import java.util.concurrent.ExecutorService;

/**

* Java线程:线程池-固定大小的线程池、单任务线程池、可变尺寸的线程池

*

*/

public class Test1 {

public static void main(String[] args) {

//创建一个可重用固定线程数的线程池

//ExecutorService pool = Executors.newFixedThreadPool(2);

//创建一个使用单个 worker线程的 Executor,以无界队列方式来运行该线程

//ExecutorService pool = Executors.newSingleThreadExecutor();

//创建一个可根据需要创建新线程的线程池

ExecutorService pool = Executors.newCachedThreadPool();

//创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口

Thread t1 = new MyThread();

Thread t2 = new MyThread();

Thread t3 = new MyThread();

Thread t4 = new MyThread();

Thread t5 = new MyThread();

//将线程放入池中进行执行

pool.execute(t1);

pool.execute(t2);

pool.execute(t3);

pool.execute(t4);

pool.execute(t5);

//关闭线程池

pool.shutdown();

}

}

class MyThread extends Thread{

@Override

public void run() {

//System.out.println(Thread.currentThread().getName() + "正在执行。。。");

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

         //同步锁synchronized()

         synchronized (this) {

            if(ticket>0){

                try {

                    Thread.sleep(500);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                System.out.println("车票"+ticket--);

            }

         }

        }

}

}

 

6.4 深入剖析线程池实现原理

 

6.4.1.线程池状态

ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:

volatile int runState;

static final int RUNNING    = 0;

static final int SHUTDOWN   = 1;

static final int STOP       = 2;

static final int TERMINATED = 3;

runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;

下面的几个static final变量表示runState可能的几个取值。

当创建线程池后,初始时,线程池处于RUNNING状态;

如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

当线程池处于SHUTDOWNSTOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

 

6.4.2.任务的执行

  通过调用submit()execute()方法执行run()方法。

 

6.4.3.线程池中的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

prestartCoreThread():初始化一个核心线程;

prestartAllCoreThreads():初始化所有核心线程

 

6.4.4.任务缓存队列及排队策略

在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。

workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

(1).ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

(2).LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE

(3).synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

 

6.4.5.任务拒绝策略

务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

 

6.4.6线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()shutdownNow(),其中:

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

 

6.4.7线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()setMaximumPoolSize()

setCorePoolSize:设置核心池大小

setMaximumPoolSize:设置线程池最大能创建的线程数目大小

当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

实例:

public class Test {

     public static void main(String[] args) {   

         ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));  

         for(int i=0;i<15;i++){

             MyTask myTask = new MyTask(i);

             executor.execute(myTask);

             System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+

             executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());

         }

         executor.shutdown();

     }

}

class MyTask implements Runnable {

    private int taskNum;

     

    public MyTask(int num) {

        this.taskNum = num;

    }

    @Override

    public void run() {

        System.out.println("正在执行task "+taskNum);

        try {

            Thread.currentThread().sleep(4000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("task "+taskNum+"执行完毕");

    }

}

6.4.8线程池容量的静态处理

Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE

Executors.newSingleThreadExecutor();   //创建容量为1的缓冲池

Executors.newFixedThreadPool(int);    //创建固定容量大小的缓冲池

 

newFixedThreadPool创建的线程池corePoolSizemaximumPoolSize值是相等的,它使用的LinkedBlockingQueue

newSingleThreadExecutorcorePoolSizemaximumPoolSize都设置为1,也使用的LinkedBlockingQueue

newCachedThreadPoolcorePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue

也就是说来了任务就创建线程运行,当线程空闲超过规定时间,就销毁线程。

实例:

 public class ThreadPoolTest {

    public static void main(String[] args) {

        ExecutorService threadPool1 = Executors.newFixedThreadPool(5);

        ExecutorService threadPool2 = Executors.newCachedThreadPool();

        ExecutorService threadPool = Executors.newSingleThreadExecutor();

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

            final int task = i;

            threadPool.execute(new Runnable() {

 

                @Override

                public void run() {

                    for (int j = 1; j <= 10; j++) {

                        try {

                            Thread.sleep(20);

                        } catch (InterruptedException e) {

                            e.printStackTrace();

                        }

                        System.out.println(Thread.currentThread().getName()

                                + " is looping of " + j + " for  task of "

                                + task);

                    }

                }

            });

        }

        System.out.println("all of 10 tasks have committed! ");

        // threadPool.shutdownNow();

 

        Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable() {

            public void run() {

                System.out.println("begining!");

            }

        }, 6, 2, TimeUnit.SECONDS);

    }

 

6.4.9如何合理配置线程池的大小

一般需要根据任务的类型来配置线程池大小:

如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为  +1

如果是IO密集型任务,参考值可以设置为2*

当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,

再观察任务运行情况和系统负载、资源利用率来进行适当调整。

0 0
原创粉丝点击