`

线程的一点小总结

    博客分类:
  • 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多线程-多线程知识点总结和企业真题

    - 因为`wait()`方法需要当前线程持有对象的监视器锁才能调用,只有在`synchronized`代码块中才能确保这一点。 - **题4**:如何使用`ReentrantLock`的`Condition`对象实现线程间的通知和等待? 7. **单例模式...

    双线程JAVA小程序

    总结起来,"双线程JAVA小程序"涉及到Java多线程编程,包括创建线程、实现`Runnable`接口、在UI组件中显示文本、以及通过按钮控制线程的启停。理解这些概念对于开发任何具有并发特性的Java应用程序都是至关重要的。

    libevent多线程处理

    总结,Libevent在多线程环境中的应用是通过创建线程池,分配事件处理任务,并使用线程安全的数据结构和同步机制来实现的。理解这些核心概念和实践技巧,对于开发高效、可扩展的多线程网络服务至关重要。通过深入研究...

    MFC c++ vs2005 多线程 demo

    总结来说,"MFC c++ vs2005 多线程 demo"是一个展示如何在C++环境中利用MFC创建并管理多线程的应用程序,特别是如何在多线程中处理并发任务和同步问题。通过学习这个示例,开发者可以更好地理解和应用多线程技术,以...

    java线程学习总结.pdf

    - **屏障**:也称为栅栏,是一种同步工具,用于让一组线程等待,直到所有线程都到达某一点后再继续执行。 - **锁工具类**:Java并发包中的`java.util.concurrent.locks`提供了高级锁,如`ReentrantLock`、`...

    C#单线程与多线程实例

    1. **资源利用率**:多线程可以更好地利用多核处理器,提高CPU的使用率,而单线程则无法实现这一点。 2. **并发执行**:多线程可以在同一时间处理多个任务,而单线程需要等待一个任务完成后才能执行下一个。 3. **...

    java多线程编程总结.pdf

    volatile变量的使用是一种轻量级的同步机制,但是它并不保证操作的原子性,因此在使用时需要注意这一点。 接下来,谈到线程的创建和启动。在Java中,创建线程通常有两种方式,一种是通过继承Thread类并重写其run...

    C#的多线程示例;几个多线程之间的互斥,同步;WPF主界面INVOKE

    总结起来,C#的多线程机制允许我们创建并行执行的任务,通过线程互斥和同步保证数据一致性。而在WPF中,我们需谨慎处理UI更新,利用`Dispatcher`确保操作在正确的线程上执行。理解并熟练运用这些概念和技术,对于...

    .NET多线程编程教程,_NET多线程编程实例

    3.3 Barrier:屏障,使多个线程到达某一点后同时继续执行。 3.4 TaskCompletionSource:异步操作的线程间通信,通过设置Result属性完成任务。 四、线程状态与控制 4.1 线程状态:新建、可运行、运行、阻塞、等待、...

    python 线程的暂停, 恢复, 退出详解及实例

    ### Python线程的暂停、恢复与退出详解 在Python中,多线程是通过...对于需要即时响应的应用场景来说,这一点需要特别注意。此外,在实际开发中还应考虑到线程间的同步问题,避免因线程竞争而导致的数据不一致等问题。

    MFC用户界面多线程工程案例

    总结来说,“MFC用户界面多线程工程案例”是一个实践教程,它展示了如何在MFC应用中有效地使用多线程,以及如何处理与用户界面交互相关的并发问题。通过学习这个案例,开发者可以掌握创建和管理用户界面线程的关键...

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

    本文将总结在Qt环境下进行SQLite多线程操作时遇到的四个关键问题及其解决方案。 #### 1. 多线程下的各个线程或定时器数据库驱动加载需独立进行 在多线程环境中,不同线程间共享资源会导致各种难以预料的问题。对于...

    最清楚的进程线程,进程和线程对比

    线程基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但它可与同属一个进程的其他线程共享进程所拥有的全部资源。线程的存在使得操作系统可以同时调度同一进程中的多个...

    Linux2.6内核实现的是NPTL线程模型

    这一点可以从系统调用`getpid`和`gettid`的实现看出。`getpid`返回当前进程的线程组ID,而`gettid`返回当前线程的实际线程ID。 2. **Group Leader**: `group_leader` 字段用于指示线程组中的“领导者”线程,通常是...

    MFC多线程发送消息

    在Windows编程中,MFC(Microsoft Foundation Classes)库提供...总结,MFC多线程发送消息涉及线程创建、消息发送、接收以及线程同步等多个方面。通过理解和熟练运用这些概念,可以构建高效、稳定的多线程MFC应用程序。

    用MFC写的简单多线程下载例子

    总结,这个MFC多线程下载例子展示了如何结合MFC的线程机制、同步工具、文件操作和错误处理来实现高效的下载功能。通过理解和实践这样的例子,开发者能更好地掌握多线程编程技巧,从而优化Windows应用程序的性能。

Global site tag (gtag.js) - Google Analytics