`

线程的一点小总结

    博客分类:
  • java
阅读更多
java中main方法启动的是一个进程还是一个线程?
答:是一个线程也是一个进程,一个java程序启动后它就是一个进程,进程相当于一个空盒,它只提供资源装载的空间,具体的调度并不是由进程来完成的,而是由线程来完成的。一个java程序从main开始之后,进程启动,为整个程序提供各种资源,而此时将启动一个线程,这个线程就是主线程,它将调度资源,进行具体的操作。Thread、Runnable的开启的线程是主线程下的子线程,是父子关系,此时该java程序即为多线程的,这些线程共同进行资源的调度和执行。

每个Java服务启动的时候相当于是启动一个进程,
像日常的接口项目里面,每次我们请求controller里的某个接口一次,每一个请求过来进到服务里都会新启一个线程,系统都会新启一个线程,直到这个请求执行完返回给调用端,系统自动释放这个线程,这个算主线程,在这个主线程执行的过程中,如果我们自己再启动新的线程或者我们调用一些别的控件或方法里启动新的线程,这些都是新启动的线程,这些线程和主线程没有任何关系,都是不同的线程。


Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。


(1) newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。示例代码如下:
public class ThreadPoolExecutorTest {
    // main方法执行的时候启动一个main主线程
    public static void main(String[] args) {
        // 线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                // 当前现成睡 index*1000毫秒,当前线程为main方法的主线程
                Thread.sleep(index * 1000);
                // 打印当前代码执行所在的线程名称
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 调用线程池启动一个线程,这个线程是main主线程的子线程
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    // 执行完之后从线程池里拿出来的线程直接就释放了,所以下次循环从线程池拿线程执行这个打印可能用的还是同一个线程
                    System.out.println(index);
                    // 打印当前代码执行所在的线程名称(应该是从线程池里拿出来的线程的名称)
                    System.out.println("========================" + Thread.currentThread().getName());
                }
            });
        }
    }
}


public class ThreadPoolExecutorTest {
    // main方法执行的时候启动一个main主线程
    public static void main(String[] args) {
        // 线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                // 当前现成睡 index*1000毫秒,当前线程为main方法的主线程
                Thread.sleep(index * 1000);
                // 打印当前代码执行所在的线程名称
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 调用线程池启动一个线程,这个线程是main主线程的子线程
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(index);
                    // 打印当前代码执行所在的线程名称(应该是从线程池里拿出来的线程的名称)
                    System.out.println("========================" + Thread.currentThread().getName());
                    try {
                        // 当前线程睡觉了,所以可能一次循环上面的打印执行完毕之后当前线程还不会释放,下次循环进来的时候从线程池里拿线程,
                        // 发现上次执行的那个线程还在睡觉,还在占用,那么线程池会给他分配一个新的线程来执行当次循环的打印。可能上次执行
                        // 用的那个线程待会才能释放,释放了之后从线程池里拿出来他还可以继续用
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}


(2) newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:
public class ThreadPoolExecutorTest {
    // main方法执行的时候启动一个main主线程
    public static void main(String[] args) {
        // 线程池里最多放3个线程,如果三个线程都占着,而且有新的从线程池里拿线程的请求那么就只能排队等着,等那三个被占用的线程释放
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            System.out.println("当前主线程为:" + Thread.currentThread().getName());
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                public void run() {
                    try {
                        System.out.println(index);
                        System.out.println("==========================" + Thread.currentThread().getName());
                        // 因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。线程池里开始创建的三个线程一次次被重复利用
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}


因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()

(3)  newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
package test;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
 public static void main(String[] args) {
  ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
  scheduledThreadPool.schedule(new Runnable() {
   public void run() {
    System.out.println("delay 3 seconds");
   }
  }, 3, TimeUnit.SECONDS);
 }
}


表示延迟3秒执行。
定期执行示例代码如下:
package test;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
 public static void main(String[] args) {
  ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
  scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
   public void run() {
    System.out.println("delay 1 seconds, and excute every 3 seconds");
   }
  }, 1, 3, TimeUnit.SECONDS);
 }
}


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

(4) newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
 public static void main(String[] args) {
  ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
  for (int i = 0; i < 10; i++) {
   final int index = i;
   singleThreadExecutor.execute(new Runnable() {
    public void run() {
     try {
      System.out.println(index);
      Thread.sleep(2000);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   });
  }
 }
}


结果依次输出,相当于顺序执行各个任务。
你可以使用JDK自带的监控工具来监控我们创建的线程数量,运行一个不终止的线程,创建指定量的线程,来观察:
工具目录:C:\Program Files\Java\jdk1.6.0_06\bin\jconsole.exe
运行程序做稍微修改:

package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
 public static void main(String[] args) {
  ExecutorService singleThreadExecutor = Executors.newCachedThreadPool();
  for (int i = 0; i < 100; i++) {
   final int index = i;
   singleThreadExecutor.execute(new Runnable() {
    public void run() {
     try {
      while(true) {
       System.out.println(index);
       Thread.sleep(10 * 1000);
      }
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   });
   try {
    Thread.sleep(500);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }
}


下面做一些比较深入的底层讲解(简单看下就行):
http://blog.csdn.net/w2393040183/article/details/52177572

线程池底层类库继承关系:






在使用spring框架的时候,如果我们用java提供的方法来创建线程池,在多线程应用中非常不方便管理,而且不符合我们使用spring的思想。(虽然spring可以通过静态方法注入)
其实,Spring本身也提供了很好的线程池的实现。这个类叫做ThreadPoolTaskExecutor。
在spring中的配置如下:
<bean id="executorService" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="${threadpool.corePoolSize}" />
    <!-- 线程池维护线程的最少数量 -->
    <property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" />
    <!-- 线程池维护线程所允许的空闲时间 -->
    <property name="maxPoolSize" value="${threadpool.maxPoolSize}" />
    <!-- 线程池维护线程的最大数量 -->
    <property name="queueCapacity" value="${threadpool.queueCapacity}" />
    <!-- 线程池所使用的缓冲队列 -->
  </bean>


当然也可以在Java类里面申明ThreadPoolTaskExecutor进行线程池的定义,例如:
public final class ThreadPoolUtil {

    // 线程池维护线程的最少数量
    private static final int COREPOOLSIZE = 5;
    // 线程池维护线程的最大数量
    private static final int MAXINUMPOOLSIZE = 20;
    // 线程池维护线程所允许的空闲时间
    private static final long KEEPALIVETIME = 5;
    // 线程池维护线程所允许的空闲时间的单位
    private static final TimeUnit UNIT = TimeUnit.MINUTES;
    // 线程池所使用的缓冲队列
    private static final BlockingQueue<Runnable> WORKQUEUE = new ArrayBlockingQueue<>(100);
    // 线程池对拒绝任务的处理策略:
    /*
     * AbortPolicy为抛出异常
     * CallerRunsPolicy为重试添加当前的任务,自动重复调用execute()方法
     * DiscardOldestPolicy为抛弃旧的任务
     * DiscardPolicy为抛弃当前的任务
     */
    private static final CallerRunsPolicy HANDLER = new ThreadPoolExecutor.CallerRunsPolicy();

    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(COREPOOLSIZE, MAXINUMPOOLSIZE, KEEPALIVETIME, UNIT, WORKQUEUE, HANDLER);

    public static void execute(Runnable r) {
        executor.execute(r);
    }

    public static boolean isShutDown() {
        return executor.isShutdown();
    }

    public static void shutDownNow() {
        executor.shutdownNow();
    }

    public static void shutdown() {
        executor.shutdown();
    }
}


使用线程池的注意事项
•死锁
任何多线程程序都有死锁的风险,最简单的情形是两个线程AB,A持有锁1,请求锁2,B持有锁2,请求锁1。(这种情况在mysql的排他锁也会出现,不会数据库会直接报错提示)。线程池中还有另一种死锁:假设线程池中的所有工作线程都在执行各自任务时被阻塞,它们在等待某个任务A的执行结果。而任务A却处于队列中,由于没有空闲线程,一直无法得以执行。这样线程池的所有资源将一直阻塞下去,死锁也就产生了。
•系统资源不足
如果线程池中的线程数目非常多,这些线程会消耗包括内存和其他系统资源在内的大量资源,从而严重影响系统性能。
•并发错误
线程池的工作队列依靠wait()和notify()方法来使工作线程及时取得任务,但这两个方法难以使用。如果代码错误,可能会丢失通知,导致工作线程一直保持空闲的状态,无视工作队列中需要处理的任务。因为最好使用一些比较成熟的线程池。
•线程泄漏
使用线程池的一个严重风险是线程泄漏。对于工作线程数目固定的线程池,如果工作线程在执行任务时抛出RuntimeException或Error,并且这些异常或错误没有被捕获,那么这个工作线程就异常终止,使线程池永久丢失了一个线程。(这一点太有意思)
另一种情况是,工作线程在执行一个任务时被阻塞,如果等待用户的输入数据,但是用户一直不输入数据,导致这个线程一直被阻塞。这样的工作线程名存实亡,它实际上不执行任何任务了。如果线程池中的所有线程都处于这样的状态,那么线程池就无法加入新的任务了。
•任务过载
当工作线程队列中有大量排队等待执行的任务时,这些任务本身可能会消耗太多的系统资源和引起资源缺乏。
综上所述,使用线程池时,要遵循以下原则:
1. 如果任务A在执行过程中需要同步等待任务B的执行结果,那么任务A不适合加入到线程池的工作队列中。如果把像任务A一样的需要等待其他任务执行结果的加入到队列中,可能造成死锁
2. 如果执行某个任务时可能会阻塞,并且是长时间的阻塞,则应该设定超时时间,避免工作线程永久的阻塞下去而导致线程泄漏。在服务器才程序中,当线程等待客户连接,或者等待客户发送的数据时,都可能造成阻塞,可以通过以下方式设置时间:
调用ServerSocket的setSotimeout方法,设定等待客户连接的超时时间。
对于每个与客户连接的socket,调用该socket的setSoTImeout方法,设定等待客户发送数据的超时时间。
3. 了解任务的特点,分析任务是执行经常会阻塞io操作,还是执行一直不会阻塞的运算操作。前者时断时续的占用cpu,而后者具有更高的利用率。预计完成任务大概需要多长时间,是短时间任务还是长时间任务,然后根据任务的特点,对任务进行分类,然后把不同类型的任务加入到不同的线程池的工作队列中,这样就可以根据任务的特点,分配调整每个线程池
4. 调整线程池的大小。线程池的最佳大小主要取决于系统的可用cpu的数目,以及工作队列中任务的特点。假如一个具有N个cpu的系统上只有一个工作队列,并且其中全部是运算性质(不会阻塞)的任务,那么当线程池拥有N或N+1个工作线程时,一般会获得最大的cpu使用率。
如果工作队列中包含会执行IO操作并经常阻塞的任务,则要让线程池的大小超过可用 cpu的数量,因为并不是所有的工作线程都一直在工作。选择一个典型的任务,然后估计在执行这个任务的工程中,等待时间与实际占用cpu进行运算的时间的比例WT/ST。对于一个具有N个cpu的系统,需要设置大约N*(1+WT/ST)个线程来保证cpu得到充分利用。
当然,cpu利用率不是调整线程池过程中唯一要考虑的事项,随着线程池工作数目的增长,还会碰到内存或者其他资源的限制,如套接字,打开的文件句柄或数据库连接数目等。要保证多线程消耗的系统资源在系统承受的范围之内。
5. 避免任务过载。服务器应根据系统的承载能力,限制客户并发连接的数目。当客户的连接超过了限制值,服务器可以拒绝连接,并进行友好提示,或者限制队列长度。
以上这篇Java线程池的几种实现方法及常见问题解答就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。
  • 大小: 388 KB
  • 大小: 70.2 KB
分享到:
评论

相关推荐

    多线程自己的一点总结.md

    多线程自我总结

    Java线程总结教程

    在论坛上面常常看到初学者对线程的无可奈何,所以总结出了下面一篇文章,希望对一些正在学习使用java线程的初学者有所帮助。 首先要理解线程首先需要了解一些基本的东西,我们现在所使用的大多数操作系统都属于多...

    QT中sqlite多线程操作4个注意问题

    总结了一下Qt中sqlite多线程操作遇到的几个问题,希望能对有需要的朋友一点帮助 总结了一下Qt中sqlite多线程操作遇到的几个问题,希望能对有需要的朋友一点帮助

    linux系统编程之线程.zip

    5. 在linux下,线程最是小的执行单位;进程是最小的分配资源单位 察看LWP号:ps –Lf pid 查看指定线程的lwp号。 三级映射:进程PCB --&gt; 页目录(可看成数组,首地址位于PCB中) --&gt; 页表 --&gt; 物理页面 --&gt; 内存单元...

    Java-学习多线程总结上

    多线程 1. 多线程的基本概念 每一个正在执行的程序都是一个进程,资源只有一块,所以在同一时间段会有多个程序同时执行,但是在一个时间点上,只能由一个...2.用户会感到一点的卡顿问题 3.给系统增加了资源压力 4.多

    Java_SE总结.xmind

    从无到有,一点一点自己总结出来,提供复习,查阅.从头过一遍基础,包括基础的程序语法,和集合,常用类,IO流,多线程,等所有的java知识点

    Linux下多线程编程技术

    作为一个IT人员,不断的学习和总结是我们这个职业习惯,所以我会将每个阶段的学习都会通过一点的总结来记录和检测自己的学习效果,今天为大家总结了关于Linux下多线程编程技术

    桌面弹球游戏代码以及个人总结

    这是我自己写的总结,不是很好,但还凑合,希望对大家有帮助

    从小程序不支持DOM操作开始深入分析小程序运行机制

    小程序现在如日中天,各大公司都推出了自己的小程序平台,...本文大部分是官方文档引用加上自己一点总结。 web开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应。开发者可以使用

    css 基础知识总结以及demo

    可以和其它、JS文件以及内的内容进行多线程加载,使得加载速度更快 利于项目整体风格的调整,维护起来也更加便捷。单文件修改,全网站(应用)生效 浏览器会将CSS文件进行缓存,进一步地减少了加载所需时间 可以根据...

    Linux环境下的Fork使用

    这是我个人总结的关于多线程的一点心得体会,希望对大家有一定的帮助

    Tomcat内存溢出的三种情况及解决办法分析

    有一点需要注意:java -Xmx***M version 命令来测试的最大堆内存是 -Xmx与 -XX:PermSize的 和 比如系统支持最大的jvm堆大小事1.5G,那 -Xmx1024m -XX:PermSize=768M 是无法运行的。 第三种:无法创建新的线程。 ...

    UML基础、案例与应用(第三版)].施穆勒.扫描版_2分.pdf

    10.5 编号的一点注意事项 111 10.6 其他概念 112 10.6.1 发送给多对象的消息 112 10.6.2 返回结果 112 10.6.3 主动对象 113 10.6.4 同步 113 10.7 UML“大图” 114 10.8 小结 115 10.9 常见问题解答 115 10.10 小...

    android dialog与popwindow之间的简单运用

    PopupWindow顾名思义为弹出式菜单,不同于Dialag对话框,PopupWindow ...总结: PopupWindow必须在某个事件中显示或者是开启一个新线程去调用,不能直接在onCreate方法中显示一个Popupwindow,否则永远会有以上的错误。

    java是去蜗牛还是源码时代-JVM-:JVM-

    JVM-JVM调优总结 -Xms -Xmx -Xmn -Xss(转) 田间的蜗牛chris 关注 0.969 2018.05.30 15:29:02 字数 5,507 阅读 29,986 Xms 是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致...

Global site tag (gtag.js) - Google Analytics