Java多线程的实现方式

原文:http://www.javashuo.com/article/p-clpwlmyl-bo.htmljava

多线程的形式上实现方式主要有两种,一种是继承Thread类,一种是实现Runnable接口。本质上实现方式都是来实现线程任务,而后启动线程执行线程任务(这里的线程任务实际上就是run方法)。这里所说的,实际上都是在以上两种的基础上的一些变形。linux

第一种方式:继承Thread类

万物皆对象,那么线程也是对象,对象就应该可以抽取其公共特性封装成为类,使用类能够实例化多个对象,那么实现线程的第一种方式就是继承Thread类的方式。继承Thread类是最简单的一种实现线程的方式,经过jdk给咱们提供的Thread类,重写Thread类的run方法便可,那么当线程启动的时候,就会执行run方法体的内容。代码以下:spring

package com.hy.thread.t1;

/**
 * 继承Thread类的方式实现多线程演示
 * 
 * @author 007
 *
 */
public class ThreadDemo extends Thread {

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        td.start(); // 启动线程

        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

运行结果以下数据库

main is running ... 
Thread-0 is running ... 
main is running ... 
Thread-0 is running ... 
Thread-0 is running ... 
main is running ... 
Thread-0 is running ... 
main is running ... 

这里要注意,在启动线程的时候,咱们并非调用线程类的run方法,而是调用了线程类的start方法。那么咱们能不能调用run方法呢?答案是确定的,由于run方法是一个public声明的方法,所以咱们是能够调用的,可是若是咱们调用了run方法,那么这个方法将会做为一个普通的方法被调用,并不会开启线程。这里其实是采用了设计模式中的模板方法模式,Thread类做为模板,而run方法是在变化的所以放到子类。设计模式

建立多个线程

上面的例子中除了咱们建立的一个线程之外其实还有一个主线程也在执行。除了这两个线程之外还有没有其余的线程在执行了呢,实际上是有的,好比咱们看不到的垃圾回收线程,也在默默的执行。这里咱们并不去考虑有多少个线程在执行,上面咱们本身建立了一个线程,那么能不能多建立几个一块儿执行呢,答案是确定的。一个Thread类就是一个线程对象,那么多建立几个Thread类,并调用其start方法就能够启动多个线程了。代码以下缓存

package com.hy.thread.t1;

public class MultiThreadDemo extends Thread {

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        
        // 建立四个线程对象,表明四个线程
        MultiThreadDemo td1 = new MultiThreadDemo();
        MultiThreadDemo td2 = new MultiThreadDemo();
        MultiThreadDemo td3 = new MultiThreadDemo();
        MultiThreadDemo td4 = new MultiThreadDemo();
        
        td1.start(); // 启动线程
        td2.start(); // 启动线程
        td3.start(); // 启动线程
        td4.start(); // 启动线程

        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
}

运行结果以下多线程

main is running ... 
Thread-2 is running ... 
Thread-1 is running ... 
Thread-3 is running ... 
Thread-0 is running ... 
Thread-3 is running ... 
Thread-2 is running ... 
main is running ... 
Thread-1 is running ... 
Thread-0 is running ... 
Thread-1 is running ... 
main is running ... 
Thread-2 is running ... 
Thread-0 is running ... 
Thread-3 is running ... 

咱们发现这里有个问题,多个线程的名字都是系统定义好的,就是Thread-开头,后面跟数字,若是咱们每一个线程处理不一样的任务,那么咱们能不能给线程起上不一样的名字,方便咱们排查问题呢?答案是能够的。只要在建立线程实例的时候,在构造方法中传入指定的线程名称便可。以下:app

package com.hy.thread.t1;

public class MultiThreadDemo extends Thread {

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 指定线程名称的构造方法
     * 
     * @param name
     */
    public MultiThreadDemo(String name) {
        super(name);
    }
    
