线程
多线程就是一个程序中有多个线程在同时执行。
多线程下CPU的工作原理
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
一、创建线程
方法1:继承Thread类,重写run方法
public class SubThread extends Thread{ public SubThread(){ super("x5456"); //通过构造方法修改线程名 } public void run() { for(int i=0;i<100;i++){ System.out.println(super.getName()+i); } } }
调用:
public static void main(String[] args) { //创建刚刚继承Thread类的子类的对象 SubThread st = new SubThread(); //通过setName方法,修改线程名 st.setName("x54256"); //调用对象的start方法,会自动执行我们重写的run方法 st.start(); for(int i=0;i<100;i++) { System.out.println(Thread.currentThread().getName()+i); //获取当前线程的对象,调用getname()方法 } }
方法2:实现接口Runnable,重写run方法
public class SubRunnable implements Runnable{ public void run(){ for(int i=0;i<100;i++){ try { // 调用Thread类的sleep方法,休眠50ms,由于父接口没有throws异常,so我们只能用try...catch Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"..."+i); } } }
调用:
public static void main(String[] args) { //创建实现Runnable接口的类的对象 SubRunnable sr = new SubRunnable(); //创建Thread类的对象 Thread t = new Thread(sr); //启动线程 t.start(); for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+"..."+i); } }
方法3:使用匿名内部类,实现多线程程序
匿名内部类的前提:继承或者接口实现
使用方法:
new 父类或者接口(){
重写抽象方法
}
public static void main(String[] args) { //继承方式 XXX extends Thread{ public void run(){}} new Thread(){ public void run(){ System.out.println("!!!"); } }.start(); //实现接口方式 XXX implements Runnable{ public void run(){}} Runnable r = new Runnable(){ public void run(){ System.out.println("###"); } }; new Thread(r).start(); //==================或===================== new Thread(new Runnable(){ public void run(){ System.out.println("@@@"); } }).start(); }
实现接口的好处:
高内聚,低耦合:模块内能做的事就自己做,模块间的关系要尽量的小
第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
多线程的内存图解:
线程的一生:
二、线程池
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
方法1:使用线程池方式--Runnable接口
public static void main(String[] args) { //调用工厂类的静态方法,创建线程池对象(ExecutorService接口的实现类) //返回线程池对象,是返回的接口 ExecutorService es = Executors.newFixedThreadPool(2); //池内有2个线程 //调用接口实现类对象es中的方法submit提交线程任务 //将Runnable接口实现类对象,传递 es.submit(new SubRunnable()); es.submit(new SubRunnable()); es.submit(new SubRunnable()); es.submit(new SubRunnable()); }
实现的Runnable接口
public class ThreadPoolRunnable implements Runnable { public void run(){ System.out.println(Thread.currentThread().getName()+" 线程提交任务"); } }
方法2:使用线程池方式—Callable接口
之前的实现方法,线程运行完没有返回值,而且不能抛异常。
Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService es = Executors.newFixedThreadPool(2); //提交线程任务的方法submit方法返回 Future接口的实现类 Future<Integer> f = es.submit(new SubCallable()); //获取返回值 Integer i = f.get(); System.out.println(i); }
实现的Callable接口
public class SubCallable implements Callable<Integer>{ @Override public Integer call() { return 123; } }
使用线程实现异步计算
private int a; //通过构造方法传参 public GetSumCallable(int a){ this.a=a; } public Integer call(){ int sum = 0 ; for(int i = 1 ; i <=a ; i++){ sum = sum + i ; } return sum; }
ThreadPoolDemo.java
/* * 使用多线程技术,求和 * 两个线程,1个线程计算1+100,另一个线程计算1+200的和 * 多线程的异步计算 */ public class ThreadPoolDemo { public static void main(String[] args)throws Exception { ExecutorService es = Executors.newFixedThreadPool(2); Future<Integer> f1 =es.submit(new GetSumCallable(100)); Future<Integer> f2 =es.submit(new GetSumCallable(200)); System.out.println(f1.get()); System.out.println(f2.get()); es.shutdown(); } }
FutureTask的使用
FutureTask继承了Callable和Future接口,所以它既能像callable一样被Thread执行,也能像Future那样获取结果。
@Override public boolean testServiceUrl(ServiceUrlTestDTO url){ // 调用地图服务 Callable<Boolean> callable = new WebServiceUtil(url.getUrl(),url.getProxy(),url.getToken()); FutureTask<Boolean> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.start(); try { Boolean isOK = futureTask.get(); System.out.println("服务【"+ url.getUrl() +"】测试:"+ isOK); return isOK; } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } return false; }