zoukankan      html  css  js  c++  java
  • 并发--基本的线程机制

    1. 基本概念

    线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System VSunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

    线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

    同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符信号处理等等。但同一进程中的多个线程有各自的调用栈call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

    一个进程可以有很多线程,每条线程并行执行不同的任务。

    在多核或多 CPU,或支持Hyper-threadingCPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。(摘自百度百科)

    线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

    每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。

    Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:

    调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。

    非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。

    2 定义任务

    线程可以驱动任务,因此需要一种描述任务的方式。创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。

    2.1 重写Thread类的的run方法

    public class Thread extends Object implements Runnable

    查看 API文档可知:其实 Thread 也是实现 Runable 接口的类

     1 package thread;
     2 
     3 public class liftOff implements Runnable {
     4     protected int countDown=10;
     5     private static int taskCount=0;
     6     private final int id=taskCount++;
     7     public liftOff() {}
     8     public liftOff(int countDown) {
     9         this.countDown=countDown;
    10     }
    11     public String status() {
    12         return "#"+id+"("+(countDown>0?countDown:"liftOff")+"),";
    13     }
    14     @Override
    15     public void run() {
    16         // TODO Auto-generated method stub
    17         while(countDown-->0) {
    18             System.out.print(status());
    19             Thread.yield();
    20         }
    21     }
    22 
    23 }
    package thread;
    
    public class basicThread {
        public static void main(String[] args){
            Thread t=new Thread(new liftOff());
            t.start();
            System.out.println("Waiting for liftOFF");
        }
    }//Output
    /*#0(9),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(liftOff),#1(9),#1(8),#1(7),#1(6),#1(5),#1(4),#1(3),#1(2),#1(1),#1(liftOff),#2(9),#2(8),#2(7),#2(6),#2(5),#2(4),#2(3),#2(2),#2(1),#2(liftOff),#3(9),#3(8),#3(7),#3(6),#3(5),#3(4),#3(3),#3(2),#3(1),#3(liftOff),#4(9),#4(8),#4(7),#4(6),#4(5),#4(4),#4(3),#4(2),#4(1),#4(liftOff),
    
    */
    View Code

    2.2 声明实现 Runnable 接口

     1 package thread;
     2 
     3 public class mainThread {
     4     public static void main(String[] args) {
     5         liftOff launch=new liftOff();
     6         launch.run();
     7     }
     8 }
     9 /* Output   #0(9),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(liftOff),
    10 */

    从运行结果可以看出,并发是每个线程是互不影响的。

    异步时,new Thread的弊端如下:

    a. 每new Thread新建对象性能差。
    b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom
    c. 缺乏更多功能,如定时执行、定期执行、线程中断。
    相比new ThreadJava提供的四种线程池的好处在于:
    a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
    b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
    c. 提供定时执行、定期执行、单线程、并发数控制等功能。

    2.3 使用 Executor接口

     执行已提交的 Runnable 任务的对象。此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。通常使用 Executor 而不是显式地创建线程。

    不过,Executor 接口并没有严格地要求执行是异步的。在最简单的情况下,执行程序可以在调用者的线程中立即运行已提交的任务: 更常见的是,任务是在某个不是调用者线程的线程中执行的。以下执行程序将为每个任务生成一个新线程。

    许多 Executor 实现都对调度任务的方式和时间强加了某种限制。以下执行程序使任务提交与第二个执行程序保持连续,这说明了一个复合执行程序。

    此包中提供的 Executor 实现实现了 ExecutorService,这是一个使用更广泛的接口。ThreadPoolExecutor 类提供一个可扩展的线程池实现。Executors 类为这些 Executor 提供了便捷的工厂方法。

    内存一致性效果:线程中将 Runnable 对象提交到 Executor 之前的操作 happen-before 其执行开始(可能在另一个线程中)。

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

     

     1)使用CachedThreadPool

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

     1 package thread;
     2 
     3 import java.util.concurrent.ExecutorService;
     4 import java.util.concurrent.Executors;
     5 
     6 public class cachedThreadPool {
     7     public static void main(String[] args) {
     8         ExecutorService exec = Executors.newCachedThreadPool();
     9         for (int i = 0; i < 5; i++) {
    10             exec.execute(new liftOff());
    11         }
    12         exec.shutdown();
    13 
    14     }
    15 }/*Output #1(9),#0(9),#1(8),#0(8),#3(9),#4(9),#2(9),#0(7),#2(8),
    #3(8),#0(6),#1(7),#0(5),#3(7),#0(4),#2(7),#4(8),#1(6),#3(6),#2(6),#4(7),#1(5),#3(5),
    #1(4),#2(5),#0(3),#4(6),#3(4),#1(3),#2(4),#0(2),#4(5),#1(2),#2(3),#1(1),#3(3),#2(2),#4(4),
    #0(1),#1(liftOff),#2(1),#0(liftOff),#3(2),#2(liftOff),#4(3),#3(1),#4(2),#3(liftOff),#4(1),#4(liftOff),
    16 */

     

      2)使用FixedThreadPool

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

     1 package thread;
     2 
     3 
     4 
     5 import java.util.concurrent.ExecutorService;
     6 import java.util.concurrent.Executors;
     7 
     8 public class fixedThreadPool {
     9     public static void main(String[] args) {
    10         ExecutorService exec = Executors.newFixedThreadPool(5);
    11         for (int i = 0; i < 5; i++) {
    12             exec.execute(new liftOff());
    13         }
    14         exec.shutdown();
    15 
    16     }
    17 }
    18 /*Output #1(9),#0(9),#1(8),#0(8),#3(9),#4(9),
    #2(9),#0(7),#2(8),#3(8),#0(6),#1(7),#0(5),#3(7),#0(4),#2(7),
    #4(8),#1(6),#3(6),#2(6),#4(7),#1(5),#3(5),#1(4),#2(5),#0(3),#4(6),
    #3(4),#1(3),#2(4),#0(2),#4(5),#1(2),#2(3),#1(1),#3(3),#2(2),#4(4),#0(1),#1(liftOff),
    #2(1),#0(liftOff),#3(2),#2(liftOff),#4(3),#3(1),#4(2),#3(liftOff),#4(1),#4(liftOff),
    19 20 * */

      3)使用SingleThreadPool

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

    
    

    package thread;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;

    
    

    public class singleThreadPool {
    public static void main(String[] args) {
    ExecutorService exec = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 5; i++) {
    exec.execute(new liftOff());
    }
    exec.shutdown();

    
    

    }
    }/*#0(9),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(liftOff),
    #1(9),#1(8),#1(7),#1(6),#1(5),#1(4),#1(3),#1(2),#1(1),#1(liftOff),
    #2(9),#2(8),#2(7),#2(6),#2(5),#2(4),#2(3),#2(2),#2(1),#2(liftOff),
    #3(9),#3(8),#3(7),#3(6),#3(5),#3(4),#3(3),#3(2),#3(1),#3(liftOff),
    #4(9),#4(8),#4(7),#4(6),#4(5),#4(4),#4(3),#4(2),#4(1),#4(liftOff),
    */

     

      

  • 相关阅读:
    多线程环境下调用 HttpWebRequest 并发连接限制
    i—比 i++ 快?
    文件在不同文件系统间拷贝文件时间改变的问题
    Go websocket 聊天室demo2
    Go websocket 聊天室demo以及k8s 部署
    AcWing 1077. 皇宫看守
    AcWing 1073. 树的中心
    AcWing 1085. 不要62
    AcWing 1081 度的数量
    AcWing 1082. 数字游戏
  • 原文地址:https://www.cnblogs.com/tianliang94/p/10633797.html
Copyright © 2011-2022 走看看