"多线程和线程池"-之通俗易懂的介绍+图文,代码解析

一.多线程的重点部分

1.两种创建方式

1.继承Threa类
2.实现Runnable接口
最后都通过重写run方法实现线程

2.wait和sleep区别

1.wait释放锁,sleep不释放
2.wait用于线程间交互,sleep用于暂停
3.wait等待CPU,sleep攒着CPU睡觉

3.synchronized和volatile关键字作用

volatile :易变,不稳定之意,一个成员变量被修饰后:
1.保证了不同线程对这个变量操作时的可见性:一个线程修改了某变量值,该值对其他线程立即可见.
2.禁止进行指令重排序
    volatile告诉jvm,当前变量在寄存器(工作内存)中值是不确定的,需要从主存中读取
    synchronized锁定当前变量,只有当前线程可访问,其他的阻塞.
    1.volatile只能在变量级别
        synchronized可在变量,方法,类级别
    2.volatile紧能实现变量的修改可见性,不能保证原子性
        synchronized能实现变量的修改可见性并能保证原子性
    3.volatile不会造成线程的阻塞
        synchronized可能会造成线程的阻塞。
    4.volatile 标记的变量不会被编译器优化;
        synchronized 标记的变量可以被编译器优化;

4.线程并发访问代码

public class Demo3 {
    public static void main(String[] args) {
        final Counter counter = new Counter();
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    counter.inc();
                }
            }).start();
        }
        System.out.println(counter);
    }
}

class Counter {
    private volatitle int count = 0;

    public void inc() {
        try {
            Thread.sleep(3);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //增加
        count++;
    }

    @Override
    public String toString() {
        return "[count = " + count + "]";
    }
}

问,是否打印1000?

不是,结果不可能等于1000,肯定是小于1000的,线程安全问题,所以小于,不管有无都小于.

线程池的重点部分

5.线程池是什么?怎么使用?
1.”事先”将”多个线程”放入一个容器中;
2.使用的时候就不用new线程而是从”池”中拿;
3.节省了开辟线程的时间,提高代码的执行效率;

使用方法
Executors的工厂方法提供的5种不同的线程池,都是.newXXX,自动回收的,固定大小的,调度的,单例的.    
(java.util.concurrent.Executors-同时进行.执行):
    1.ExecutorService newCachedThreadPool  = Executors.newCachedThreadPool(); 
    2.ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
    3.ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4); 
    4.ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
    然后再调用他们的execute()方法即可;

创建举例:
固定线程数量和单个:

public class Demo3 {
    public static void main(String[] args) {
        //预先创建6个线程
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        MyThread t4 = new MyThread();
        MyThread t5 = new MyThread();
        MyThread t6 = new MyThread();
        //创建一个线程池,即容器
        ExecutorService executorService = Executors.newFixedThreadPool(4);//设定同时运行的个数
        //单例线程,任意时间池中只能有一个线程
        //一个池最多只能同时执行一个
        ExecutorService executor = Executors.newSingleThreadExecutor();
        //把线程"们"放入池中,并执行
        executorService.execute(t1);
        executorService.execute(t2);
        executorService.execute(t3);
        executorService.execute(t4);
        executorService.execute(t5);
        executorService.execute(t6);
        //关闭池
        executorService.shutdown();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i < 101; i++) {
            System.out.println(Thread.currentThread().getName() + " 正在执行...." + i);
        }
    }
}

输出结果(4个线程):

pool-1-thread-2 正在执行....4
pool-1-thread-4 正在执行....1
pool-1-thread-1 正在执行....11
pool-1-thread-4 正在执行....2
pool-1-thread-2 正在执行....5
pool-1-thread-3 正在执行....2

一次只能执行一次线程(1个线程):

pool-1-thread-1 正在执行....5
pool-1-thread-1 正在执行....6
pool-1-thread-1 正在执行....7
pool-1-thread-1 正在执行....8
pool-1-thread-1 正在执行....9
pool-1-thread-1 正在执行....10

>

6.对线程池的理解

三个好处:
1.降低能源消耗,通过反复利用已经创建线程,减少线程的创建和销毁造成的消耗
2.提高响应速度,任务一到达不需要等待线程创建就能立即执行
2.提高了线程的可管理性,线程是奇缺资源,如果不停的创建,还会消耗系统资源,对系统的稳定性造成影响.