    public static void main(String[] args) {
        
        // 建立四个线程对象,表明四个线程
        MultiThreadDemo td1 = new MultiThreadDemo("t1"); // 指定线程的名字
        MultiThreadDemo td2 = new MultiThreadDemo("t2");
        MultiThreadDemo td3 = new MultiThreadDemo("t3");
        MultiThreadDemo td4 = new MultiThreadDemo("t4");
        
        td1.start(); // 启动线程
        td2.start(); // 启动线程
        td3.start(); // 启动线程
        td4.start(); // 启动线程

        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
}

运行的结果以下框架

main is running ... 
t1 is running ... 
t2 is running ... 
t3 is running ... 
t4 is running ... 
main is running ... 
t1 is running ... 
t2 is running ... 
t4 is running ... 
t3 is running ... 

 

第二种方式:实现Runnable接口

实现Runnable接口也是一种常见的建立线程的方式。使用接口的方式可让咱们的程序下降耦合度。Runnable接口中仅仅定义了一个方法,就是run。咱们来看一下Runnable接口的代码。less

package java.lang;

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

其实Runnable就是一个线程任务,线程任务和线程的控制分离,这也就是上面所说的解耦。咱们要实现一个线程,能够借助Thread类,Thread类要执行的任务就能够由实现了Runnable接口的类来处理。 这就是Runnable的精髓之所在!

使用Runnable实现上面的例子步骤以下:

  • 定义一个类实现Runnable接口,做为线程任务类
  • 重写run方法,并实现方法体,方法体的代码就是线程所执行的代码
  • 定义一个能够运行的类,并在main方法中建立线程任务类
  • 建立Thread类,并将线程任务类作为Thread类的构造方法传入
  • 启动线程

线程任务类代码以下

package com.hy.thread.t2;

public class ThreadTarget implements Runnable {

