android多线程

2021-03-02  本文已影响0人  Johnson_Coding
与进程区别:进程是资源分配的基本单位。线程是cpu调度的基本单元,线程不拥有资源,线程可以访问隶属进程的资源。
同一个进程中可以有多个线程,它们共享进程资源,线程的切换不会引起进程切换。
而从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
进程创建或撤销开销比线程大。
线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。

多线程可以提高程序并发执行性能,创建线程有继承Thread重写run方法和实现runnable接口重写run方法,实现Callable接口方式重写call方法还能指定返回值。两者区别是尽量使用runnable,因为他相比thread
1):可以避免java中的单继承的限制,增加程序健壮性。
2newthread更强调将对象划分多个资源,每个thread执行自己的那部分资源,而runnable是资源是同一份多个线程异步执行。android也提供了3套封装好的轻量级异步类:AsyncTask,handlerThread和intentService。

AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后会把执行的进度和最终结果传递给主线程并更新UI。AsyncTask本身是一个抽象类它提供了Params、Progress、Result 三个泛型参数,其类声明如下:
public abstract class AsyncTask<Params, Progress, Result> {}

由类声明可以看出AsyncTask抽象类确实定义了三种泛型类型 Params,Progress和Result,它们分别含义如下:
Params :启动任务执行的输入参数,如HTTP请求的URL
Progress : 后台任务执行的百分比
Result :后台执行任务最终返回的结果类型

如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替。好~,我们现在创建一个类继承自AsyncTask如下:

public class DownLoadAsyncTask extends AsyncTask<String,Integer,Bitmap> {

    /**
     * onPreExecute是可以选择性覆写的方法
     * 在主线程中执行,在异步任务执行之前,该方法将会被调用
     * 一般用来在执行后台任务前对UI做一些标记和准备工作,
     * 如在界面上显示一个进度条。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 抽象方法必须覆写,执行异步任务的方法
     * @param params
     * @return
     */
    @Override
    protected Bitmap doInBackground(String... params) {
        return null;
    }

    /**
     * onProgressUpdate是可以选择性覆写的方法
     * 在主线程中执行,当后台任务的执行进度发生改变时,
     * 当然我们必须在doInBackground方法中调用publishProgress()
     * 来设置进度变化的值
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    /**
     * onPostExecute是可以选择性覆写的方法
     * 在主线程中执行,在异步任务执行完成后,此方法会被调用
     * 一般用于更新UI或其他必须在主线程执行的操作,传递参数bitmap为
     * doInBackground方法中的返回值
     * @param bitmap
     */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
    }

    /**
     * onCancelled是可以选择性覆写的方法
     * 在主线程中,当异步任务被取消时,该方法将被调用,
     * 要注意的是这个时onPostExecute将不会被执行
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

二、HandlerThread源码解析

HandlerThread的源码不多只有140多行,那就一步一步来分析吧,先来看看其构造函数

/**
 * Handy class for starting a new thread that has a looper. The looper can then be 
 * used to create handler classes. Note that start() must still be called.
 */
public class HandlerThread extends Thread {
    int mPriority;//线程优先级
    int mTid = -1;
    Looper mLooper;//当前线程持有的Looper对象
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }

从源码可以看出HandlerThread继续自Thread,构造函数的传递参数有两个,一个是name指的是线程的名称,一个是priority指的是线程优先级,我们根据需要调用即可。其中成员变量mLooper就是HandlerThread自己持有的Looper对象。onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,但是注意重写时机是在Looper循环启动前,再看看run方法:

@Override
public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll(); //唤醒等待线程
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
   }

前面我们在HandlerThread的常规使用中分析过,在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了Looper.prepare()代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程(也就是当前异步线程),这样我们才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。接着将执行代码:

synchronized (this) {
       mLooper = Looper.myLooper();
       notifyAll(); //唤醒等待线程
   }

这里在Looper对象创建后将其赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程,最后执行Looper.loop();代码,开启looper循环语句。那这里为什么要唤醒等待线程呢?我们来看看,getLooper方法

public Looper getLooper() {
 //先判断当前线程是否启动了
   if (!isAlive()) {
       return null;
   }
   // If the thread has been started, wait until the looper has been created.
   synchronized (this) {
       while (isAlive() && mLooper == null) {
           try {
               wait();//等待唤醒
           } catch (InterruptedException e) {
           }
       }
   }
   return mLooper;
}

事实上可以看出外部在通过getLooper方法获取looper对象时会先先判断当前线程是否启动了,如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程,最后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程中执行的,而调用getLooper方法则是在主线程进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,HandlerThread内部则通过等待唤醒机制解决了同步问题。