线程池

1."线程池"就是一个容器,存放"Thread"or"Runable",

2.若每次有任务都"new Thread()"开启一个线程,那么就会"大量的消耗CPU"的资源,导致"Android"运行变慢,甚至"OOM(out of memory)" ,因而"java"就出现了一个"ThreadPoolExecutor"来管理这些线程。
    1.控制最多的线程数"maximumPoolSize" 
    2.核心线程数"corePoolSize",来管理我们需要开启的线程数。

目的:减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务

所以:我们就可以根据"手机的CPU"核数来控制App可以开的最大线程数。保证程序的合理运行

创建线程池对象

几个参数的完全理解

1.比如去火车站买票, 有10个售票窗口, 但目前只有5个窗口开放(日常情况). 那么对外开放的5个窗口称为核心线程数"corePoolSize",

2.而最大线程数"maximumPoolSize"是10个窗口.

3.如果5个窗口都被占用, 那么后来的人就必须在后面排队, 但后来售票厅人越来越多, 已经人满为患, 就类似于线程队列new  "LinkedBlockingQueue<Runnable>()"已满.

4.这时候火车站站长下令, 把剩下的5个窗口也打开, 也就是目前已经有10个窗口同时运行(春运). 后来又来了一批人,
5.10个窗口也处理不过来了, 而且售票厅人已经满了, 这时候站长就下令封锁入口,不允许其他人再进来, 这就是线程异常处理策略.
6.而线程存活时间"keepAliveTime"指的是, 允许售票员休息的最长时间, 以此限制售票员偷懒的行时间。休息一下在处理。

线程池实际:来5个人,正好,来第六个,如果等了500毫秒还没人出来,那么开第6个窗口,然后再78910个人就开10个窗口,多过10个人,就按照自定义策略处理,当少<10个人,那么窗口会对比空闲时间,超过就关闭.

7.线程池启动策略

这里写图片描述

图解
这里写图片描述

官方建议用Executor的工厂方法创建,以前最常用的方法是ThreadPoolExecutor,线程池执行

