多线程
进程vs线程:
进程:每个程序被运行加载到内存之后,都会被操作系统作为一个进程,进程是处于运行过程中的程序,是具有独立功能,被操作系统进行资源分配和调度的独立单元。
线程:一个进程里面可以拥有多个线程,线程拥有自己的堆栈,程序计数器和自己的局部变量,但是不拥有系统资源,多个线程共享进程的系统资源。
创建线程的三种方式:
1.继承Thread类创建线程类
继承Thread类,重写run()方法,该run()方法就代表程序需要完成的任务。创建Thread子类的实例,即创建线程对象。然后通过start()方法启动线程。
1 public class MyThread extends Thread { 2 private int count; 3 4 @Override 5 public void run() { 6 7 for (; count < 100; count ++) { 8 System.out.println(getName() + "---" + count); 9 } 10 } 11 12 public static void main(String[] args) { 13 for (int i = 0; i < 100; i++) { 14 System.out.println(Thread.currentThread().getName() + i); 15 if (i == 20) { 16 new MyThread().start(); 17 new MyThread().start(); 18 } 19 } 20 } 21 }
2.实现Runnable接口来创建并启动多线程
继承Runnable接口,并重写该接口的run()方法。创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread类对象,该Thread类对象才是真正的多线程对象。然后调用start()方法来执行该Thread实例线程。
示例:
1 public class MyRunableThread implements Runnable { 2 3 private int count = 0; 4 5 @Override 6 public void run() { 7 for (; count < 20; count ++){ 8 System.out.println(Thread.currentThread().getName() + "--" + count); 9 } 10 } 11 12 public static void main(String[] args){ 13 for (int i=0; i<30; i++){ 14 System.out.println(Thread.currentThread().getName() + "--" + i); 15 if (i == 20){ 16 MyRunableThread myRunableThread = new MyRunableThread(); 17 new Thread(myRunableThread, "子线程1").start(); 18 new Thread(myRunableThread, "子线程2").start(); 19 } 20 } 21 } 22 }
3.使用Callable和Future创建线程
Java无法将任意方法包装成线程执行体,在Java5之后提供了一个Callable接口,该接口提供了call()方法作为线程执行体,类似于Runnable接口,call()方法类似于run()方法。但是call()方法比run()方法作为执行体,就更加强大。
1.call()可以有返回值。
2.call()可以声明抛出异常。
public class MyCallableThread { public static void main(String[] args) { //FutureTask包装Callable实例对象,此处使用了Lambda表达式来创建一个Callable对象 FutureTask<Integer> task = new FutureTask<>(() -> { int count = 0; for (; count < 10; count++) { System.out.println(Thread.currentThread().getName() + "--" + count); } return count; }); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "--" + i); if (i == 20) { new Thread(task, "callAbleThread").start(); } } try { //Callable线程获取返回值 System.out.println("线程的返回值为:" + task.get()); } catch (Exception e) { e.getStackTrace(); } } }
Future接口代表Callable接口里call()方法的返回值,FutureTask是Future接口的实现类,FutureTask实现类实现了Future接口和Runnable接口,可以做为Thread类的Target。Future接口里提供了如下几个方法来控制实现Callable接口的任务:
boolean cancel(Boolean mayInterruptIfRunning) 试图取消该Future里关联的Callable任务。
V get()返回Callable任务里的返回值。该方法会阻塞程序,必须等子线程结束后,才会得到返回值。
V get(long timeOut, TimeUnit unit)返回Callable任务里的返回值。该方法让程序最多阻塞的最长时间,由timeOut和unit来指定,如果超时,Callable接口依然没有返回值,则会抛出TimeOutException异常。
boolean isCancelled()如果在Callable任务正常执行前被取消,则返回true
boolean isDone()如果Callable任务已经完成,则返回true。
Callable接口创建线程的步骤:
1)创建Callable接口的实现类,并实现Call()方法,该方法作为线程的执行体,且该Call()方法有返回值,在创建Callable实现类的实例。也可以使用Lambda表达式来创建Callable接口的实例。
2)使用FutureTask对象来包装Callable接口的实现类实例,FutureTask包装了Callable实现类的返回值。
3)使用FutureTask对象作为Thread对象的target创建并启动多线程。
4)调用FutureTask对象的get()方法来获取子线程执行结束后的返回值。
控制线程
(1)join线程
join()方法时Thread提供一个线程等待被join()线程完成的方法。
join()等待被join线程执行完成。
join(long millis)等待被join线程的最长时间,单位为millis毫秒。
(2)后台进程
后台进程就是为了其他进程提供服务,一旦前台进程死亡之后,后台进程就自动死亡。
setDaemon(true);设置后台进程
(3)线程睡眠sleep()
如果需要当前线程暂停一段时间,并进入阻塞状态,可以使用Thread类的sleep()方法。
static void sleep(long millis) 让当前正在执行的线程暂停millis毫秒,并进入阻塞状态。
static void sleep(long millis, int nanos) 让当前正在执行的线程暂停millis毫秒,加上nanos毫微秒。
(4)线程让步:yield()
让当前正在执行的线程暂停,但是它不会阻塞该线程,只是将线程转入阻塞状态。只是让当前线程暂停一下,处于就绪状态,让线程调度器将其调度出来重新执行。
线程同步
sychronized可以用来修饰方法和代码块,来实现加锁。
线程通信
(1)Object类提供的wait(),notify(),notifyAll()三个方法可以实现线程通信。一般用于sychronized关键字保证的同步通信,这三个方法可以由同步监视器对象来调用,可以分为如下两种情况:
1)对于sychronized修饰的同步方法,该类默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法。
2)对于使用sychronized修饰的同步代码块,同步监视器就是sychronized括号里面的对象,所以必须使用该对象调用上述三个方法。
await():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或者notifyAll()方法来唤醒该线程。wait()方法有三种形式,无时间参数表示一直等待,直到其他线程唤醒,另外两种形式是等待一段时间后自动唤醒。
notify()唤醒在此同步监视器上等待的单个线程。如果所有线程都在此监视器上等待,则会唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定之后,才可以执行被唤醒的线程。
notifyAll()唤醒在此同步监视器上等待的所有线程。
(2)使用Condition控制线程通信
使用Lock对象来保证同步时,java提供了一个Condition类来保持协调。使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象。
Condition将同步监视器方法(wait(),notify(),notifyAll())分解成不同的对象,以便通过将这些对象与Lock对象组合使用。Lock替代了同步方法或者同步代码块,Condition替代了同步监视器的功能。
Condition实例被绑定在一个Lock对象上。获取特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。Condition类提供的三个方法如下:
await()类似于隐式同步监视器上的await()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。
signal()唤醒此Lock对象上等待的单线程。如果所有线程在该Lock对象上等待,则会选择性唤醒其中一个线程。只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程。
signalAll()唤醒此Lock对象上等待的所有线程。只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程。
(三)使用阻塞队列(BlockingQueue)控制线程通信
BlockingQueue的主要用途并不是作为容器,而是作为线程同步的工具。BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则线程被阻塞,当消费者线程试图从BlockingQueue中取出元素时,如果队列已空,则线程被阻塞。
程序的两个线程通过交替向BlockingQueue中放入元素,取出元素,既可以很好的控制线程的通信。
BlockingQueue提供的方法:
put(E e) 尝试把E元素放入BlockingQueue中,如果该队列元素已满,则阻塞该线程。
take()尝试从BlockingQueue的头部取出元素,如果该队列中的元素已空,则阻塞该线程。
BlockingQueue继承Queue接口。同样也可以使用Queue接口中提供的方法。Queue接口中提供的方法归纳起来分为三组:
1)在队列尾部插入元素。包括:add(E e), offer(E e)和put(E e)方法,当该队列已满时,这三个方法分别会抛出异常,返回false,阻塞队列。
2)在队列头部删除并返回删除的元素。包括remove(),poll()和take()方法。当该队列已空时,这三个方法分别会抛出异常,返回false,阻塞队列。
3)在队列头部取出但并不删除元素。包括element()和peek(),当队列已空时,这两个方法分别抛出异常,返回false。
线程池
优点:实现资源的复用,有效控制并发线程的数量,有效的管理线程。
使用Executors工厂类生产线程池,该工厂类包含如下几个静态工厂方法来创建线程池。
newCachedThreadPool()创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。
newFixedThreadPool(int nThread)创建一个可用的具有固定线程数的线程池。
newSingleThreadExecutor()创建一个具有单线程的线程池,相当于newFixedThreadPool()方法传入参数为1.
newScheduledThreadPool(int corePoolSize)创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池里。
newSingleScheduledExecutor()创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。
ExecutorService newWorkStealingPool(int parallelism)创建持有足够的线程的线程池来支持给定的并行级别,该方法会使用多个队列来减少竞争。
ExecutorService newWorkStealingPool()该方法是上一个方法的简化版本。如果当前机器有4个CPU,则目标并行级别被设置成为4,也就是相当于为前一个方法传入4作为参数。
上面7个方法的前三个方法返回一个ExecutorService对象,该对象代表一个线程池,它可以执行Runnable对象和Callable对象所代表的线程;而中间的两个方法返回一个ScheduleExecutorService线程池,它是ExecutorService的子类,它是在指定延迟后执行线程任务。后两个方法。生成的work stealing池,相当于后台线程池,如果所有前台线程都死亡了,work stealing池的线程会自动死亡。
ExecutorService代表线程池,程序只需要将Runnable对象或者Callable对象(代表线程任务)提交给指定的线程池,该线程池就会尽快执行该任务。
ExecutorService提供如下方法:
Future<?> submit(Runnable task)将一个Runnable对象提交给线程池,线程池将在空闲的时候执行Runnable对象所代表的任务。Future对象代表Runnable任务的返回值,然后run()方法并没有返回值,所以Future对象将在run()方法执行结束返回null。但可以调用Future的isDone(),isCanceled()方法来获取Runnable对象状态。
<T> Future<T> submit(Runnable task, T result) 将一个Runnable对象提交给指定的线程池,线程池将在有空闲线程时执行Runnable对象所代表的任务。result显式得指定Future对象在run()方法执行结束后返回result。
<T> Future<T> submit(Callable<T> task) 将一个Callable对象提交给指定的线程池,线程池将在有空闲时执行Callable对象所代表的任务提交给线程池,线程池将在空闲的时候执行Callable代表的任务。Future代表Callable对象里Call()方法的返回值。
用完一个线程池后,应该调用线程池的shutdown()方法,该方法将启动线程池的关闭序列,调用shutdown()方法后的线程池将不再接受新任务,但会将以前所有已提交任务执行完。
线程池来执行任务的步骤:
1)调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
2)创建Runnable实现类或Callable实现类的实例,作为线程执行任务。
3)调用ExecutorService对象的submit()方法来提交Runnable实例或者Callable实例
4)当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池。
1 public class ThreadPoolTest { 2 public static void main(String[] args) throws ExecutionException, InterruptedException { 3 ExecutorService executorService = Executors.newFixedThreadPool(2); 4 AtomicInteger counts = new AtomicInteger(); 5 FutureTask<Integer> task = new FutureTask<>(() -> { 6 for (int i = 0; i < 100; i++) { 7 counts.getAndIncrement(); 8 System.out.println(Thread.currentThread().getName() + "--" + i); 9 System.out.println(counts); 10 } 11 return counts.get(); 12 }); 13 executorService.submit(task); 14 System.out.println(task.get()); 15 if (task.isDone()){ 16 executorService.submit(task); 17 System.out.println(task.isDone()); 18 } 19 executorService.shutdown(); 20 } 21 }