public boolean quit() {  
       Looper looper = getLooper();  
       if (looper != null) {  
           looper.quit();  
           return true;  
       }  
       return false;  
   }  
public boolean quitSafely() {  
    Looper looper = getLooper();  
    if (looper != null) {  
           looper.quitSafely();  
           return true;  
       }  
       return false;  
   }  

从源码可以看出当我们调用quit方法时,其内部实际上是调用Looper的quit方法而最终执行的则是MessageQueue中的removeAllMessagesLocked方法(Handler消息机制知识点),该方法主要是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送)还是非延迟消息。
  当调用quitSafely方法时,其内部调用的是Looper的quitSafely方法而最终执行的是MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理完成后才停止Looper循环,quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息。最后需要注意的是Looper的quit方法是基于API 1,而Looper的quitSafely方法则是基于API 18的。

三、IntentService源码解析

我们先来看看IntentService的onCreate方法:

@Override
public void onCreate() {
   // TODO: It would be nice to have an option to hold a partial wakelock
   // during processing, and to have a static startService(Context, Intent)
   // method that would launch the service & hand off a wakelock.

   super.onCreate();
   HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
   thread.start();

   mServiceLooper = thread.getLooper();
   mServiceHandler = new ServiceHandler(mServiceLooper);
}

当第一启动IntentService时,它的onCreate方法将会被调用,其内部会去创建一个HandlerThread并启动它,接着创建一个ServiceHandler(继承Handler),传入HandlerThread的Looper对象,这样ServiceHandler就变成可以处理异步线程的执行类了(因为Looper对象与HandlerThread绑定,而HandlerThread又是一个异步线程,我们把HandlerThread持有的Looper对象传递给Handler后,ServiceHandler内部就持有异步线程的Looper,自然就可以执行异步任务了),那么IntentService是怎么启动异步任务的呢?其实IntentService启动后还会去调用onStartCommand方法,而onStartCommand方法又会去调用onStart方法,我们看看它们的源码:

@Override
public void onStart(Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

/**
 * You should not override this method for your IntentService. Instead,
 * override {@link #onHandleIntent}, which the system calls when the IntentService
 * receives a start request.
 * @see android.app.Service#onStartCommand
 */
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

从源码我们可以看出,在onStart方法中,IntentService通过mServiceHandler的sendMessage方法发送了一个消息,这个消息将会发送到HandlerThread中进行处理(因为HandlerThread持有Looper对象,所以其实是Looper从消息队列中取出消息进行处理,然后调用mServiceHandler的handleMessage方法),我们看看ServiceHandler的源码:

private final class ServiceHandler extends Handler {
   public ServiceHandler(Looper looper) {
       super(looper);
   }

   @Override
   public void handleMessage(Message msg) {
       onHandleIntent((Intent)msg.obj);
       stopSelf(msg.arg1);
   }
}

这里其实也说明onHandleIntent确实是一个异步处理方法(ServiceHandler本身就是一个异步处理的handler类),在onHandleIntent方法执行结束后,IntentService会通过 stopSelf(int startId)方法来尝试停止服务。这里采用stopSelf(int startId)而不是stopSelf()来停止服务,是因为stopSelf()会立即停止服务,而stopSelf(int startId)会等待所有消息都处理完后才终止服务。最后看看onHandleIntent方法的声明:

protected abstract void onHandleIntent(Intent intent);

到此我们就知道了IntentService的onHandleIntent方法是一个抽象方法,所以我们在创建IntentService时必须实现该方法,通过上面一系列的分析可知,onHandleIntent方法也是一个异步方法。这里要注意的是如果后台任务只有一个的话,onHandleIntent执行完,服务就会销毁,但如果后台任务有多个的话,onHandleIntent执行完最后一个任务时,服务才销毁。最后我们要知道每次执行一个后台任务就必须启动一次IntentService,而IntentService内部则是通过消息的方式发送给HandlerThread的,然后由Handler中的Looper来处理消息,而Looper是按顺序从消息队列中取任务的,也就是说IntentService的后台任务时顺序执行的,当有多个后台任务同时存在时,这些后台任务会按外部调用的顺序排队执行

总结

asyncTask 一般就是继承他重写5个方法,onPreExecute 线程执行前的准备工作,doInbackgroud执行任务中的耗时操作,onProgressUpdate在主线程中显示任务进度,onPostExcute接受线程任务执行的结果并在Ui线程显示出来,onCancell异步任务的取消操作。它内部实现原理就是2个线程池+1个handler。其中,任务队列线程池维护了一个队列用synchronized修饰保证了队列串行执行,而执行的线程池thread_pool是真正具体执行任务的,内部internalHandler用来实现异步通信和消息传递。适用大量串行多线程任务
handlerThrad 继承自thread,本质就是在子线程thread中创建了looper,主线程通过子线程的handler对象向子线程分发耗时任务,子线程处理完后会调用UiHandler把结果分发到主线程进行处理。适用少量串行多线程任务
intentService 继承自service,内部就是用了handlerThread和serviceHandler实现的,在intentService的oncreate方法中通过handlerThread开启线程来处理startService
发送过来的intent对象,而且intentService在没有新的intent请求到达时会自动停止service不用像service一样手动stop。适用与后台运行的异步任务。

线程池ThreadPoolExecutor

Executors工具类

  /**
    * 创建一个固定大小的线程池,而且全是核心线程,
    * 会一直存活,除非特别设置了核心线程的超时时间
    */
   public static ExecutorService newFixedThreadPool(int nThreads) {
       return new ThreadPoolExecutor(nThreads, nThreads,
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>());
   }

  /**
    * 创建了一个没有大小限制的线程池,全是非核心线程;如果线程
    * 空闲的时间超过60s就会被移除
    */
   public static ExecutorService newCachedThreadPool() {
       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                     60L, TimeUnit.SECONDS,
                                     new SynchronousQueue<Runnable>());
   }

  /**
    * 这个线程池只有1个唯一的核心线程
    */
   public static ExecutorService newSingleThreadExecutor() {
       return new FinalizableDelegatedExecutorService
           (new ThreadPoolExecutor(1, 1,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>()));
   }

  /**
    * 创建一个定长的线程池,可以执行周期性的任务
    */
   public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
       return new ScheduledThreadPoolExecutor(corePoolSize);
   }