案例1:

    public class ThreadPool {
        public static void main(String[] args) {
            //线程池
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    3   //corePoolSize:线程池维护线程的最少数量
                    , 5 //maximumPoolSize:最大限度线程数
                    , 3 //keepAliveTime:线程池维护线程的空闲时间
                    , TimeUnit.SECONDS //unit:对应单位是什么
                    , new ArrayBlockingQueue<Runnable>(5)//workQueue,缓存队列数
                    , new ThreadPoolExecutor.DiscardOldestPolicy()//handler:拒绝任务的处理策略
            );

            for (int i = 1; i <= 10; i++)
            {
                try
                {
                    // 产生一个任务,并将其加入到线程池
                    String task = "task@ " + i;
                    System.out.println("put " + task);
                    threadPool.execute(new ThreadPoolTask(task));

                    // 便于观察,等待一段时间,慢点加
                    Thread.sleep(10);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
            //关闭
            threadPool.shutdown();
        }
    }

    /**
     * 参数:
     * 1.任务通过executor(Runnable)方法添加到线程池
     * 2.任务就是一个runable对象
     * 3.认为的执行方法就是run();
     *
     *优先级:
     * 1.核心线程corePoolSize
     * 2.任务队队列workQueue
     * 3.最大限度线程
     * 如果都满了,hanlder策略处理
     *
     * 线程数量大于最小数量,如果空闲时间超过了keepAliveTime,那么线程将会被终止.即线程池动态的调整池中线程数量.
     */

    class ThreadPoolTask implements Runnable
    {
        // 保存任务所需要的数据
        private Object threadPoolTaskData;

        ThreadPoolTask(Object tasks)
        {
            this.threadPoolTaskData = tasks;
        }

        public void run()
        {
            // 处理一个任务,仅是一个打印
            System.out.println(Thread.currentThread().getName()+"...start ..." + threadPoolTaskData);

            try
            {
               //便于观察,等待一段时间
                Thread.sleep(1000);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }   

打印结果:

put task@ 1
pool-1-thread-1...start ...task@ 1
put task@ 2
pool-1-thread-2...start ...task@ 2
put task@ 3
pool-1-thread-3...start ...task@ 3
put task@ 4
put task@ 5
put task@ 6
put task@ 7
put task@ 8
put task@ 9
pool-1-thread-4...start ...task@ 9
put task@ 10
pool-1-thread-5...start ...task@ 10
pool-1-thread-1...start ...task@ 4
pool-1-thread-2...start ...task@ 5

案例2:

    /**
     * 需求:
     * 1.100个生产任务
     * 2.核心线程数是3
     * 3.最大线程数是5
     * 4.2秒进行线程的回收
     * 5.队列最多有6个在排队
     *
     * 1.当排队满的时候,给出提示,正在使用策略
     * 2.打印过程数据
     */
    public class ThreadPoolExecutorTest
    {

        private ThreadPoolExecutor mTpe;

        public void createThreadPool() {
            /*
             * 创建线程池,最小线程数为3,最大线程数为5,线程池维护线程的空闲时间为3秒,
             * 使用队列深度为6的有界队列,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,
             */
            /**
             * AbortPolicy  队列满"抛出异常"
             * CallerRunsPolicy     "重试添加"当前任务,会自动调用execute()方法
             * DiscardOldestPolicy  抛弃"旧"任务
             * DidscardPolicy       抛弃"当前任务"
             *
             */
            ThreadPoolExecutor.CallerRunsPolicy HANDLER = new ThreadPoolExecutor.CallerRunsPolicy();
            mTpe = new ThreadPoolExecutor(3, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(6),HANDLER);

            // 向线程池中添加 100 个任务
            for (int i = 0; i < 100; i++) {
                mTpe.execute(new TaskThreadPool(i));
            }
            // 关闭线程池
            mTpe.shutdown();
        }


        public static void main(String[] args)
        {
            ThreadPoolExecutorTest test = new ThreadPoolExecutorTest();
            test.createThreadPool();
        }

        class TaskThreadPool implements Runnable
        {
            private int index;

            public TaskThreadPool(int index)
            {
                this.index = index;
            }

            public void run() {
                System.out.println("线程池中任务数是" + mTpe.getQueue().size());
                System.out.println(Thread.currentThread().getName() + " 生产中... :" + index);
                try {
                    //模拟生产耗时,一个工序要3秒
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 完成了 :" + index);
            }
        }
    }

打印结果

线程池中任务数是6
线程池中任务数是6
线程池中任务数是6
线程池中任务数是6
main 生产中... :11
线程池中任务数是6
pool-1-thread-1 生产中... :0
pool-1-thread-4 生产中... :9
线程池中任务数是6
pool-1-thread-3 生产中... :2
pool-1-thread-2 生产中... :1
pool-1-thread-5 生产中... :10
pool-1-thread-1 完成了 :0
pool-1-thread-4 完成了 :9
main 完成了 :11
线程池中任务数是4
pool-1-thread-4 生产中... :4
线程池中任务数是5
pool-1-thread-1 生产中... :3
线程池中任务数是6
main 生产中... :14
pool-1-thread-2 完成了 :1
pool-1-thread-5 完成了 :10
pool-1-thread-3 完成了 :2
线程池中任务数是3
pool-1-thread-3 生产中... :7

8.怎么控制某个方法允许并发访问线程个数

采用Semaphore,信号的意思

1.创建对象,构造传入并发线程数量   
    static Semaphore semaphore = new Semaphore(5, true);
2.申请请求
    semaphore.acquire();
3.释放
    semaphore.release();

例子:

    class Demo3 {
        //1.创建一个对象
        static Semaphore semaphore = new Semaphore(5, true);

        public static void main(String[] args) {
            //并发100个线程
            for (int i = 0; i < 100; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test();
                    }
                }).start();
            }
        }
        public static void test(){
            try {
                //2.申请一个请求 ,相当于收尾包住
                semaphore.acquire();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"进来了");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"走了");
            //3.释放一个请求
            semaphore.release();
        }
    }

打印结果
添加后,控制了,进来了同时最多5个:

Thread-1进来了
Thread-56进来了
Thread-2进来了
Thread-3进来了
Thread-4进来了
Thread-1走了
Thread-5进来了

不添加时:

Thread-0进来了
Thread-1进来了
Thread-2进来了
Thread-3进来了
Thread-4进来了
Thread-5进来了
Thread-6进来了
Thread-7进来了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值