    @Override
    public void run() {
        while(true) {
            System.out.println(Thread.currentThread().getName() + " is running .. ");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

可运行类代码以下

package com.hy.thread.t2;

public class Main {
    
    public static void main(String[] args) {
        
        ThreadTarget tt = new ThreadTarget(); // 实例化线程任务类
        Thread t = new Thread(tt); // 建立线程对象,并将线程任务类做为构造方法参数传入
        t.start(); // 启动线程
        
        // 主线程的任务,为了演示多个线程一块儿执行
        while(true) {
            System.out.println(Thread.currentThread().getName() + " is running .. ");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

线程任务和线程的控制分离,那么一个线程任务能够提交给多个线程来执行。这是颇有用的,好比车站的售票窗口,每一个窗口能够看作是一个线程,他们每一个窗口作的事情都是同样的,也就是售票。这样咱们程序在模拟现实的时候就能够定义一个售票任务,让多个窗口同时执行这一个任务。那么若是要改动任务执行计划,只要修改线程任务类,全部的线程就都会按照修改后的来执行。相比较继承Thread类的方式来建立线程的方式,实现Runnable接口是更为经常使用的。

 

第三种方式:使用内部类的方式

这并非一种新的实现线程的方式,只是另外的一种写法。好比有些状况咱们的线程就想执行一次,之后就用不到了。那么像上面两种方式都还要再定义一个类,显得比较麻烦,咱们就能够经过匿名内部类的方式来实现。使用内部类实现依然有两种,分别是继承Thread类和实现Runnable接口。代码以下:

package com.hy.thread.t3;

public class DemoThread {
    
    public static void main(String[] args) {
        
        // 基于子类的实现
        new Thread() {
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
                    try {
                        Thread.sleep(1000); // 休息1000ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
        
        // 基于接口的实现
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
                    try {
                        Thread.sleep(1000); // 休息1000ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
        // 主线程的方法
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印当前线程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }

}

能够想象一下,我能不能既基于接口,又基于子类呢?像下面的代码会执行出什么样子呢?

package com.hy.thread.t3;

public class DemoThred2 {
    
    public static void main(String[] args) {
        
        
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                while (true) {
                    System.out.println("runnable is running ... "); // 打印当前线程的名字
                    try {
                        Thread.sleep(1000); // 休息1000ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }) {
            public void run() {
                while (true) {
                    System.out.println("sub is running ... "); // 打印当前线程的名字
                    try {
                        Thread.sleep(1000); // 休息1000ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
        
        
    }

}

运行结果以下:

sub is running ... 
sub is running ... 
sub is running ... 

咱们能够看到,实际上是基于子类的执行了,为何呢,其实很简单,咱们先来看一下为何不基于子类的时候Runnable的run方法能够执行。这个要从Thread的源码看起,下面是我截取的代码片断。

    public Thread(Runnable target)
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target; // 注意这里
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

其实上面的众多代码就是为了表现 this.target = target 那么target是什么呢,是Thread类的成员变量。那么在什么地方用到了target呢?下面是run方法的内容。

 @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

咱们能够看到,若是经过上面的构造方法传入target,那么就会执行target中的run方法。可能有朋友就会问了,咱们同时继承Thread类和实现Runnable接口,target不为空,那么为什么不执行target的run呢。不要忘记了,咱们在子类中已经重写了Thread类的run方法,所以run方法已经不在是咱们看到的这样了。那固然也就不回执行target的run方法。

第四种方式:定时器

定时器能够说是一种基于线程的一个工具类。能够定时的来执行某个任务。好比要在凌晨的时候汇总一些数据,好比要每隔10分钟抓取一次某个网站上的数据等等,总之计时器无处不在。咱们通常将须要定时完成的任务称之为计划任务,这在不少的系统中是很是常见的,好比linux的计划任务,好比Windows下的任务计划等等。咱们本身的系统中也须要不少定时执行的也都须要计划任务。最简单的计划任务就能够经过jdk给我提供的API来实现,固然也有不少的计划任务的框架,好比spring的schedule以及著名的quartz。咱们这里不去讨论其余的计划任务框架,咱们就来看一下jdk所给咱们提供的API来实现定时任务。

  • 例1:在2017年10月11日晚上10点执行任务。
package com.roocon.thread.t3;

import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 定时器举例
 * 
 */
public class TimerDemo {

    private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    public static void main(String[] args) throws Exception {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时任务执行了....");
            }
        }, format.parse("2017-10-11 22:00:00"));
    }

}
  • 例2: 每隔5s执行一次
package com.roocon.thread.t3;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo2 {
    
    public static void main(String[] args) {
        Timer timer = new Timer();
        
        timer.schedule(new TimerTask() {
            
            @Override
            public void run() {
                System.out.println("Hello");
            }
        }, new Date(), 5000);
    }

}

关于Spring的定时任务,能够经过spring的教程来学习。

 

第五种方式:带返回值的线程实现方式

咱们发现上面提到的不论是继承Thread类仍是实现Runnable接口,发现有两个问题,第一个是没法抛出更多的异常,第二个是线程执行完毕以后并没有法得到线程的返回值。那么下面的这种实现方式就能够完成咱们的需求。这种方式的实现就是咱们后面要详细介绍的Future模式,只是在jdk5的时候,官方给咱们提供了可用的API,咱们能够直接使用。可是使用这种方式建立线程比上面两种方式要复杂一些,步骤以下。

1. 建立一个类实现Callable接口,实现call方法。这个接口相似于Runnable接口,但比Runnable接口更增强大,增长了异常和返回值。
2. 建立一个FutureTask,指定Callable对象,作为线程任务。
3. 建立线程,指定线程任务。
4. 启动线程
package com.roocon.thread.t4;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class CallableTest {
    
    public static void main(String[] args) throws Exception {
        Callable<Integer> call = new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                System.out.println("thread start .. ");
                Thread.sleep(2000);
                return 1;
            }
        };
        
        FutureTask<Integer> task = new FutureTask<>(call);
        Thread t =  new Thread(task);
        
        t.start();
        System.out.println("do other thing .. ");
        System.out.println("拿到线程的执行结果 : " + task.get());
    }

}

执行结果以下:

do other thing .. 
thread start .. 
拿到线程的执行结果 : 1

Callable中能够经过范型参数来指定线程的返回值类型。经过FutureTask的get方法拿到线程的返回值。

 

第六种方式:基于线程池的方式

咱们知道,线程和数据库链接这些资源都是很是宝贵的资源。那么每次须要的时候建立,不须要的时候销毁,是很是浪费资源的。那么咱们就可使用缓存的策略,也就是使用线程池。固然了,线程池也不须要咱们来实现,jdk的官方也给咱们提供了API。

代码以下:

package com.roocon.thread.t5;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    
    public static void main(String[] args) {
        
        // 建立线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        
        while(true) {
            threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
                
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " is running ..");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

}

执行结果以下:

pool-1-thread-4 is running ..
pool-1-thread-1 is running ..
pool-1-thread-6 is running ..
pool-1-thread-2 is running ..
pool-1-thread-8 is running ..
pool-1-thread-3 is running ..
pool-1-thread-5 is running ..
pool-1-thread-9 is running ..
pool-1-thread-10 is running ..
pool-1-thread-7 is running ..

线程池的内容还有很是多,这里再也不详细地讲解。

 

Spring方式:使用Spring来实现多线程

这种方式依赖于Spring3以上版本,咱们能够经过Spring的@Async注解很是方便的实现多线程。具体的使用方式见 Spring实现多线程

相关文章
相关标签/搜索