多线程

线程基础

线程的状态

  Java线程在运行的声明周期中可能会处于6种不同的状态,如下:

  • New:新创建状态。线程被创建,还没有调用start方法,在线程运行之前还有一些基础工作要做;
  • Runnable:可运行状态。一旦调用start方法,线程就处于Runnable状态。一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间;
  • Blocked:阻塞状态。表示线程被锁阻塞,它暂时不活动;
  • Waiting:等待状态。线程暂时不活动,并且不运行任何代码,这消耗最少的资源,直到线程调度器重新激活它;
  • Timed waiting:超时等待状态。和等待状态不同的是,它是可以在指定的时间自行返回的;
  • Terminated:终止状态。表示当前线程已经执行完毕。导致线程终止有两种情况:第一种就是run方法执行完毕正常退出;第二种就是因为一个没有捕获的异常而终止了run方法,导致线程进入终止状态;

  线程创建后,调用Thread的start方法,开始进入运行状态,当线程执行wait方法后,线程进入等待状态,进入等待状态的线程需要其他线程通知才能返回运行状态。超时等待相当于在等待状态加上了时间限制,如果超过时间限制,则线程返回运行状态。当线程调用到同步方法时,如果线程没有获得锁则进入阻塞状态,当阻塞状态的线程获取到锁时则重新回到运行状态。当线程执行完毕或者遇到意外异常终止时,都会进入终止状态。

创建线程

  线程的实现一般有以下三种方法,其中前两种最常见:

  • 继承Thread类,重写run()方法
      Thread本质上也是实现了Runnable接口的一个实例。需要注意的是调用start()方法后并不是立即执行线程的代码,而是使该线程变为可运行态,什么时候运行线程代码是由操作系统决定的,以下是其主要步骤:
    1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程要完成的任务。因此,run()方法被称为执行体;
    2. 创建Thread子类的实例,即创建了线程对象;
    3. 调用线程对象的start()方法来启动该线程;
public class DemoThread extends Thread{
    private final String TAG = DemoThread.class.getSimpleName();
    
    public void run(){
        Log.e(TAG,"执行了run()方法");
    }
    
    public static void main(String[] args){
        Thread thread = new DemoThread();
        thread.start();
    }
}
  • 实现Runnable接口,并实现该接口的run()方法
      以下是其主要步骤:
    1. 自定义类并实现Runnable接口,实现run()方法;
    2. 创建Thread子类的实例,用实现Runnable接口的对象作为参数实例化该Thread对象;
    3. 调用Thread的start()方法来启动该线程;
public class DemoRunnable implements Runnable{
    private final String TAG = DemoRunnable.class.getSimpleName();
    
    public void run(){
        Log.e(TAG,"执行了run()方法");
    }
}

public class DemoClass{
    public static void main(String[] args){
        DemoRunnable runnable = new DemoRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
  • 实现Callable接口,重写call()方法
      Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要表现为以下三点:
    1. Callable可以在任务接受后提供一个返回值,Runnable无法提供这个功能;
    2. Callable中的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常;
    3. 运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下就可以使用Future来监视目标线程调用call()方法的情况。但调用Future的get()方法以获取结果时,当前线程就会阻塞,直到call()方法返回结果;
public class DemoCallable{
    public static class MyDemoCallable implements Callable{
        public String call() throws Exception{
            return "this is result";
        }
    }
    
    public static void main(String[] args){
        MyDemoCallable myDemoCallable = new MyDemoCallable();
        ExecutorService executorService = ExecutorService.newSingleThread();
        Future future = executorService.submit(myDemoCallable);
        try{
            System.out.println(future.get());
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

  在这三种方式中,推荐使用实现Runnable接口的方式,其原因是,一个类应该在其需要加强或者修改时才会被继承。

中断

  当线程的run方法

同步

  同步一直是Java多线程的难点。在多线程应用中,两个或者多个以上的线程需要共享对同一个数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用了修改该对象的方法,这种情况通常被称为竞争条件。比如:车站卖车票,车票是一定的,但是卖车票的窗口好多个,每个窗口就相当于一个线程。这么多的线程共用所有的车票资源,如果不使用同步是无法保证其原子性的。在一个时间点上,两个线程同时使用车票资源,那么取出来的车票是一样的(座位号一样),这样就会给乘客造成麻烦。解决方法如下:当一个线程使用车票资源时,我们就交给它一把锁,等它把事情做完后再把锁给另一个要用这个资源的线程。这样就不会出现上面取出一样的车票的情况了。

重入锁与条件对象

阻塞队列

线程池

AsyncTask的原理

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-----------------last line for now-------------------