可以看出这几种方式最后都是通过ThreadPoolExecutor来实现的,所以下面就来研究一下今天的主角ThreadPoolExecutor,等理解了这个类,也就可以掌握线程池等工作原理,甚至可以根据自己的策略来自定义线程池。ThreadPoolExecutor继承与抽象方法AbstractExecutorService,也就间接实现了ExecutorService、Executor等接口。从构造方法谈起:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

AbortPolicy:直接抛出异常,这是默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;

execute(关键方法)

这是线程池的关键方法,用来提交任务的。

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //表示 “线程池状态” 和 “线程数量” 的整数
        int c = ctl.get();
        /*
         * 如果当前活跃线程数小于核心线程数,就会添加一个worker来执行任务;
         * 具体来说,新建一个核心线程放入线程池中,并把任务添加到该线程中。
         */
        if (workerCountOf(c) < corePoolSize) {
            /*
             * addWorker()如果返回true表示添加成功,线程池会执行这个任务,那么本方法可以结束了,返回 false 代表线程池不允许提交任务,那么就会执行后面的方法。
             */
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

      //程序执行到这里,说明要么活跃线程数大于核心线程数;要么addWorker()失败

        /*
         * 如果当前线程池是运行状态,会把任务添加到队列
         */
        if (isRunning(c) && workQueue.offer(command)) {
            /*
            *这里的逻辑比较有意思,又重新检查了线程状态和数量;
            *如果线程不处于 RUNNING 状态,就会移除刚才添加到队列中的任务;
            *如果线程池还是 RUNNING 状态,并且线程数为 0,那么开启新的线程;
            * addWorker(null, false)参数分析:
            * 1\. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
            * 2\. 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize,
            * 添加线程时根据maximumPoolSize来判断; 
          */

            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }

 //程序执行到这里,说明要么线程状态不是RUNNING;要么workQueue队列已经满了

         /*
          * 这时,再调用addWorker方法去创建线程,
          * 会把线程池的线程 数量的上限设置为maximum;
          * 如果失败,说明当前线程数已经达到 maximumPoolSize,执行拒绝策略
          */
        else if (!addWorker(command, false))
            reject(command);
    }

为什么当任务添加到队列后,内部还执行了那么复杂的判断?

因为担心任务提交到队列中了,但是线程池却关闭了。

当执行execute方法提交一个任务的时候,如果线程池一直处于RUNNING状态,那流程如下:

注意:
addWorker(null, false);也是创建一个线程,但并没有传入任务,因为任务已经被添加到workQueue中了,当worker在执行的时候,会直接从workQueue中获取任务。在workerCountOf(recheck) == 0时执行addWorker(null, false);也是为了保证线程池在RUNNING状态下必须要有一个线程来执行任务。

关于Worker类和addWorker方法

addWorker()是尝试在线程池中创建一个线程并执行任务,firstTask表示作为新创建的线程的第一个任务,core参数为true的时候,会用核心线程数做创建线程的边界;如果为false,会用最大线程数maximumPoolSize做为边界。如果addWorker()返回true,表示创建线程成功

