java多线程实现方式和线程池详解

java多线程实现方式和线程池详解

    • 多线程的三种实现方式
      • 1、继承Thread类
      • 2、实现Runnable接口
      • 3、实现Callable接口
    • 线程安全问题
      • 1、卖车票案例
      • 2、解决线程安全
        • 同步代码块
        • 同步方法
      • 3、同步锁(Lock锁)
    • 线程池的使用
      • 1、Excutors创建多线程
      • 2、ExecutorService创建多线程(常用子类ThreadPoolExecutor)
      • 3、多线程中workQueue任务队列集中使用类型
      • 4、拒绝策略
      • 5、ThreadFactory自定义线程创建
      • 6、ThreadPoolExecutor扩展
      • 7、线程池线程数量

多线程的三种实现方式

1、继承Thread类

public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "主线程");
        new Thread("thread实现多线程") {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "子线程");
            }
        }.start();
      }

2、实现Runnable接口

public static void main(String[] args) {
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                           System.out.println(Thread.currentThread().getName() + "2");
                    }
                }, "runnable实现多线程").start();
      }

3、实现Callable接口

与使用Runnable相比, Callable功能更强大些

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果
public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "主线程");
        Callable<Integer> callable = new Callable<Integer>() {
            private Integer sum=0;
            @Override
            public Integer call() throws Exception {
                for(int i = 0;i<=100;i++){
                    if(i % 2 == 0){
                        sum += i;
                        Thread.sleep(100);
                    }
                }
                return sum;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            //get返回值即为FutureTask构造器参数callable实现类重写的call的返回值
            //get方法是阻塞的,直到拿到线程的执行结果
            Integer sum = futureTask.get();
            System.out.println(Thread.currentThread().getName()+":"+sum);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

线程安全问题

1、卖车票案例

下面代码案例中,会出现多个线程卖同一张票问题,也会出现卖票出现负数问题,这就是使用多线程时,发生了线程安全问题。

    public static void main(String[] args) {
        /**
         * 创建一个Runnable共享ticket
         */
        Runnable runnable = new Runnable() {
            private int ticket = 100;

            @Override
            public void run() {
                while (true) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(10);
                            System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket-- + "张票");
                            ticket--;
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };

        Thread thread1 = new Thread(runnable, "线程1");
        Thread thread2 = new Thread(runnable, "线程2");
        Thread thread3 = new Thread(runnable, "线程3");
        thread1.start();
        thread2.start();
        thread3.start();
    }

2、解决线程安全

同步代码块

其中,obj 称为同步监视器,也就是锁,原理是:当线程开始执行同步代码块前,必须先获得对同步代码块的锁定。并且任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。
其中的锁,在非静态方法中可为this,在静态方法中为当前类(类名.class)。

synchronized(obj){
    //需要被同步的代码块
 }
//已经可以正常卖票了
public static void main(String[] args) {
        /**
         * 创建一个Runnable共享ticket
         */
        Runnable runnable = new Runnable() {
             int ticket = 100;
            @Override
            public void run() {
                while (true) {
                    synchronized (test1.class) {
                        if (ticket > 0) {
                            try {
                                Thread.sleep(10);
                                System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票");
                                ticket--;
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }

            }
        };

        Thread thread1 = new Thread(runnable, "线程1");
        Thread thread2 = new Thread(runnable, "线程2");
        Thread thread3 = new Thread(runnable, "线程3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
同步方法

对于关键字synchronized修饰的方法,不需要再指定同步监视器,这个同步方法(非static方法)无需显式地指定同步监视器,同步方法的同步监视器就是this,也就是调用该方法的对象。

public synchronized void testThread()
{
    //需要被同步的代码块
}
public class test1 {
    public static void main(String[] args) {
        CustRunable custRunable = new CustRunable();
        Thread thread1 = new Thread(custRunable, "线程1");
        Thread thread2 = new Thread(custRunable, "线程2");
        Thread thread3 = new Thread(custRunable, "线程3");
        thread1.start();
        thread2.start();
        thread3.start();
    }

}

class CustRunable implements Runnable {
    public static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            payTicket();
        }
    }

    public synchronized void payTicket(){
        if (ticket > 0) {
            try {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票");
                ticket--;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

注意,synchronized可以修饰方法,修饰代码块,但是不能修饰构造器、成员变量等。

3、同步锁(Lock锁)

public class test1 {
    public static void main(String[] args) {
        CustRunable custRunable = new CustRunable();
        Thread thread1 = new Thread(custRunable, "线程1");
        Thread thread2 = new Thread(custRunable, "线程2");
        Thread thread3 = new Thread(custRunable, "线程3");
        thread1.start();
        thread2.start();
        thread3.start();
    }


}

class CustRunable implements Runnable {
    private ReentrantLock lock = new ReentrantLock();
    public static int ticket = 100;

    @Override
    public void run() {
        lock.lock();
        try {
            while (true) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(20);
                        System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票");
                        ticket--;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

线程池的使用

背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。
优势:提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)便于线程管理。

1、Excutors创建多线程

常用API

  • void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable。
  • Future submit(Callable task):执行任务,有返回值,一般又来执行callable。
  • void shutdown():关闭连接池。
    java多线程实现方式和线程池详解_第1张图片
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                for(int i = 0;i<=100;i++){
                    if (i % 2 ==0 )
                        System.out.println("2"+Thread.currentThread().getName()+":"+i);
                }
            }
        });
        Future<Integer> submit = executorService.submit(new Callable<Integer>() {
            private Integer sum = 0;

            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + ":张刚");
                for (int i = 0; i <= 100; i++) {
                    if (i % 2 == 0) {
                        sum += i;
                        Thread.sleep(50);
                    }
                }
                return sum;
            }
        });
        System.out.println(submit.get());
        executorService.shutdown();
    }

2、ExecutorService创建多线程(常用子类ThreadPoolExecutor)

在这里插入图片描述通过源码可以看到,ThreadPoolExecutor有四个构造方法,解读下构造方法参数。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

参数详解:

1、corePoolSize
指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
2、maximumPoolSize
指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
3、keepAliveTime
当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
4、unit
keepAliveTime的单位
5、workQueue
任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
6、threadFactory
线程工厂,用于创建线程,一般用默认即可;
7、handler
拒绝策略;当任务太多来不及处理时,如何拒绝任务;

3、多线程中workQueue任务队列集中使用类型

1、SynchronousQueue(直接提交队列)

使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;

下面代码中maximumPoolSize是2,运行三个线程故而会报错哦

public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000,
                TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        for (int i=0;i<3;i++){
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                        System.out.println("你好:"+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        pool.shutdown();
    }

2、ArrayBlockingQueue(有界的任务队列)

使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。

下面代码中maximumPoolSize是2,ArrayBlockingQueue中的等待队列四个,也就是说,运行6个线程肯定不会报错,超过6个可能报错哦

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = pool = new ThreadPoolExecutor(1, 2, 1000,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 6; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                        System.out.println("你好:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        pool.shutdown();
    }

3、LinkedBlockingQueue(无界的任务队列)

使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

下面代码中maximumPoolSize是2,LinkedBlockingQueue中的等待队列无限的,也就是说,运行多少个线程都不会报错,但是要注意服务器CPU、内容是否够用哦

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000,
                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 60; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                        System.out.println("你好:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        pool.shutdown();
    }
    

4、拒绝策略

handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;
一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略如下:

1、AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
2、CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
3、DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
4、DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;

以上内置的策略均实现了RejectedExecutionHandler接口,当然你也可以自己扩展RejectedExecutionHandler接口,定义自己的拒绝策略。
任务加了休眠阻塞,执行需要花费一定时间,导致会有一定的任务被丢弃,从而执行自定义的拒绝策略

public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
                /**
                 * 自定义拒绝策略
                 * @param r
                 * @param executor
                 */
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    System.out.println(r.toString() + "执行了拒绝策略");
                }
        });

        for (int i = 0; i < 20; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                        System.out.println("你好:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        pool.shutdown();
    }

5、ThreadFactory自定义线程创建

线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。那么通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等。

  public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool =  new ThreadPoolExecutor(2, 4, 1000,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                //自定义线程工厂
                new ThreadFactory() {
                    public Thread newThread(Runnable r) {
                        System.out.println("线程"+r.hashCode()+"创建");
                        //线程命名
                        Thread th = new Thread(r,"threadPool"+r.hashCode());
                        return th;
                    }
                }, new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 6; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                        System.out.println("你好:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        pool.shutdown();
    }

6、ThreadPoolExecutor扩展

ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个接口实现的。

  • 1、beforeExecute:线程池中任务运行前执行
  • 2、afterExecute:线程池中任务运行完毕后执行
  • 3、terminated:线程池退出后执行
    通过这三个接口我们可以监控每个任务的开始和结束时间,或者其他一些功能。
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService  pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                new ThreadFactory() {
                    public Thread newThread(Runnable r) {
                        System.out.println("线程"+r.hashCode()+"创建");
                        //线程命名
                        Thread th = new Thread(r,"threadPool"+r.hashCode());
                        return th;
                    }
                }, new ThreadPoolExecutor.CallerRunsPolicy()) {
                    protected void beforeExecute(Thread t,Runnable r) {
                        System.out.println("准备执行:"+r.hashCode());
                    }
                    protected void afterExecute(Runnable r,Throwable t) {
                        System.out.println("执行完毕:"+r.hashCode());
                    }
                    protected void terminated() {
                        System.out.println("线程池退出");
                    }
        };

        for (int i = 0; i < 6; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                        System.out.println("你好:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        pool.shutdown();
    }

7、线程池线程数量

Nthreads=CPU数量。
Ucpu=目标CPU的使用率,0<=Ucpu<=1。
W/C=任务等待时间与任务计算时间的比率。
Nthreads = NcpuUcpu(1+W/C)

你可能感兴趣的:(研究,自创,java,java-ee,开发语言)