Java高并发编程——多线程

线程简介

  • 进程
    • 正在运行的程序。
    • 进程是系统进行资源分配和调用的独立单位。每一个进程都有他自己的内存空间和系统资源。
  • 线程
    • 在同一个进程内又可以执行多个任务。而这每一个任务就可以看成是一个线程。
    • 线程是运行在进程中的一个独立实体,是CPU调度和分派的基本单位。
    • 单线程:程序只有一条执行路劲。
    • 多线程:程序有多条执行路劲。
      • 多个线程会共享进程所拥有的全部资源。
      • 多线程:为了提高应用程序的使用率。(程序的执行其实都是在抢CPU资源,CPU的执行权。多个进程在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。)通过多个线程并发执行,从而提高任务处理的速度。
    • 线程的开销
      • 关于时间:创建线程使用是直接向系统申请资源的,对操作系统来说,创建一个线程的代价是十分昂贵的, 需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU 的缓存被 清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。
      • 关于资源:Java线程的线程栈所占用的内存是在Java堆外的,所以是不受java程序控制的,只受系统资源限制,默认一个线程的线程栈大小是1M(当让这个可以通过设置-Xss属性设置,但是要注意栈溢出问题),但是,如果每个用户请求都新建线程的话,1024个用户光线程就占用了1个G的内存,如果系统比较大的话,一下子系统资源就不够用了,最后程序就崩溃了。
    • 并行和并发
      • 并行:逻辑上同时发生,指在某一个时间内同时运行多个程序。
      • 并发:物理上同时发生,指在某一个时间点同时运行多个程序。
    • Q:JVM虚拟机的启动是单线程还是多线程?
      • 多线程。
      • 原因是垃圾回收线程也要先启动,否则很容易出现内存溢出。
      • 垃圾回收线程+主线程,最低启动了两个线程,所以jvm的启动其实是多线程的。

多线程实现方式

  • 实现原理
    • 由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。 而进程是由系统创建的,所以我们应该去调用系统功能创建一个线程。
    • java是不能直接调用系统功能的,所以,我们没办法直接实现多线程程序。但是,Java可以调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由java去调用即可实现多线程程序。
  • 实现方式
    • 方式一:继承Thread类
    • 方式二:实现Runnable接口
    • 方式三:线程池Executors类
      • 创建固定数目线程的线程池
      • 创建一个可缓存的线程池(可大可小,随着任务量)
      • 定时及周期性的执行任务的线程池
        • scheduleAtFixedRate 这个方法是不管你有没有执行完,反正我每隔几秒来执行一次,以相同的频率执行
        • scheduleWithFixedDelay 这个是等你方法执行完后,我再隔几秒来执行,也就是相对延迟后,以固定的频率去执行
# 有返回值的线程。通过Callable和Future创建线程
public static void main(String[] args) throws ExecutionException {
    //Callable的返回值就要使用Future对象,Callable负责计算结果,Future负责拿到结果
    //1、实现Callable接口
    Callable<Integer> callable = new Callable<Integer>() {
        public Integer call() throws Exception {
            int i=999;
            //do something
            // eg request http server and process
            return i;
        }
    };
    //2、使用FutureTask启动线程
    FutureTask<Integer> future = new FutureTask<Integer>(callable);
    new Thread(future).start();
    //3、获取线程的结果
    try {
        Thread.sleep(5000);// 可能做一些事情
        System.out.println(future.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}
# 线程池
public static  void testFixedThreadPool() {
    //创建固定的线程池,使用3个线程来并发执行提交的任务。底层是个无界队列
    ExecutorService executorService = Executors.newFixedThreadPool(6);
    executorService.execute(new MyThread());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyThread());
    executorService.execute(new MyThread());
}

public static void testSingleThreadPool() {
    //创建单线程,在任务执行时,会依次执行任务。底层是个无界队列。
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.execute(new MyThread());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyThread());
}

public static void testCacheThreadPool() {
    //创建非固定数量,可缓存的线程池。当提交的任务数量起起伏伏时,会自动创建或者减少执行线程的数量。
    //当然,重用线程是线程池的基本特征。
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(new MyThread());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyRunnable());
    executorService.execute(new MyThread());
}

public static void testScheduledThreadPool(){
    //创建一个定时执行线程池
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(30);
    //1、配置任务的执行周期
    //scheduleAtFixedRate 固定周期执行完毕
    executorService.scheduleAtFixedRate(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);
    //scheduleWithFixedDelay 上一次执行完毕之后下一次开始执行
    executorService.scheduleWithFixedDelay(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);
}

public static void testSingleCacheThreadPool(){
    //创建一个单个线程执行的定时器
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    //scheduleAtFixedRate 固定周期执行完毕
    executorService.scheduleAtFixedRate(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);
    //scheduleWithFixedDelay 上一次执行完毕之后下一次开始执行
    executorService.scheduleWithFixedDelay(new MyRunnable(),0,1000,TimeUnit.MILLISECONDS);
}

public static void testMyThreadPool(){
    //自定义连接池稍微麻烦些,不过通过创建的ThreadPoolExecutor线程池对象,可以获取到当前线程池的尺寸、正在执行任务的线程数、工作队列等等。
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,100,10,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100));
    threadPoolExecutor.execute(new MyThread());
    threadPoolExecutor.execute(new MyRunnable());
    threadPoolExecutor.execute(new MyRunnable());
}
  • Q:多线程中run()和start()的区别?
    • run():仅仅是封装被线程执行的代码,直接调用是普通方法
    • start():首先启动了线程,然后再由JVM去调用该线程的run()方法
  • 线程调度
    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
    • Java使用的是抢占式调度模型。
  • 线程的生命周期
    • 新建:创建线程对象
    • 就绪:有执行资格,没有执行权
    • 执行:有执行资格,有执行权
      • 阻塞:由于一些操作让线程处于了该状态。没有执行资格,没有执行权。而另一些操作却可以把它激活,激活后处于就绪状态。
    • 死亡:线程对象变成垃圾,等待回收。
      线程的生命周期
  • 多线程安全问题
    • 原因:是否是多线程环境;是否有共享数据 ;是否有多条语句操作共享数据。
    • 解决方式:加锁
      • 把多条语句操作共享数据的代码包成一个整体,让某个线程在执行的时候,别人不能来执行。
      • 同步代码块:synchronized(对象){ code }
        • 对象:可以是任意对象
        • private Object obj = new Object();(成员变量,共用一把锁)
      • 同步方法:锁对象为this
      • JDK5之后提供lock锁
        • lock():获取锁 unlock():释放锁。
    • Q:如何把一个线程不安全的集合类变成一个线程安全的集合类?
      • Collections工具类的带synchronized的方法
    • Q:死锁问题
      • 同步的弊端:效率低;容易产生死锁
      • 死锁:两个或两个以上的线程在争夺资源过程中,发生的一种相互等待的现象。

猜你喜欢

转载自blog.csdn.net/pc_zkr/article/details/83074033