一、概念介绍
1.1 并发与并行
并发和并行从宏观上来讲都是同时处理多路请求的概念,但并发和并行又有区别。
并行是指两个或者多个事件在同一时刻发生。
并发是指两个或多个事件在同一时间间隔内发生。
1.2 并发的特点
在操作系统中,并发是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理器上运行,但任一个时刻点上只有一个程序在处理机上运行。
①程序与计算不再一一对应,一个程序副本可以有多个计算
②并发程序之间有相互制约关系,直接制约体现为一个程序需要另一个程序的计算结果,间接制约体现为多个程序竞争某一资源,如处理机、缓冲区等。
③并发程序在执行中是走走停停,断续推进的。
二、如何提高执行速度
如果我们的处理器是多核的,那么就可以在这些处理器上分配多个任务,这样可以大大的提高系统的吞吐量。这种情况一般在服务器端很是常见。服务器给每个请求分配一个线程来提供服务,这样就可以将大量的用户请求分布到多个CPU上。但是,并发通常是提高运行在单处理上的程序的性能。这听起来的有点不合常理。因为单处理上运行的并发程序的开销要比该程序所有部分都顺序执行的开销大,因为在并发执行会有发生线程切换,这会浪费一部分系统资源。但是有一种特殊情况--阻塞,会使用我们觉得上面的认识变得合理一些。
如果进程在执行的过程中因等待某个事件发生或者是某个操作(I/O操作)要完成而发生的中止现象,我们就称进程被阻塞了。如果程序采用的是并发编程方式,那么一个进程的运行,可以由多个线程共同完成,当其中一个线程被阻塞了,其它的线程仍能够继续执行,所以总体上看,该进程并没有被阻塞。但是,如果进程在运行期间永远都不会发生阻塞,那么在单处理器让其并发执行将变得毫无意义。
三、java中的线程
java的线程机制是抢占式的,这表示调度机制会周期性的中止某个线程的执行,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都可以获得时间去完成它们的任务。
四、java中的并发编程
1 定义线程
- 方式一:实现Runnable接口,将我们的任务写到run方法中
public class LiftOff { public static void main(String[] args) throws InterruptedException { MyThread my = new MyThread(); new Thread(my).start(); System.out.println("This is LiftOff!"); } } class MyThread implements Runnable { @Override public void run() { System.out.println("hello thread!"); } }
- 方式二:采用Thread类
1 public class LiftOff { 2 3 public static void main(String[] args) throws InterruptedException { 4 Thread my = new MyThread(); 5 my.start(); 6 System.out.println("This is main function!"); 7 } 8 9 } 10 11 class MyThread extends Thread { 12 13 @Override 14 public void run() { 15 System.out.println("hello thread!"); 16 } 17 18 }
上面的代码可以改成这样 new Thread(new MyThread()).start(),使用匿名对象直接执行start(),但是值得注意的是,虽然没有指向该对象的引用,但垃圾回收器并不会回收该对象,直到run()方法执行完毕,这是因为每个线程在开始的时候都"注册"了它自己,每个对象都有指向它的引用。
- 方式三:如果我们用的线程池的话可以采用执行器Executor
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 public class LiftOff { 5 6 public static void main(String[] args) throws InterruptedException { 7 ExecutorService exec = Executors.newCachedThreadPool(); 8 for (int i = 0; i < 5; i++) 9 exec.execute(new MyThread()); 10 System.out.println("This is main function!"); 11 exec.shutdown(); 12 } 13 14 } 15 16 class MyThread implements Runnable { 17 18 @Override 19 public void run() { 20 System.out.println("hello thread!"); 21 } 22 23 }
Executor在程序员和任务执行之间提供了一个间接层,不需要我们主动去开启一个线程,而是通过这个中介来执行管理任务的执行。在调用shutdown后Executor不再会接管新的线程。
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 public class LiftOff { 5 6 public static void main(String[] args) throws InterruptedException { 7 ExecutorService exec = Executors.newFixedThreadPool(10); 8 for (int i = 0; i < 5; i++) 9 exec.execute(new MyThread()); 10 System.out.println("This is main function!"); 11 for (int i = 0; i < 5; i++) 12 exec.execute(new MyThread()); 13 exec.shutdown(); 14 } 15 16 } 17 18 class MyThread implements Runnable { 19 20 @Override 21 public void run() { 22 System.out.println("hello thread!"); 23 } 24 25 }
使用序列化线程,等一个线程执行完毕下一个线程才开始执行,如果对临界资源进行访问可以采用该方式
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 public class LiftOff { 5 6 public static void main(String[] args) throws InterruptedException { 7 ExecutorService exec = Executors.newSingleThreadExecutor(); 8 for (int i = 0; i < 5; i++) 9 exec.execute(new MyThread()); 10 System.out.println("This is main function!"); 11 for (int i = 0; i < 5; i++) 12 exec.execute(new MyThread()); 13 exec.shutdown(); 14 } 15 16 } 17 18 class MyThread implements Runnable { 19 public static int i = 0; 20 @Override 21 public void run() { 22 System.out.println(MyThread.i++); 23 } 24 25 }
-
方式五: 线程另类的创建方式,使用匿名类
1 import java.io.IOException; 2 3 class InnerThread { 4 private int countDown = 5; 5 private Thread t; 6 public InnerThread(String name) { 7 t = new Thread(name) { 8 @Override 9 public void run() { 10 while (true) { 11 System.out.println(this); 12 if (--countDown == 0) 13 return; 14 try { 15 sleep(10); 16 } catch (InterruptedException e) { 17 System.out.println("sleep() interrupt"); 18 } 19 } 20 } 21 @Override 22 public String toString() { 23 return getName() + " " + countDown; 24 } 25 }; 26 t.start(); 27 } 28 } 29 public class Test { 30 31 public static void main(String[] args) throws IOException { 32 new InnerThread("Demo1"); 33 } 34 }
2.创建有返回值的线程
上面的方式都是实行Runnable接口,但是Runnable方法执行完后没有返回值。如果我们需要线程执行完毕返回一个值,一种方式是在Thread子类中设置一个缓存区并对外暴露一个接口来提供对该缓存的访问。当线程执行的结尾将返回的数据保存到缓存区中,这样就可以达到提供一个返回值的目的。但是这样做有个坏处就是会出现竞态条件。另一种方式就是使用回调机制,在设计模式中被称为观察者模式。把那些对线程的运行结果感兴趣的类传递到线程内部,当线程运行快要结束时,调用这些类的方法来处理线程运行的结果。最后一种方式就是实现一个Callable接口,该接口从Java5后就被提出来以处理线程返回值的问题。
首先创建一个ExecutorService,他会根据需要为你创建线程。然后向ExecutorService提交Callable任务(Callable中的call方法会被放到线程中执行),对于每个Callable任务,会分别得到一个Future。线程运行放回的结果会被放到Future中。之后就可以向Future请求得到任务的结果。如果结果已经准备就绪,就会立即得到这个结果。如果还没准备好,轮询线程会阻塞,直到结果准备就绪。
1 import java.util.ArrayList; 2 import java.util.concurrent.Callable; 3 import java.util.concurrent.ExecutionException; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 import java.util.concurrent.Future; 7 8 public class LiftOff { 9 10 public static void main(String[] args) throws InterruptedException { 11 ExecutorService exec = Executors.newCachedThreadPool(); 12 ArrayList<Future<String>> results = new ArrayList<Future<String>>(); 13 for (int i = 0; i < 10; i++) 14 results.add(exec.submit(new MyThread(i))); 15 for (Future<String> fs : results) { 16 try { 17 System.out.println(fs.get()); 18 } catch (InterruptedException e) { 19 System.out.println(e); 20 } catch (ExecutionException e) { 21 System.out.println(e); 22 return; 23 } finally { 24 exec.shutdown(); 25 } 26 27 } 28 } 29 30 } 31 32 class MyThread implements Callable<String> { 33 private int id; 34 public MyThread(int id) { 35 this.id = id; 36 } 37 public String call() { 38 return "return of MyThread " + id; 39 } 40 }
实现Callable接口时需要指定泛型,该泛型表明线程执行完后的返回值。将任务加入线程池时需要调用sumbit()方法,而Executor会将线程的返回值封装到Future的实现类中,所以要获得最终的返回结果需要调用Future定义的get()方法。
3.设置线程优先级
线程的优先级将该线程的重要性专递给调度器。调度器倾向于让优先级最高的线程执行,而优先级低的线程执行的频率相对比较低。
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 public class SetPriorityDemo implements Runnable { 5 private int countDown = 5; 6 private volatile double d; 7 private int priority; 8 9 public SetPriorityDemo(int priority) { 10 this.priority = priority; 11 } 12 13 // Thread.MIN_PRIORITY = 1 14 // Thread.MAX_PRIORITY = 10 15 public static void main(String[] args) { 16 ExecutorService exec = Executors.newCachedThreadPool(); 17 for (int i = 0; i < 5; i++) { 18 exec.execute(new SetPriorityDemo(Thread.MIN_PRIORITY)); 19 exec.execute(new SetPriorityDemo(Thread.MAX_PRIORITY)); 20 } 21 exec.shutdown(); 22 } 23 24 @Override 25 public String toString() { 26 return Thread.currentThread() + ":" + countDown; 27 } 28 29 @Override 30 public void run() { 31 Thread.currentThread().setPriority(priority); 32 while (true) { 33 for (int i = 1; i < 100000; i++) { 34 d += (Math.PI + Math.E) / (double) i; 35 if (i % 1000 == 0) 36 Thread.yield(); 37 } 38 System.out.println(this); 39 if (--countDown == 0) 40 return; 41 } 42 } 43 44 }
4.后台线程
后台线程是指程序在运行的时候在后台提供一种通用服务器的线程,当所有非后台线程结束时,程序也就终止了,同时会杀死进程中所有的后台线程。main()是一个非后台线程。通过setDaemon(true)可以将一个线程变为后台线程
note:当一个线程为后台线程时,其派生出的子线程也是后台线程
由于后台线程会突然终止,所以finally中的代码块有可能不会被执行
1 import java.util.concurrent.TimeUnit; 2 3 public class SimpleDaemons implements Runnable { 4 5 @Override 6 public void run() { 7 try { 8 while (true) { 9 TimeUnit.MILLISECONDS.sleep(100); 10 System.out.println(Thread.currentThread() + " " + this); 11 } 12 } catch (InterruptedException e) { 13 System.out.println("sleep() interrupted"); 14 } 15 } 16 17 public static void main(String[] args) throws InterruptedException { 18 for (int i = 0; i < 10; i++) { 19 Thread daemon = new Thread(new SimpleDaemons()); 20 daemon.setDaemon(true); 21 daemon.start(); 22 } 23 System.out.println("All daemon started"); 24 TimeUnit.MILLISECONDS.sleep(175);// 睡眠175ms 25 } 26 27 }
执行结果:
如果将每个线程的睡眠时间调整大一些,输出结果就会发生改变
1 public void run() { 2 try { 3 while (true) { 4 TimeUnit.MILLISECONDS.sleep(175); 5 System.out.println(Thread.currentThread() + " " + this); 6 } 7 } catch (InterruptedException e) { 8 System.out.println("sleep() interrupted"); 9 } 10 }
输出结果:
5.通过线程工厂设置线程属性
我们可以通过实现ThreadFactory接口来定义自己的线程工厂,通过工厂设置线程的优先级、是否为后台线程等等
1 import java.util.concurrent.ThreadFactory; 2 import java.util.concurrent.TimeUnit; 3 4 public class SimpleDaemons implements Runnable { 5 6 @Override 7 public void run() { 8 try { 9 while (true) { 10 TimeUnit.MILLISECONDS.sleep(175); 11 System.out.println(Thread.currentThread() + " " + this); 12 } 13 } catch (InterruptedException e) { 14 System.out.println("sleep() interrupted"); 15 } 16 } 17 18 public static void main(String[] args) throws InterruptedException { 19 for (int i = 0; i < 10; i++) { 20 Thread daemon = new MyThreadFactory() 21 .newThread(new SimpleDaemons()); 22 daemon.start(); 23 } 24 System.out.println("All daemon started"); 25 TimeUnit.MILLISECONDS.sleep(175);// 睡眠175ms 26 } 27 28 } 29 30 class MyThreadFactory implements ThreadFactory { 31 32 @Override 33 public Thread newThread(Runnable r) { 34 Thread t = new Thread(r); 35 t.setDaemon(true); 36 return t; 37 } 38 39 }
6.线程之间的等待
我们可能会有这样一种需求,线程A只有等线程B运行结束后自己才能继续运行。这时候需要用到线程中的join()方法,A线程在自己的代码中调用了B线程的join()方法,则线程A会一直等待线程B执行完之后,自己才能够继续执行。
1 import java.io.IOException; 2 class Sleeper extends Thread { 3 private int duration; 4 public Sleeper(String name, int sleepTime) { 5 super(name); 6 duration = sleepTime; 7 start(); 8 } 9 public void run() { 10 try { 11 sleep(duration); 12 } catch (InterruptedException e) { 13 System.out.println(getName() + " was interrupted. " 14 + "isInterrupted():" + isInterrupted()); 15 return; 16 } 17 System.out.println(getName() + " has awakened"); 18 } 19 } 20 21 class Joiner extends Thread { 22 private Sleeper sleeper; 23 public Joiner(String name, Sleeper sleeper) { 24 super(name); 25 this.sleeper = sleeper; 26 start(); 27 } 28 public void run() { 29 try { 30 sleeper.join(); 31 } catch (InterruptedException e) { 32 System.out.println("Interrupted"); 33 } 34 System.out.println(getName() + " join completed"); 35 } 36 } 37 38 public class Test { 39 40 public static void main(String[] args) throws IOException { 41 Sleeper sleepy = new Sleeper("Sleeper", 15000), grumpy = new Sleeper( 42 "Grumpy", 1500); 43 Joiner dopey = new Joiner("Dopey", sleepy), doc = new Joiner("Doc", 44 grumpy); 45 grumpy.interrupt(); 46 } 47 }
上面代码中总共创建了4个线程,两个睡眠线程Sleepy, Grumpy,两个等待线程Dopey,Doc。Dopey调用了Sleepy的join方法,只有等Sleepy醒来之后,Dopey才执行,从输出可以看出先打印 Sleeper has awakened 后打印 Dopey join completed。Grumpy睡眠线程在睡眠过程被中断了(调用了interrupt()方法),但是我们发现查看Grumpy线程是否被中断时,返回值为false。这是因为当一个线程在该线程上调用interrupte()时,将给该线程设定一个标志,表明该线程已经被中断。然而,异常被捕获时将清理这个标志,所以在catch子句中,异常被捕获的时间这个标志位总是为假。
7.捕获线程抛出的异常
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 class Test implements Runnable { 5 @Override 6 public void run() { 7 throw new RuntimeException(); 8 } 9 public static void main(String[] args) { 10 try { 11 ExecutorService exe = Executors.newCachedThreadPool(); 12 exe.execute(new Test()); 13 } catch (Exception e) { 14 System.out.println("Exception!"); 15 } 16 } 17 }
在上面的代码中我们可看到在main函数中创建了一个线程,并通过执行器对该线程进行管理,当该线程抛出异常时我们将其捕获,然后在控制台输出Exception这样的提示。但是运行结果并不是我们想象的那样。这是因为线程有个特性就是不会将自己产生的异常抛给另一个线程(我们的代码运行时会产生出两个线程,一个线程执行mai()方法,一个是我们自己new出来的)。
解决方法:给每个线程上绑定一个异常处理器,该处理器会在线程抛出致命性异常(该异常可能导致线程终止)时被调用,调用时会创建一个新的线程来执行处理器中的代码
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 import java.util.concurrent.ThreadFactory; 4 5 public class Test { 6 public static void main(String[] args) { 7 8 ExecutorService exe = Executors 9 .newCachedThreadPool(new HandlerThreadFactory()); 10 exe.execute(new ExceptionThread2()); 11 } 12 } 13 14 class ExceptionThread2 implements Runnable { 15 public void run() { 16 throw new RuntimeException(); 17 } 18 } 19 20 class MyUncaughtExecptionHandler implements Thread.UncaughtExceptionHandler { 21 22 @Override 23 public void uncaughtException(Thread t, Throwable e) { 24 System.out.println("caught " + e); 25 } 26 } 27 28 class HandlerThreadFactory implements ThreadFactory { 29 30 @Override 31 public Thread newThread(Runnable r) { 32 System.out.println(this + " creating new Thread"); 33 Thread t = new Thread(r); 34 System.out.println("created " + t); 35 t.setUncaughtExceptionHandler(new MyUncaughtExecptionHandler()); 36 System.out.println("eh = " + t.getUncaughtExceptionHandler()); 37 return t; 38 } 39 40 }
执行结果中可以看出在进行异常捕获时创建了一个名为Thread-1.5的线程对异常进行处理