线程:多个任务同时进行,看似多任务同时进行,但实际上一个时间点上咱们大脑仍是只在作一件事情。程序也是如此,除非多核cpu,否则一个cpu里,在一个时间点里仍是只在作一件事,不过速度很快的切换,形成同时进行的错觉。java
多线程:编程
方法间调用:普通方法调用,从哪里来到哪里去,是一条闭合的路径;
使用多线程:开辟了多条路径。设计模式
进程和线程:安全
也就是 Process 和 Thread ,本质来讲,进程做为资源分配的单位,线程是调度和执行的单位。具体来讲:网络
其余概念:多线程
建立线程:并发
在 java 中,建立线程有 3 种方式:ide
根据设计原则,不论是里氏替换原则,仍是在工厂设计模式种,都提到过,尽可能多用实现,少用继承,因此通常状况下尽可能使用第二种方法建立线程。函数式编程
先直接看下面一个 demo函数
/* 建立方式1:继承Thread + 重写run 启动方式:建立子类对象 + start */ public class StartThread extends Thread { //线程入口点 @Override public void run() { for (int i=0; i<50; i++){ System.out.print("睡觉ing "); } } public static void main(String[] args) { //建立子类对象 StartThread startThread = new StartThread(); //启动,主意是start startThread.start(); for (int i=0; i<50; i++){ System.out.print("吃饭ing "); } } }
咱们把上面的run方法成为线程的入口点,里面是线程执行的代码,当程序运行以后,能够发现,每次的运行结果都是不同的。
能够看到这种随机穿插执行的结果,这是由cpu去安排时间片,调度决定的。
到这里咱们总结使用第一种方法建立线程的步骤就是:
这种方法是推荐的方式,和上一种写法相比较,很简单,只须要把 extends Thread 改为 implements Runnable ,其余的地方几乎没有变化。
区别在于,调用的时候,不能直接 start(),只能借助一个 Thread 对象做为代理。
/* 建立方式2:实现Runnable + 重写run 启动方式:建立实现类对象 + 借助thread代理类 + start */ public class StartThreadwithR implements Runnable { @Override public void run() { for (int i=0; i<50; i++){ System.out.print("睡觉ing "); } } public static void main(String[] args) { StartThreadwithR startThread = new StartThreadwithR(); //建立代理类 Thread t = new Thread(startThread); t.start();//启动 for (int i=0; i<50; i++){ System.out.print("吃饭ing "); } } }
总结第二种建立线程的方法步骤是:
特殊的,若是咱们的一个对象只使用一次,那就彻底能够用匿名,上面的
StartThreadwithR startThread = new StartThreadwithR(); Thread t = new Thread(startThread); t.start();
能够改为:
new Thread(new StartThreadwithR()).start();
两种方法相比,由于推荐优先实现接口,而不是继承类,因此第二种方法是推荐的。
当多个线程同时进行修改资源的时候,可能出现线程不安全的问题,最上面咱们提到了,这里作一个简单模拟。
假如三个黄牛同时在抢票,服务端的票数--的过程,对于三个线程可能会出现哪些问题呢?
/* 使用多线程修改资源带来的线程安全问题 */ public class Tickets implements Runnable{ private int ticketNum = 100; @Override public void run() { while(true){ if (ticketNum<0){ break; } System.out.println(Thread.currentThread().getName() + "正在抢票,余票" + ticketNum--); } } //客户端 public static void main(String[] args) { Tickets tickets = new Tickets(); //多个Thread代理 new Thread(tickets,"黄牛1").start(); new Thread(tickets,"黄牛2").start(); new Thread(tickets,"黄牛3").start(); } }
这里面用了简单的模拟服务端和客户端行为,请求票的时候,分别对票数进行 -- 操做,执行以后咱们来看:
显然出现了逻辑上的错误,由于多个线程的执行带来的问题。
从运行结果的最后两行入手,背后的缘由是:
若是咱们再模拟一个网络延迟,在 run 方法里加入:
//加入线程阻塞,模拟网络延迟 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
多运行几遍,甚至可能票数变成负数。
显然,若是在实际开发中,票数的变化,应该是严格递减的过程,而且,余票到达 0 就应该 break,而不能还出现继续执行了--操做,从而出现这种错误(不考虑退票之类的业务)。
这就是 高并发 问题,主要就是多线程带来的安全问题。
再来看一个例子,假若有乌龟和兔子进行赛跑,咱们模拟两个线程,分别对距离++。
/* 龟兔赛跑,借助Runnable和Thread代理 */ public class Racer implements Runnable{ private String winner; @Override public void run() { for (int dis=1; dis<=100; dis++){ System.out.println(Thread.currentThread().getName() + " 跑了 " + dis); //每走一步,判断是否比赛结束 if (gameOver(dis))break; } } public boolean gameOver(int dis){ if (winner != null){ return true; } else if (dis == 100){ winner = Thread.currentThread().getName(); System.out.println("获胜者是 "+winner); return true; } return false; } public static void main(String[] args) { Racer racer = new Racer();//1.建立实现类 new Thread(racer,"兔子").start();//2.建立代理类并start new Thread(racer,"乌龟").start(); } }
这样运行起来,总会有一我的赢,可是赢的每次不必定是哪个。
面对高并发的状况,须要用到线程池。
来看从新实现的龟兔赛跑:
/* 建立方法3:Callable,是java.util.concurrent包里的内容 */ public class RacerwithCal implements Callable<Integer> { private String winner; //须要实现的是call方法 @Override public Integer call() throws Exception { for (int dis=1; dis<=100; dis++){ System.out.println(Thread.currentThread().getName() + " 跑了 " + dis); //每走一步,判断是否比赛结束,而且结束能够有返回值 if (gameOver(dis))return dis; } return null; } public boolean gameOver(int dis){ if (winner != null){ return true; } else if (dis == 100){ winner = Thread.currentThread().getName(); if (winner.equals("pool-1-thread-1"))System.out.println("获胜者是 乌龟"); else System.out.println("获胜者是 兔子"); return true; } return false; } public static void main(String[] args) throws ExecutionException, InterruptedException { //1.建立目标对象 RacerwithCal race = new RacerwithCal(); //2.建立执行服务,含有2个线程的线程池 ExecutorService service = Executors.newFixedThreadPool(2); //3.提交执行 Future<Integer> result1 = service.submit(race); Future<Integer> result2 = service.submit(race); //4.获取结果:pool-1-thread-1也就是第一个线程是乌龟,第二个兔子 Integer i = result1.get(); Integer j = result2.get(); System.out.println("比分是: "+ i + " : " + j); //5.关闭服务 service.shutdownNow(); } }
来看执行结果:
总结一下,步骤通常分为 5 步:
能够看到,这种方法的特殊之处在于:
注意到在前面使用第二种方法建立多线程的时候,提到了 new Thread(tickets,"黄牛1").start(); 是使用了 Thread 做为代理。代理模式自己也是设计模式种的一种,分为动态代理和静态代理,代理模式在开发中记录日志等等很经常使用。
静态代理的代理类是直接写好的,拿过来用,动态代理则是在程序执行过程当中临时建立的。
在这里简单介绍静态代理。
实现一个婚庆公司,做为你的婚礼的代理,而后进行婚礼举办。
/* 静态代理模式demo 1.真实角色 2.代理角色 3.1和2都实现同一个接口 */ public class StaticProxy { public static void main(String[] args) { //彻底相似于 new Thread(new XXX()).start(); new WeddingCompany(new You()).wedding(); } } //接口 interface Marry{ void wedding(); } //真实角色 class You implements Marry{ @Override public void wedding() { System.out.println("结婚路上ing"); } } //代理角色 class WeddingCompany implements Marry{ //要代理的真实角色 private Marry target; public WeddingCompany(Marry target) { this.target = target; } @Override public void wedding() { ready();//准备 this.target.wedding(); after();//善后 } private void after() { System.out.println("结束ing"); } private void ready() { System.out.println("布置ing"); } }
能够看到,最后的调用方法就至关因而写线程的时候用到的 new Thread(new XXX()).start();
小小区别就在于,咱们写的线程类是实现的 run 方法,没有实现start方法,可是不重要。
重要的是,代理类 可能作了不少的事,而中间须要 真实类 实现的一个方法必须实现,其余的方法,真实类不须要关心,也就是交给代理类去办了。
jdk1.8 后可使用 lambda 表达式来简化代码,通常用在 只使用一次的、简单的线程 里面。
简化的写法有不少,下面是逐渐简化的过程。
若是某个类只但愿使用一次,能够用静态内部类来实现,调用的时候同样。
public class StartThreadLambda { //静态内部类 static class Inner implements Runnable{ @Override public void run() { for (int i=0; i<50; i++){ System.out.print("睡觉ing "); } } } //静态内部类 static class Inner2 implements Runnable{ @Override public void run() { for (int i=0; i<50; i++){ System.out.print("吃饭ing "); } } } public static void main(String[] args) { new Thread(new Inner()).start(); new Thread(new Inner2()).start(); } }
使用静态内部类的好处是,不使用的时候这个内部类是不会编译的,这其实就是一个单例模式。
还能够直接写到 main 方法内部,由于main 方法就是static,只启动一次。
public class StartThreadLambda { public static void main(String[] args) { //方法内部类(局部内部类) class Inner implements Runnable{ //。。。。。。 } class Inner2 implements Runnable{ //。。。。。。 } new Thread(new Inner()).start(); new Thread(new Inner2()).start(); } }
更进一步,能够直接利用匿名内部类,不用声明出类的名称来。
public class StartThreadLambda { public static void main(String[] args) { //匿名内部类,必须借助接口或者父类,由于没有名字 new Thread(new Runnable() { @Override public void run() { for (int i=0; i<50; i++){ System.out.print("吃饭ing "); } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i=0; i<50; i++){ System.out.print("睡觉ing "); } } }).start(); } }
这里面必须带上实现体了就,由于没有名字,那么就要借助父类或者接口,而父类或者接口的run方法是须要重写/实现的。
jdk 8 对匿名内部类写法再进行简化,只用关注线程体,也就是只关注 run 方法里面的内容。
public class StartThreadLambda { public static void main(String[] args) { //使用Lambda表达式 new Thread(()-> { for (int i=0; i<50; i++){ System.out.print("吃饭ing "); } }).start(); new Thread(()->{ for (int i=0; i<50; i++){ System.out.print("睡觉ing "); } }).start(); } }
() - > 这个符号,编译器就默认你是在实现 Runnable,而且默认是在实现 run 方法。
显然,若是不是线程,是其余的咱们本身写的接口+实现类,Lambda表达式也是可用的,并且能够进行参数和返回值的扩展。
public class LambdaTest { public static void main(String[] args) { //直接使用lambda表达式实现接口 Origin o = (int a, int b)-> { return a+b; }; System.out.println(o.sum(100,100)); } } //自定义接口,至关于Runnable interface Origin{ int sum(int a, int b); }
更有甚者,参数的类型也能够省略,他会本身去匹配:
//省略参数类型 Origin o1 = (a, b) -> { return a+b; };
若是实现接口的方法,只有一行代码,甚至花括号也能够省略:
Origin o2 = (a, b) -> a+b;
有关返回值和参数的个数仍是有一些细微差异的。
Lambda表达式也在 Sort 方法里有应用,要想对引用类型里面统一按照某个属性进行排序,须要实现Comparator接口里面的compare方法,可使用简化写法。