美文网首页
【Java梳理】多线程

【Java梳理】多线程

作者: 田文健 | 来源:发表于2017-12-12 10:45 被阅读0次

    多线程是计算机执行并发任务的一个方案,随着现在多核CPU流行起来,多线程能够更好的利用其算力资源。一般来说进程是操作系统对程序的的管理方式,而线程一般是系统内核调度的基本单位,在一个进程中可以创建多个线程。操作系统一般使用时间片轮转调度算法来调度线程。关于操作系统线程管理的内容,这里不详述。

    创建线程
    在Java中,可以用两种方法来创建线程,直接继承Thread类实现run()方法或者实现Runnable接口并用Thread启动。

    //继承Thread的方式
        public static void main(String[] args) {
    
            new MyThread().start();
            System.out.println("end main:" + System.currentTimeMillis());
        }
    
        public static class MyThread extends Thread{
            @Override
            public void run() {
                //新线程开始
                super.run();
                try {
                    Thread.sleep(2 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("end thread: " + System.currentTimeMillis());
            }
        }
    
    //实现Runnable的方式,lamabda写法需要JDK8支持
        public static void main(String[] args) {
    
            new Thread(() -> {
                //新线程开始
                try {
                    Thread.sleep(2 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("end thread: " + System.currentTimeMillis());
            }).start();
    
            System.out.println("end main:" + System.currentTimeMillis());
        }
    

    线程同步
    使用了线程可以达到并发执行的效果,CPU资源的利用率也大大提高。但是这也面临许多问题,最主要的的就是并发下的资源竞争。比如说当一个线程在写一个日志文件时,另一个线程也要往里面写,这个时候就会竞争这个文件资源。操作系统的处理办法是让先访问文件的线程获得操作权限,后面的线程排队(挂起它或者告诉它获取失败)。线程在挂起的状态中会暂停执行,直到等待的资源能够获得,操作系统会再次调度到它。
    那么Java程序中的代码块和对象该怎么处理同步呢, Java提供了synchronized 和一些列的锁(Lock)类来保证资源的同步。

     public static void main(String[] args) {
    
            new Thread(() -> {
                syncMethod();
                System.out.println("end thread: " + System.currentTimeMillis());
            }, "my_thread").start();
    
            syncMethod();
            System.out.println("end main:" + System.currentTimeMillis());
        }
    
        /**
         * 同步方法,使用synchronized关键字
         */
        public static synchronized void syncMethod(){
            try {
                Thread.sleep(2 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("curr thread:" + Thread.currentThread().getName());
        }
    

    输出
    curr thread:main
    end main:1512129765291
    curr thread:my_thread
    end thread: 1512129767305

    下面是有个使用锁的例子

    Lock lock = new ReentrantLock();
            new Thread(() -> {
                //syncMethod();
                //System.out.println("end thread: " + System.currentTimeMillis());
    
                lock.lock();
                try {
                    Thread.sleep(2 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("end thread:" + System.currentTimeMillis());
                lock.unlock(); //释放锁
    
            }, "my_thread").start();
    
            //syncMethod();
            lock.lock();
            try {
                Thread.sleep(2 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end main:" + System.currentTimeMillis());
            lock.unlock(); //释放锁
    

    使用锁的时候要注意的是避免死锁,即线程A在等待B持有的资源,而此时B也在等待A持有的资源,这样就造成了相互等待谁都无法继续下去。
    还有一种情况是线程需要去获取自身已经得到的资源,如果不加以判断也会线程执行不下去。这样就产生了可重入锁的概念,即线程可以重复获得锁,在Java中synchronized 和 ReentrantLock都是可重入的。

    线程池:
    线程在程序中是会消耗计算机资源,尤其是创建线程时需要使用操作系统内核调用,不断创建线程也会给jvm垃圾回收带来一定的压力。这个时候可以使用线程池来对线程复用。一个使用线程池的例子:

     public static void main(String[] args) {
            ExecutorService executorService = new ThreadPoolExecutor(1,  //核心线程数
                    Runtime.getRuntime().availableProcessors() * 2,   //最大线程数
                    60, TimeUnit.SECONDS,   //空闲时间
                    new LinkedBlockingDeque<>(20),  //队列大小,超过则进入RejectedExecutionHandler
                    new ThreadPoolExecutor.AbortPolicy()  //拒绝处理,AbortPolicy策略抛出一个RejectedExecutionException,是默认策略
            );
    
            for (int i = 0; i < 30; i++) {
                Task task = new Task("task" + i);
                try {
                    executorService.submit(task);
                }catch (RejectedExecutionException e) {
                    System.out.println("task be rejected:" + task.name);
                }
            }
        }
    
        public static class Task implements Runnable {
            private String name;
    
            public Task(String name) {
                this.name = name;
            }
    
            public String getName() {
                return name;
            }
    
            @Override
            public void run() {
                try {
                    //do some things
                    Thread.sleep(5 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("task " + name + " end");
            }
        }
    

    我的机器是I5四核,运行这段代码,会发现只有两个任务被拒绝,是因为先到的任务会直接进入线程执行,剩下的任务会进入队列,所以一种执行了28个任务。

    java.util.concurrent包提供了支持并发的集合和工具,常用的如AtomicInteger,ReentrantLock, ConcurrentHashMap等

    相关文章

      网友评论

          本文标题:【Java梳理】多线程

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