美文网首页
多线程学习

多线程学习

作者: 盼旺 | 来源:发表于2019-08-28 00:01 被阅读0次

线程的基本介绍

1.什么是进程
进程是指在系统中正在运行的一个应用程序
每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内


2.什么是线程
1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)
线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行
比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行

3.线程的串行
1个线程中任务的执行是串行的
如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
也就是说,在同一时间内,1个线程只能执行1个任务
比如在1个线程中下载3个文件(分别是文件A、文件B、文件C)

4.线程的生命周期
1.新建
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,
此时仅由 JVM 为其分配 内存,并初始化其成员变量的值.

2.就绪
当线程对象调用了 start()方法之后,该线程处于就绪状态。
Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。

3.运行
如果处于就绪状态的线程被调度获得CPU执行权,就会执行run()方法的逻辑,此时处于运行状态。

4.阻塞
阻塞状态是指线程因为某种原因放弃CPU使用权,暂时停止运行.
需要等到线程进入就绪状态才有机会获得cpu时间片从而执行.
这个状态分下面三种情况
1.等待阻塞(obj.wait()->进入wait):即运行中的线程执行wait方法,JVM会将该线程放入等待队列中
2.同步阻塞(lock.lock()/synchronized->锁池):即运行中的线程获取对象的
同步锁(指jvm提供的内置锁synchronized)或者显示锁lock失败,会将线程阻塞挂起
3.其他方式阻塞(sleep/join):运行中的线程执行Thread.sleep()或者thread.join()方法,
或者发出I/O请求待处理的时候,jvm会将线程置为阻塞状态。
当sleep()状态超时、join()等待线程运行结束或者超时、或者处理I/O完毕,会重新进入就绪状态

5.死亡
线程结束任务之后自己结束,或者产生了异常而结束。

5.创建线程
线程的创建一共有四种方式:
1.继承于Thread类,重写run()方法。
2.实现Runable接口,实现里面的run()方法。
3.使用 Future Task 实现有返回结果的线程。
4.使用线程池。

//继承于Thread类,重写run()方法
class MyThread extends Thread{
    //重写run方法
    @Override
    public void run() {
        //任务内容....
        System.out.println("当前线程是:"+Thread.currentThread().getName());
    }
}
Thread thread = new MyThread();
//线程启动
thread.start();
//当前线程是:Thread-0
使用匿名内部类
Thread thread = new Thread(){
        @Override
        public void run() {
            System.out.println("当前线程是:"+Thread.currentThread().getName());
        }
    };

因为java是单继承结构,一旦继承了Thread类,就无法继承其他类了。所以建议使用 实现Runable接口 的方法

//实现Runable接口,实现里面的run()方法:
class MyTask implements Runnable{
    //重写run方法
    public void run() {
        //任务内容....
        System.out.println("当前线程是:"+Thread.currentThread().getName());
    }
}
Thread thread = new Thread(new MyTask());
//线程启动
thread.start();
使用匿名内部类
Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("当前线程是:"+Thread.currentThread().getName());
        }
    });

FutureTask是一个可取消的异步计算任务,是一个独立的类,实现了 FutureRunnable接口。FutureTask的出现是为了弥补 Thread的不足而设计的,可以让程序员跟踪、获取任务的执行情况、计算结果 。
因为FutureTask实现了 Runnable,所以FutureTask可以作为参数来创建一个新的线程来执行,也可以提交给 Executor 执行。FutureTask一旦计算完成,就不能再重新开始或取消计算。

FutureTask的构造方法
可以接受 Runnable,Callable 的子类实例。
//创建一个 FutureTask,一旦运行就执行给定的 Callable。
public FutureTask(Callable<V> callable);
//创建一个 FutureTask,一旦运行就执行给定的 Runnable,并安排成功完成时 get 返回给定的结果 。
public FutureTask(Runnable runnable, V result)
//FutureTask 的简单例子
MyCallable.java
public class MyCallable implements Callable<Double>{

    @Override
    public Double call() throws Exception {
        double d = 0;
        try {
            System.out.println("异步计算开始.......");
             d = Math.random()*10;
            d += 1000;
           Thread.sleep(2000);
            System.out.println("异步计算结束.......");
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       return d;
    }
}
Test.java
public class Test {
    public static void main(String[] args) {
        FutureTask<Double> task = new FutureTask<>(new MyCallable());
        //创建一个线程,异步计算结果
        Thread thread = new Thread(task);
        thread.start();
        //主线程继续工作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程等待计算结果...");
        //当需要用到异步计算的结果时,阻塞获取这个结果
        Double d;
        try {
            d = task.get();
            System.out.println("计算结果是:"+d);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        
        //用同一个 FutureTask 再起一个线程
        Thread thread2 = new Thread(task);
        thread2.start();
    }
}

第四种先简单给个例子,具体看下面
JDK中提供了工具类Executors,提供了几个创建常用的线程池的工厂方法
package wgzyx;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyRunable implements Runnable{
     private String taskName;
        
        public MyRunable(String taskName) {
            this.taskName = taskName;
        }
    @Override
    public void run() {
        System.out.println("线程池完成任务:"+taskName);
    }
    
    public static void main(String[] args) {
         //创建一个只有一个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //创建任务,并提交任务到线程池中
        executorService.execute(new MyRunable("任务1"));
        executorService.execute(new MyRunable("任务2"));
        executorService.execute(new MyRunable("任务3"));
    }
}
/*
线程池完成任务:任务1
线程池完成任务:任务2
线程池完成任务:任务3
*/

多线程

1.什么是多线程
1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
多线程技术可以提高程序的执行效率
比如同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C)


2.多线程的原理
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
如果线程非常非常多
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低线程的执行效率降低

3.多线程的优缺点