private boolean addWorker(Runnable firstTask, boolean core) {}

image

业务场景(分析2种常用的线程池)

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

固定大小的线程池。最大线程数与核心线程数相等,keepAliveTime的设置无效,因为核心线程默认不会销毁,阻塞队列为LinkedBlockingQueue,它是无界队列。这种线程池适合CPU密集型任务.

关于CPU密集型任务和IO密集型任务可以参考这篇文章
线程池核心线程数多少最为合适(IO密集型和CPU密集型)?

工作流程:

虽然线程数量是固定的,但是由于使用了无界队列LinkedBlockingQueue,如果线程的并发量比较大,任务的执行时间比较长,那还是可能会OOM的。适用于CPU密集型的任务,也就是那种长期的任务。

newCachedThreadPool

   public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

核心线程数为 0,最大线程数为 Integer.MAX_VALUE,所有线程空闲时间 为 60 秒,任务队列采用 SynchronousQueue。

用它去处理那种并发量很大的任务就不合适,由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。适用于那种任务可以快速完成的任务。

总结

开辟了一块缓存了一定线程数量的区域,可以统一分配、调度、监控每个线程和控制线程池最大并发数,而且可以复用线程,不需要每次都重新创建销毁线程对象带来的资源消耗,减少,因为统一管控线程所以很好避免阻塞情况。java提供了4种线程池,

定长线程池(FixedThreadPool):只包含核心线程而且一直存活。适用需要控制线程最大并发数的场景。
定时线程池(ScheduledThreadPool ):包含核心和非核心线程,非核心闲置会马上回收,适用定制、周期性任务
可缓存线程池(CachedThreadPool)只包含非核心线程,数量无限制,可以灵活回收,适用执行大量但耗时少的任务
单线程化线程池(SingleThreadExecutor) 只有一个核心线程,不会被回收,适用不适合并发但可能引起IO阻塞和UI线程响应操作,如数据库操作,文件操作。

synchronized关键字

是java中的关键字,保证同一时刻只有一个线程执行被synchronized修饰的方法/代码块,保证了线程安全。他自身原理是依赖底层监视器锁monitor,而monitor又依赖底层操作系统的互斥锁实现的。它有原子性(这个操作要么全执行要么都不执行不可中断),可见性(属性值对其他线程可见),有序性(程序执行的顺序按照代码先后执行),可重入(同一锁程中,线程不需要再次获取同一把锁)的特点。它可以锁:

普通同步方法:此时锁的是当前对象
静态同步方法:此时锁的是整个类,这个类的所有对象持有同一把锁
代码块:此时锁的括号里的对象
整个类:此时锁的是整个类,这个类的所有对象持有同一把锁

锁优化(偏向锁(乐观锁),轻量级锁(乐观锁),重量级锁(悲观锁))jdk1.6改动

一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时对象持有偏向锁偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用【CAS】操作,将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。CAS存在ABA问题,解决方案就是加个版本进行区别。一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋【自旋锁】超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

volatile关键字

当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量回写到系统主存中。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。所以,如果一个变量被volatile所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile在并发编程中,其值在多个缓存中是可见的。只保证可见性有序性(禁止指令重排序),不保证原子性(i++,i--操作)

synchronized和volatile,reentrantLock区别

volatile本质是将每个线程缓存值强刷到主存中,每个线程会将内存值加载到自己缓存中; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化,因为它禁止重排序;synchronized标记的变量可以被编译器优化

synchronized和ReentrantLock都是可重入锁
Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,后者可以阅读源码。
jdk1.6对synchronized优化之前比reentrantLoack性能差很多,但优化后性能差别不大,而且优化借鉴了reentrantLock的CAS机制。官方甚至建议使用synchronized。因为Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。
ReentrantLock支持非阻塞的方式获取锁,能够响应中断,而synchronized不行
ReentrantLock可以是公平锁或者非公平锁,而synchronized只能是非公平锁。
ReenTrantLock独有的能力:(只有此时才用reentrantLoack)
1.ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。2. ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。3. ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。

死锁

死锁就是俩个或者俩个以上的线程阻塞着,并且都在等待对方释放持有的锁。

产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁:破坏4个必要条件之一

threadLocal

是线程的局部变量,为每个线程提供一个存储空间,保持该线程所独有的资源,防止线程间数据资源共享。每个线程里面有个threadLocalMap维护,把当前线程的threadLocal实例作为key,保证了数据安全。handler机制中looper就是threadlocal保存

上一篇下一篇

猜你喜欢

热点阅读