  • 线程的优点
    能适当提高程序的执行效率
    能适当提高资源利用率(CPU、内存利用率)
  • 多线程的缺点
    开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    线程越多,CPU在调度线程上的开销就越大
    程序设计更加复杂:比如线程之间的通信、多线程的数据共享

4. 何时建议使用多线程
  ①. 当主线程试图执行冗长的操作,但系统会卡界面,体验非常不好,这时候可以开辟一个新线程,来处理这项冗长的工作。
  ②. 当请求别的数据库服务器、业务服务器等,可以开辟一个新线程,让主线程继续干别的事。
  ③. 利用多线程拆分复杂运算,提高计算速度

线程池

1.为什么使用线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
2.线程池的作用
使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务。
3.线程池的好处
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

创建线程池

阿里的 Java开发手册,上面有线程池的一个建议:


主要是底层的阻塞队列LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
通过创建 ThreadPoolExecutor 对象来创建
1.ThreadPoolExecutor参数介绍
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

1.corePoolSize:表示核心线程池的大小。当提交一个任务时,如果当前核心线程池的线程个数没有达到corePoolSize,则会创建新的线程来执行所提交的任务,即使当前核心线程池有空闲的线程。如果当前核心线程池的线程个数已经达到了corePoolSize,则不再重新创建线程。如果调用了prestartCoreThread()或者 prestartAllCoreThreads(),线程池创建的时候所有的核心线程都会被创建并且启动。*
2.maximumPoolSize:表示线程池能创建线程的最大个数。如果当阻塞队列已满时,并且当前线程池线程个数没有超过maximumPoolSize的话,就会创建新的线程来执行任务。
3.keepAliveTime:空闲线程存活时间。如果当前线程池的线程个数已经超过了corePoolSize,并且线程空闲时间超过了keepAliveTime的话,就会将这些空闲线程销毁,这样可以尽可能降低系统资源消耗。
4.unit:时间单位。为keepAliveTime指定时间单位。

  1. workQueue:阻塞队列。用于保存任务的阻塞队列,可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue
  2. threadFactory:创建线程的工程类。可以通过指定线程工厂为每个创建出来的线程设置更有意义的名字,如果出现并发问题,也方便查找问题原因。

7.handler:饱和策略。当线程池的阻塞队列已满和指定的线程都已经开启,说明当前线程池已经处于饱和状态了,那么就需要采用一种策略来处理这种情况。采用的策略有这几种:

    1.  AbortPolicy: 直接拒绝所提交的任务,并抛出RejectedExecutionException异常;
    2.  CallerRunsPolicy:只用调用者所在的线程来执行任务;
    3.  DiscardPolicy:不处理直接丢弃掉任务;
    4.  DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务,执行当前任务.

适当的阻塞队列
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek()
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue: 一个不存储元素的阻塞队列。
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。

线程池执行大致过程=》execute方法的执行过程
2.ThreadPoolExecutor例子
package wgzyx;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
    //newFixedThreadPool创建固定大小的线程池。
    //每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
    // 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
    //线程池接口是ExecutorService
    public static ExecutorService newFixedThreadPool(int nThreads){
        return new ThreadPoolExecutor(
                nThreads,// corePoolSize
                nThreads,// maximumPoolSize == corePoolSize
                0L,// 空闲时间限制是 0
                TimeUnit.MILLISECONDS,
                //TimeUnit.DAYS天 TimeUnit.HOURS小时 TimeUnit.MINUTES分钟 TimeUnit.SECONDS秒 TimeUnit.MILLISECONDS毫秒
                new LinkedBlockingQueue<Runnable>(nThreads)//一个由链表结构组成的有界阻塞队列
                );
    }
    // newCachedThreadPool创建一个可缓存的线程池。
    //如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,
    //当任务数增加时,此线程池又可以智能的添加新线程来处理任务。
    //此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
    public static ExecutorService newCachedThreadPool(){
        return new ThreadPoolExecutor(
            0,                  // corePoolSoze == 0
            Integer.MAX_VALUE,  // maximumPoolSize 非常大
            60L,                // 空闲判定是60 秒
            TimeUnit.SECONDS,//分钟
            // 神奇的无存储空间阻塞队列,每个 put 必须要等待一个 take
            new SynchronousQueue<Runnable>()  
        );
    }
    //newSingleThreadExecutor创建一个单线程的线程池。
    //这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。
    //如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
    //实际上FinalizableDelegatedExecutorService这个类就是对ExecutorService进行了一个包装,防止暴露出不该被暴露的方法
    //,然后加上了finalize方法保证线程池的关闭
     public static ExecutorService newSingleThreadExecutor() {
            return 
                new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>(1));
    }
    public static void main(String[] args) {
        System.out.println("使用固定大小的线程池");
        ExecutorService newCachedThreadPool = newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            newCachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "----" + index);
                }
            });
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("使用可缓存的线程池");
        newCachedThreadPool = newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            newCachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "----" + index);
                }
            });
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("使用单线程的线程池");
        newCachedThreadPool = newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            newCachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "----" + index);
                }
            });
        }
    }
}

文章参考
http://www.cnblogs.com/yxt9322yxt/p/4804026.html
https://www.cnblogs.com/jinggod/p/8485106.html

相关文章

网友评论

      本文标题:多线程学习

      本文链接:https://www.haomeiwen.com/subject/ohmeectx.html