精华内容
下载资源
问答
  • Android线程同步

    2020-07-01 15:43:18
    序言:Android开发,对高并发等并没有太高的要求,所以本文介绍主要针对安卓开发过程中的同步问题。 目录: 1.synchronized(重点讲解); 2.Lock锁; 3.CountDownLatch和Semaphore; 4.java.util.concurrent包下面...

    序言:Android开发,对高并发等并没有太高的要求,所以本文介绍主要针对安卓开发过程中的同步问题。

    目录:
    1.synchronized(重点讲解);
    2.Lock锁;
    3.CountDownLatch和Semaphore;
    4.java.util.concurrent包下面的集合;
    5.开发过程中遇到的问题

    一、.synchronized原理及使用
    使用:
    1.用于同步方法:

    // 同步普通方法
        public synchronized String getBusNumber(){
            return "";
        }
    
        // 同步静态方法
        public synchronized static String getBusTime(){
            return "";
        }
    
        // 用于同步代码块
        public String getBusColor(){
            synchronized (this){
    
            }
            return "";
        }
    
    

    关于synchronized的使用,刚接触时经常会遇到同步不了的情况,这里要注意所谓的synchronized是针对对象而言,比如说同步的是对象还是类对象。**静态方法是属于类的,所以修饰静态方法时是同步的类对象。同步的本质就是要是多个线程执行的同步方法是一个对象。下面从原理来分析:
    ** 2.实现原理:

    synchronized是针对于对象(Object)来实现的,每个Object都有一个monitor(监视器,底层实现的),我们把问题抽象出来,这样理解:
    现在A公司准备招聘(被synchronized的Object),有甲、乙、丙三个面试人员(这里相当于有3个线程要),进行面试的人要获得进入面试房间的唯一一把钥匙(要获取Object的monitor),当甲先拿到钥匙进入房间后,此时乙和丙也来了(有一个线程先获得了monitor进入同步方法或同步代码块),这个时候只能等甲面试结束,放出钥匙才能进入房间面试(后面来的线程进入block状态,会把这些线程放入Object的等待对列中)。如果这个时候甲面试完成,面试官感觉还不错,想再考虑一下,此时让甲先别走,等待结果(该线程执行Object的waite()方法),此时甲释放出钥匙(该线程交出monitor),在等待队列的乙和丙此时抢占模式,先拿到钥匙的进入房间面试(拿到monitor),假定进入房间的是乙,此时乙在面试过程中如果让面试官认为是否可以录用在隔壁等候结果的甲(及此时第二个线程调用Object的notify()方法,唤醒在等结果队列的线程),此时就可以决定让甲离开(线程进入Runable状态,等待CPU分配时间片进入运行状态),或者乙也和甲一样进入等待结果(该线程执行Object的waite()方法,释放monitor进入等待状态)…
    总结一下:Object持有一个monitor。Object还有两个集合,一个是存放阻塞的线程,另一个是存放执行了waite()方法等待唤醒的形成。执行synchronized(Object)需要获取该Object的monitor,没有获取到monitor的线程状态变为BLOCKED,被加入Object阻塞的集合里面。获取到monitor的线程,如果该线程执行了Object的waite()方法,该线程状态变为WAITING被加入等待集合里面,并释放monitor。只有当其他线程获取到该Object的monitor并调用Object的notify()或者notifyAll()时,会把Object的等待集合里面的线程从WAITING变为RUNNABLE,并从该集合移除该线程(notifyAll针对时集合里面的所有线程)。
    这里说一下线程的几种状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED。具体的解释可以看Thread的源码。
    就Android开发而言,synchronized比较适合,并发量没有Java后台那么大,而且维护很简单,不用担心死锁之类的问题(因为在编译的时候会把synchronized修饰的代码块和方法前面和结尾增加获取和释放monitor的指令)

    二、Lock锁
    这里网上讲Lock的比较多,android开发中用的场景不多。简单介绍一下Lock和synchronized的区别:
    优点:并发量大的时候,Lock在性能上有优势,synchronized性能消耗较大;
    缺点:Lock需要手动维护,处理不适当会造成死锁;

    三、CountDownLatch和Semaphore;
    1.CountDownLatch,可以看做一个倒计时器,具有原子性,适合多线程协调完成某项工作;但是CountDownLatch只能倒计时,倒计时到0时,不能重用。
    用法:

    private void startThreads(){
            // 等待任务执行完成
            new Thread(new Runnable() {
                @Override
                public void run()
                {
                    try
                    {
                        countDownLatch.await();
                        System.out.println("------任务执行完了-----");
                    } catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
            }).start();
            for (int i = 0; i < 5; i ++){
                new Thread("第" + i + "线程"){
                    @Override
                    public void run()
                    {
                        super.run();
                        System.out.println("-----正在执行任务------");
                        countDownLatch.countDown();
                    }
                }.start();
    
            }
        }
    
    **2.Semaphore信号量
    Semaphore其实就是一个原子计数器,也可以用信号量Semaphore来做同步。**
    
     Semaphore semaphore = new Semaphore(1);
        private void doSemaphore(){
            try
            {
                semaphore.acquire();
                System.out.println("----我现在可以快乐的玩耍拉---------");
                semaphore.release();
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    

    把Semaphore 初始值设为1,当有线程执行方法时先acquire(),获取到之后Semaphore 的值变为了0,当此时其他线程也执行该方法时,就只能等Semaphore 的值大于0,及需要执行semaphore.release();

    四、java.util.concurrent包下面的集合
    在生产者消费者模型解决同步问题时,简单的话可以使用Java已经实现的同步机制的集合等,在java.util.concurrent包下面,之前看实现原理主要是CAS算法来实现同步,执行效率还可以(但也不能滥用,要看场景,毕竟性能比非同步的集合等要高)里面提供原子操作的类,如下图
    在这里插入图片描述

    五、开发过程中遇到的奇葩问题
    之前在开发中使用RxJava里面线程来执行对文件的操作,比如把内容保存到指定文件。通过File.exsit()方法来判断文件是否存在,写个伪代码:

     public synchronized void saveToFile(String filePath, String content){
            File file = new File(filePath);
            if (file.exists()) return;
            // 因为之前保存的数据耗时较长,模拟耗时的操作
            try
            {
                Thread.sleep(1000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            
            // 执行保存内容
            writeToFile(filePath, content);
            
            // 对于复用线程,比如线程池时,必须要加上
            file = null;
        }
    

    如果不加上file = null的话,文件保存进去了,file.exists())依然会是false。反复排除synchronized 用的也没啥问题,为什么会这样呢?后来把线程id打出来,发现是同一个线程执行file.exists()会false。个人理解是线程内部的ThreadLocal会缓存成员变量file,如果不置null的吧,会认为是一个对象,从而不会去磁盘里面看文件是否存在。

    展开全文
  • Android/Java线程同步

    2015-06-17 20:57:21
    刚好之前面试被问到这个问题,而Android线程同步似乎使用的多一些。整理一些查找到的资料,进行学习一下。 一、什么是线程同步 在使用多线程访问同一数据的时候,非常容易出现线程安全问题。 在多线程编程里面,...

    以前做JAVA的时候,一直使用的都是各种框架,线程方面的问题很少碰到,这个方面也一直是自己的弱项。

    刚好之前面试被问到这个问题,而Android中线程同步似乎使用的多一些。整理一些查找到的资料,进行学习一下。


    一、什么是线程同步

    在使用多线程访问同一数据的时候,非常容易出现线程安全问题。

    在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

    例如火车票售票系统中出票情况,多个站点都进行同一车次的销售,如果同时操作同一张车票就会导致数据不一致。所以需要线程同步解决这些问题。


    二、JAVA线程同步

    在Java线程同步中需要使用synchronized关键字进行。

    下例来自百度百科:

    classMyTest{
      private static final Object lock = new Object();
    
    public static synchronized void test(){
      //同步的方法
      }
     
      public void test2(){
    synchronized(lock){
        //方法级同步,也可以使用this实现对象级同步
        }
      }
    }
    红色字体部分就是同步代码块,绿色字体部分就是同步方法,二者是等价的。

    当一个线程获得资源锁后就可以执行同步的内容了。

    只有这个线程释放资源锁,其他线程才能对这个资源进行修改。

    但是为了控制同步粒度,同步的代码段越小越好,所以应当避免用synchronized修饰方法。

    Android线程同步直接使用Java线程同步即可。


    这篇文章的目的主要是为了能够对线程同步能有一个概念性的理解,具体的线程同步的示例网上有很多,这里就不贴代码了。


    展开全文
  • 8.5.3 线程同步案例 图8-4 多线程同步实现计算值 Android移动应用开发实用教程 8 Android多线程 第 章 本章内容 8.1 Android线程简介 8.2 循环者消息Looper-Message机制 8.3 AsyncTask类 8.4 Android其它创建多线程...
  • Android线程同步总结

    2017-06-16 11:06:57
    线程同步

    原创 https://segmentfault.com/a/1190000009577683

    场景一:假如APP需要访问两个接口得到数据,在两个接口数据返回时再进行操作下一步。

    主线程等待两个子线程完成以后继续,但是主线程等待不是卡机了啊….?

    解决方案:三个线程,两个线程同时执行,一个线程等待,先完成的等待未完成的一个,直到都完成了才执行等待的线程,

    代码

     final int count = 2;
            final CountDownLatch countDownLatch = new CountDownLatch(count);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.e("small", "我是线程3,我在等待");
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.e("small", "他们都完成了,执行了线程3");
                }
            }).start();
            for (int i = 0; i < 2; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("small", "我是线程" + Thread.currentThread().getName());
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Log.e("small", "线程" + Thread.currentThread().getName() + "完成了");
                        countDownLatch.countDown();
                    }
                }).start();

    执行结果
    这里写图片描述

    CountDownLatch其实它就是一个计数器,await()是等待计时器为0后才执行下去,countDown()是使计数器减一。

    场景二:假如产品拿着刀来说,我要请求刚才两个接口,请求完后得到两个接口数据,再一起执行不同的操作。

    麻蛋,上面不是说了吗,我tm开四个线程,两个请求两个等待不就行了?呵呵哒,天真烂漫,上面所说的计数器作用效果只有一次,那就是说只能用于一个线程里面。我能怎么办,我也很绝望啊。来来来,装逼的时候又到了。

     final int count = 2;
            final CyclicBarrier cyclicBarrier = new CyclicBarrier(count);
            for (int i = 0; i < count; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("small", "线程" + Thread.currentThread().getName() + "执行");
                        try {
                            Log.e("small", "线程" + Thread.currentThread().getName() + "请求数据");
                            Thread.sleep(3000);
                            Log.e("small", Thread.currentThread().getName() + "请求数据成功");
                            cyclicBarrier.await();
                        } catch (InterruptedException E) {
                            E.printStackTrace();
                        } catch (BrokenBarrierException e) {
                            e.printStackTrace();
                        }
                        Log.e("small", "线程" + Thread.currentThread().getName() + "继续执行");
                    }
                }).start();

    又是很简单的代码
    动图完事
    这里写图片描述
    1. CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
    2. CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。
    3. CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

    场景三:面试官问你,怎么让两个线程依次执行?

    是不是脱口而出在一个线程执行完后在线程里再开一个线程~,~,你看面试官会不会打死你。这更加简单,直接上代码吧。

    这里写图片描述

    搞掂
    情景四:小明打了老王一巴掌,老王打回他,小明又打老王一巴掌,循环如此~,~

    我勒个擦,这么奇葩。还好,上有政策下游对策。

    final Object object = new Object();

    final Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (i < 5) {
                    synchronized (object) {
                        try {
                            System.out.println("小明打了老王一巴掌");
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        i++;
                    }
                }
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    int i = 0;
                    while (i < 5) {
                        synchronized (object) {
                            System.out.println("老王打了小明一巴掌");
                            object.notify();
                            i++;
                        }
                        Thread.sleep(1500);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        threadA.start();
        threadB.start();

    这里写图片描述

    CountDownLatch 和CyclicBarrier的不同

    CountDownLatch 适用于一组线程和另一个主线程之间的工作协作。一个主线程等待一组工作线程的任务完毕才继续它的执行是使用 CountDownLatch 的主要场景;CyclicBarrier 用于一组或几组线程,比如一组线程需要在一个时间点上达成一致,例如同时开始一个工作。另外,CyclicBarrier 的循环特性和构造函数所接受的 Runnable 参数也是 CountDownLatch 所不具备的。

    展开全文
  • Android线程面试总结

    2020-11-03 07:54:02
    和你一起终身学习,这里是程序员Android1.什么是线程线程就是进程中运行的多个子任务,是操作系统调用的最小单元2.线程的状态1.New:新建状态,new出来,还没有调用start2....

    和你一起终身学习,这里是程序员Android

    1.什么是线程

    线程就是进程中运行的多个子任务,是操作系统调用的最小单元

    2.线程的状态

    1.New:

    新建状态,new出来,还没有调用start

    2.Runnable:

    可运行状态,调用start进入可运行状态,可能运行也可能没有运行,取决于操作系统的调度

    3.Blocked:

    阻塞状态,被锁阻塞,暂时不活动,阻塞状态是线程阻塞在进入

    4.synchronized:

    关键字修饰的方法或代码块(获取锁)时的状态。

    5.Waiting:

    等待状态,不活动,不运行任何代码,等待线程调度器调度,waitsleep用来暂停当前线程的执行,以毫秒为单位,任何其它线程都可以中断当前线程的睡眠,这种情况下将抛出InterruptedException异常

    6.Timed Waiting:

    超时等待,在指定时间自行返回

    7.Terminated:

    终止状态,包括正常终止和异常终止

    3.线程的创建

    线程创建的常用方法
    • 1.继承Thread重写run方法

    • 2.实现Runnable重写run方法

    • 3.实现Callable重写call方法

    实现Callable重写call方法

    实现Callable和实现Runnable类似,但是功能更强大,具体表现在

    • a.可以在任务结束后提供一个返回值,Runnable不行

    • b.call方法可以抛出异常,Runnablerun方法不行

    • c.可以通过运行Callable得到的Fulture对象监听目标线程调用call方法的结果,得到返回值,(fulture.get(),调用后会阻塞,直到获取到返回值)

    4.线程中断

    一般情况下,线程不执行完任务不会退出,但是在有些场景下,我们需要手动控制线程中断结束任务,Java中有提供线程中断机制相关的Api,每个线程都一个状态位用于标识当前线程对象是否是中断状态

    
    public boolean isInterrupted() //判断中断标识位是否是true,不会改变标识位
    public void interrupt()  //将中断标识位设置为true
    public static boolean interrupted() //判断当前线程是否被中断,并且该方法调用结束的时候会清空中断标识位
    
    

    需要注意的是interrupt()方法并不会真的中断线程,它只是将中断标识位设置为true,具体是否要中断由程序来判断,如下,只要线程中断标识位为false,也就是没有中断就一直执行线程方法

    new Thread(new Runnable(){
          while(!Thread.currentThread().isInterrupted()){
                  //执行线程方法
          }
    }).start();
    

    前边我们提到了线程的六种状态,New 、Runnable、 Blocked、 Waiting、 Timed Waiting、 Terminated,那么在这六种状态下调用线程中断的代码会怎样呢,NewTerminated状态下,线程不会理会线程中断的请求,既不会设置标记位,在RunnableBlocked状态下调用interrupt会将标志位设置位true,在WaitingTimed Waiting状态下会发生InterruptedException异常,针对这个异常我们如何处理?

    • 1.在catch语句中通过interrupt设置中断状态,因为发生中断异常时,中断标志位会被复位,我们需要重新将中断标志位设置为true,这样外界可以通过这个状态判断是否需要中断线程

    try{
        ....
    }catch(InterruptedException e){
        Thread.currentThread().interrupt();
    }
    
    • 2.更好的做法是,不捕获异常,直接抛出给调用者处理,这样更灵活

    5.Thread为什么不能用stop方法停止线程

    SUN的官方文档可以得知,调用Thread.stop()方法是不安全的,这是因为当调用Thread.stop()方法时,会发生下面两件事:

    • 1.即刻抛出 ThreadDeath异常,在线程的run()方法内,任何一点都有可能抛出ThreadDeath Error,包括在catchfinally语句中。

    • 2.释放该线程所持有的所有的锁。调用thread.stop()后导致了该线程所持有的所有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。

    6.同步方法和同步代码块

    为何要使用同步?
    java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

    • 1.同步方法

    即有synchronized关键字修饰的方法, 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    代码如:

        public synchronized void save(){
       
        }
    

    注:synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

    • 2.同步代码块

    即有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

    代码如:

        synchronized(object){ 
        }
    

    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。
    通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

     
        /**
         * 线程同步的运用
         *
         * @author XIEHEJUN
         *
         */
        public class SynchronizedThread {
     
            class Bank {
     
                private int account = 100;
     
                public int getAccount() {
                    return account;
                }
     
                /**
                 * 用同步方法实现
                 *
                 * @param money
                 */
                public synchronized void save(int money) {
                    account += money;
                }
     
                /**
                 * 用同步代码块实现
                 *
                 * @param money
                 */
                public void save1(int money) {
                    synchronized (this) {
                        account += money;
                    }
                }
            }
     
            class NewThread implements Runnable {
                private Bank bank;
     
                public NewThread(Bank bank) {
                    this.bank = bank;
                }
     
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        // bank.save1(10);
                        bank.save(10);
                        System.out.println(i + "账户余额为:" + bank.getAccount());
                    }
                }
     
            }
     
            /**
             * 建立线程,调用内部类
             */
            public void useThread() {
                Bank bank = new Bank();
                NewThread new_thread = new NewThread(bank);
                System.out.println("线程1");
                Thread thread1 = new Thread(new_thread);
                thread1.start();
                System.out.println("线程2");
                Thread thread2 = new Thread(new_thread);
                thread2.start();
            }
     
            public static void main(String[] args) {
                SynchronizedThread st = new SynchronizedThread();
                st.useThread();
            }
     
        }
    
    • 3.使用特殊域变量(volatile)实现线程同步

      a.volatile关键字为域变量的访问提供了一种免锁机制,
      b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
      c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
      d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

    例如:
    在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。

          //只给出要修改的代码,其余代码与上同
            class Bank {
                //需要同步的变量加上volatile
                private volatile int account = 100;
     
                public int getAccount() {
                    return account;
                }
                //这里不再需要synchronized
                public void save(int money) {
                    account += money;
                }
            }
    
    

    注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。用final域,有锁保护的域和volatile域可以避免非同步的问题。

    • 4.使用重入锁实现线程同步

    JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
    ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力

    ReenreantLock类的常用方法有:

    ReentrantLock() :

    创建一个ReentrantLock实例

    lock() :

    获得锁

    unlock() :

    释放锁
    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

    例如:
    在上面例子的基础上,改写后的代码为:

    //只给出要修改的代码,其余代码与上同
            class Bank {
                
                private int account = 100;
                //需要声明这个锁
                private Lock lock = new ReentrantLock();
                public int getAccount() {
                    return account;
                }
                //这里不再需要synchronized
                public void save(int money) {
                    lock.lock();
                    try{
                        account += money;
                    }finally{
                        lock.unlock();
                    }
                    
                }
            }
    

    注:关于Lock对象和synchronized关键字的选择:

    • a.最好两个都不用,使用一种java.util.concurrent包提供的机制,
      能够帮助用户处理所有与锁相关的代码。

    • b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码

    • c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁

    • 5.使用局部变量实现线程同步

    如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

    ThreadLocal 类的常用方法

    ThreadLocal() :

    创建一个线程本地变量

    get() :

    返回此线程局部变量的当前线程副本中的值

    initialValue() :

    返回此线程局部变量的当前线程的"初始值"

    set(T value) :

    将此线程局部变量的当前线程副本中的值设置为value
    例如:
    在上面例子基础上,修改后的代码为:

    //只改Bank类,其余代码与上同
            public class Bank{
                //使用ThreadLocal类管理共享变量account
                private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                    @Override
                    protected Integer initialValue(){
                        return 100;
                    }
                };
                public void save(int money){
                    account.set(account.get()+money);
                }
                public int getAccount(){
                    return account.get();
                }
            }
    

    注:ThreadLocal与同步机制
    a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
    b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式

    7.volatile关键字

    volatile为实例域的同步访问提供了免锁机制,如果声明一个域为volatile,那么编译器和虚拟机就直到该域可能被另一个线程并发更新

    8.java内存模型

    堆内存是被所有线程共享的运行时内存区域,存在可见性的问题。线程之间共享变量存储在主存中,每个线程都有一个私有的本地内存,本地内存存储了该线程共享变量的副本(本地内存是一个抽象概念,并不真实存在),两个线程要通信的话,首先A线程把本地内存更新过的共享变量更新到主存中,然后B线程去主存中读取A线程更新过的共享变量,也就是说假设线程A执行了i = 1这行代码更新主线程变量i的值,会首先在自己的工作线程中堆变量i进行赋值,然后再写入主存当中,而不是直接写入主存

    9.原子性 可见性 有序性

    原子性:

    对基本数据类型的读取和赋值操作是原子性操作,这些操作不可被中断,是一步到位的,例如x=3是原子性操作,而y = x就不是,它包含两步:第一读取x,第二将x写入工作内存;x++也不是原子性操作,它包含三部,第一,读取x,第二,对x加1,第三,写入内存。原子性操作的类如:AtomicInteger AtomicBoolean AtomicLong AtomicReference

    可见性:

    指线程之间的可见性,既一个线程修改的状态对另一个线程是可见的。volatile修饰可以保证可见性,它会保证修改的值会立即被更新到主存,所以对其他线程是可见的,普通的共享变量不能保证可见性,因为被修改后不会立即写入主存,何时被写入主存是不确定的,所以其他线程去读取的时候可能读到的还是旧值

    有序性:

    Java中的指令重排序(包括编译器重排序和运行期重排序)可以起到优化代码的作用,但是在多线程中会影响到并发执行的正确性,使用volatile可以保证有序性,禁止指令重排
    volatile可以保证可见性 有序性,但是无法保证原子性,在某些情况下可以提供优于锁的性能和伸缩性,替代sychronized关键字简化代码,但是要严格遵循使用条件。

    10.线程池ThreadPoolExecutor

    线程池的工作原理:线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗,当一个任务提交到线程池时

    • a. 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步

    • b. 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步

    • c. 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常

    11.线程池的种类

    1.FixedThreadPool:

    可重用固定线程数的线程池,只有核心线程,没有非核心线程,核心线程不会被回收,有任务时,有空闲的核心线程就用核心线程执行,没有则加入队列排队

    2.SingleThreadExecutor:

    单线程线程池,只有一个核心线程,没有非核心线程,当任务到达时,如果没有运行线程,则创建一个线程执行,如果正在运行则加入队列等待,可以保证所有任务在一个线程中按照顺序执行,和FixedThreadPool的区别只有数量

    3.CachedThreadPool:

    按需创建的线程池,没有核心线程,非核心线程有Integer.MAX_VALUE个,每次提交
    任务如果有空闲线程则由空闲线程执行,没有空闲线程则创建新的线程执行,适用于大量的需要立即处理的并且耗时较短的任务

    4.ScheduledThreadPoolExecutor:

    继承自ThreadPoolExecutor,用于延时执行任务或定期执行任务,核心线程数固定,线程总数为Integer.MAX_VALUE

    12.线程同步机制与原理,举例说明

    为什么需要线程同步?当多个线程操作同一个变量的时候,存在这个变量何时对另一个线程可见的问题,也就是可见性。每一个线程都持有主存中变量的一个副本,当他更新这个变量时,首先更新的是自己线程中副本的变量值,然后会将这个值更新到主存中,但是是否立即更新以及更新到主存的时机是不确定的,这就导致当另一个线程操作这个变量的时候,他从主存中读取的这个变量还是旧的值,导致两个线程不同步的问题。线程同步就是为了保证多线程操作的可见性和原子性,比如我们用synchronized关键字包裹一端代码,我们希望这段代码执行完成后,对另一个线程立即可见,另一个线程再次操作的时候得到的是上一个线程更新之后的内容,还有就是保证这段代码的原子性,这段代码可能涉及到了好几部操作,我们希望这好几步的操作一次完成不会被中间打断,锁的同步机制就可以实现这一点。一般说的synchronized用来做多线程同步功能,其实synchronized只是提供多线程互斥,而对象的wait()notify()方法才提供线程的同步功能。JVM通过Monitor对象实现线程同步,当多个线程同时请求synchronized方法或块时,monitor会设置几个虚拟逻辑数据结构来管理这些多线程。新请求的线程会首先被加入到线程排队队列中,线程阻塞,当某个拥有锁的线程unlock之后,则排队队列里的线程竞争上岗(synchronized是不公平竞争锁,下面还会讲到)。如果运行的线程调用对象的wait()后就释放锁并进入wait线程集合那边,当调用对象的notify()notifyall()后,wait线程就到排队那边。这是大致的逻辑。

    13.arrayList与linkedList的读写时间复杂度

    1.ArrayList:

    ArrayList是一个泛型类,底层采用数组结构保存对象。数组结构的优点是便于对集合进行快速的随机访问,即如果需要经常根据索引位置访问集合中的对象,使用由ArrayList类实现的List集合的效率较好。数组结构的缺点是向指定索引位置插入对象和删除指定索引位置对象的速度较慢,并且插入或删除对象的索引位置越小效率越低,原因是当向指定的索引位置插入对象时,会同时将指定索引位置及之后的所有对象相应的向后移动一位。

    2.LinkedList:

    LinkedList是一个泛型类,底层是一个双向链表,所以它在执行插入和删除操作时比ArrayList更加的高效,但也因为链表的数据结构,所以在随机访问方面要比ArrayList差。

    ArrayList是线性表(数组)
    get()
    直接读取第几个下标,复杂度O(1)
    add(E)
    添加元素,直接在后面添加,复杂度O(1)
    add(index, E)
    添加元素,在第几个元素后面插入,后面的元素需要向后移动,复杂度O(n)
    remove()
    删除元素,后面的元素需要逐个移动,复杂度O(n)

    LinkedList 是链表的操作
    get()
    获取第几个元素,依次遍历,复杂度O(n)
    add(E)
    添加到末尾,复杂度O(1)
    add(index, E)
    添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度O(n)
    remove()
    删除元素,直接指针指向操作,复杂度O(1)

    14.为什么HashMap线程不安全(hash碰撞与扩容导致)

    HashMap的底层存储结构是一个Entry数组,每个Entry又是一个单链表,一旦发生Hash冲突的的时候,HashMap采用拉链法解决碰撞冲突,因为hashMapput方法不是同步的,所以他的扩容方法也不是同步的,在扩容过程中,会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组。当多个线程同时检测到hashmap需要扩容的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。扩容的时候 可能会引发链表形成环状结构

    15.进程、线程的区别

    1.地址空间:

    同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。

    2.资源拥有:

    同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。

    3.健壮性

    一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

    4.资源占用

    进程切换时,消耗的资源大,效率不高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程

    5.执行过程:

    每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

    6.线程是处理器调度的基本单位,但是进程不是。
    7.两者均可并发执行。

    16.Binder的内存拷贝过程

    相比其他的IPC通信,比如消息机制、共享内存、管道、信号量等,Binder仅需一次内存拷贝,即可让目标进程读取到更新数据,同共享内存一样相当高效,其他的IPC通信机制大多需要2次内存拷贝。

    Binder内存拷贝的原理为:
    进程ABinder客户端,在IPC调用前,需将其用户空间的数据拷贝到Binder驱动的内核空间,由于进程B在打开Binder设备(/dev/binder)时,已将Binder驱动的内核空间映射(mmap)到自己的进程空间,所以进程B可以直接看到Binder驱动内核空间的内容改动

    17.传统IPC机制的通信原理(2次内存拷贝)

    • 1.发送方进程通过系统调用(copy_from_user)将要发送的数据存拷贝到内核缓存区中。

    • 2.接收方开辟一段内存空间,内核通过系统调用(copy_to_user)将内核缓存区中的数据拷贝到接收方的内存缓存区。

    传统IPC机制存在2个问题:
    • 1.需要进行2次数据拷贝,第1次是从发送方用户空间拷贝到内核缓存区,第2次是从内核缓存区拷贝到接收方用户空间。

    • 2.接收方进程不知道事先要分配多大的空间来接收数据,可能存在空间上的浪费。

    18.Java内存模型(记住堆栈是内存分区,不是模型)

    Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成

    19.类的加载过程

    类加载过程主要包含加载、验证、准备、解析、初始化、使用、卸载七个方面,下面一一阐述。

    1.加载:

    获取定义此类的二进制字节流,生成这个类的java.lang.Class对象

    2.验证:

    保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害

    3.准备:

    准备阶段为变量分配内存并设置类变量的初始化

    4.解析:

    解析过程是将常量池内的符号引用替换成直接引用

    5.初始化:

    不同于准备阶段,本次初始化,是根据程序员通过程序制定的计划去初始化类的变量和其他资源。这些资源有static{}块,构造函数,父类的初始化等

    6.使用:

    使用过程就是根据程序定义的行为执行

    7.卸载:

    卸载由GC完成。

    20.什么情况下会触发类的初始化

    • 1、遇到new,getstatic,putstatic,invokestatic这4条指令;

    • 2、使用java.lang.reflect包的方法对类进行反射调用;

    • 3、初始化一个类的时候,如果发现其父类没有进行过初始化,则先初始化其父类(注意!如果其父类是接口的话,则不要求初始化父类);

    • 4、当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类;

    • 5、当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则先触发其类初始化;

    21.双亲委托模式

    类加载器查找class所采用的是双亲委托模式,所谓双亲委托模式就是判断该类是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后交给自身去查找

    22.双亲委托模式的好处

    • 1.避免重复加载,如果已经加载过一次Class,则不需要再次加载,而是直接读取已经加载的Class

    • 2.更加安全,确保,java核心api中定义类型不会被随意替换,比如,采用双亲委托模式可以使得系统在Java虚拟机启动时旧加载了String类,也就无法用自定义的String类来替换系统的String类,这样便可以防止核心`API库被随意篡改。

    23.死锁的产生条件,如何避免死锁

    死锁的四个必要条件

    1.互斥条件:

    一个资源每次只能被一个进程使用

    2.请求与保持条件:

    进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

    3.不可剥夺条件:

    进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

    4.循环等待条件:

    若干进程间形成首尾相接循环等待资源的关系
    这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

    避免死锁的方法:

    系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。
    在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。

    一般来说互斥条件是无法破坏的,所以在预防死锁时主要从其他三个方面入手

    (1)破坏请求和保持条件:

    在系统中不允许进程在已获得某种资源的情况下,申请其他资源,即要想出一个办法,阻止进程在持有资源的同时申请其它资源。
    方法一:在所有进程开始运行之前,必须一次性的申请其在整个运行过程中所需的全部资源,
    方法二:要求每个进程提出新的资源申请前,释放它所占有的资源

    (2)破坏不可抢占条件:

    允许对资源实行抢夺。
    方式一:如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。
    方式二:如果一个进程请求当前被另一个进程占有的资源,则操作系统可以抢占另一个进程,要求它释放资源,只有在任意两个进程的优先级都不相同的条件下,该方法才能预防死锁。

    (3)破坏循环等待条件

    对系统所有资源进行线性排序并赋予不同的序号,这样我们便可以规定进程在申请资源时必须按照序号递增的顺序进行资源的申请,当以后要申请时需检查要申请的资源的编号大于当前编号时,才能进行申请。
    利用银行家算法避免死锁:
    所谓银行家算法,是指在分配资源之前先看清楚,资源分配后是否会导致系统死锁。如果会死锁,则不分配,否则就分配。
    按照银行家算法的思想,当进程请求资源时,系统将按如下原则分配系统资源:

    24.App启动流程

    1.App启动时,AMS会检查这个应用程序所需要的进程是否存在,不存在就会请求Zygote进程启动需要的应用程序进程
    2.Zygote进程接收到AMS请求并通过fock自身创建应用程序进程,这样应用程序进程就会获取虚拟机的实例,还会创建Binder线程池(ProcessState.startThreadPool())和消息循环(ActivityThread looper.loop)
    3.然后App进程,通过Binder IPCsytem_server进程发起attachApplication请求;
    4.system_server进程在收到请求后,进行一系列准备工作后,再通过Binder IPCApp进程发送scheduleLaunchActivity请求;
    5.App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
    6.主线程在收到Message后,通过反射机制创建目标Activity,并回调Activity.onCreate()等方法。
    7.到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。

    25.Android单线程模型

    Android单线程模型的核心原则就是:
    只能在UI线程(Main Thread)中对UI进行处理。当一个程序第一次启动时,Android会同时启动一个对应的 主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事 件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线 程。在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。

    Android的单线程模型有两条原则:

    • 1.不要阻塞UI线程。

    • 2.不要在UI线程之外访问Android UI toolkit(主要是这两个包中的组件:android.widget and android.view

    26.RecyclerView在很多方面能取代ListView,Google为什么没把ListView划上一条过时的横线?

    ListView采用的是RecyclerBin的回收机制在一些轻量级的List显示时效率更高

    至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除。同时感谢您的阅读,期待您的关注。

    展开全文
  • 说到多线程的同步问题,面试多的人应该很容易被面试官问:Object的wait和notify/notifyAll如何实现线程同步? 在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入...
  • Android线程面试

    2020-03-22 15:45:10
    6、线程安全与线程同步 1.1、什么是线程安全问题? 简单地说,线程安全问题是指多个线程访问同一代码或数据,造成结果和数据的错乱。 1.2、解决的方法: (1)synchronized 关键字,保证同时刻只有一个线程进入该...
  • 相信不少人,都有在面试时被“多线程”问题卡住过吧。 今天我们就来说关于说“多线程面试问题,到底该怎么破? 1.什么是线程 线程就是进程中运行的多个子任务,是操作系统调用的最小单元。 2.线程的状态 ...
  • java Android 中关于线程同步问题

    千次阅读 2016-01-15 14:52:08
    Java 两个线程间的通信
  • 线程是操作系统的重要部分,可以提高应用程序的效率,将耗时操作放在工作...同步的实现方式有两种:synchronized,wait与notify。 Public static synchronized void test(){} Public Void test1(){synchronize
  • Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 1.什么是线程 线程就是进程中运行的多个子任务,是操作系统调用的最小单元 2.线程的状态 1.New: 新建状态,new出来,还没有调用...
  • 本系列将梳理一下多线程同步的一些经常用到方法或类,包括有CountDownLatch,CyclicBarriar,join,synchronized,wait/notify/notifyAll,Semaphore,ReentrantLock,Phaser,Future,Exchanger,concurrent等。...
  • 2019Android线程面试题总结

    千次阅读 2019-09-17 22:50:57
    极力推荐文章:欢迎收藏Android 干货分享和您一起终身学习,这里是程序员Android1.什么是线程线程就是进程中运行的多个子任务,是操作系统调用的最小单元2.线程...
  • 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android1.什么是线程线程就是进程中运行的多个子任务,是操作系统调用的最小单元2.线程的状态1.New:新建状态,new出来,还没有调用start2.Runnable:可运行状态...
  • Java多线程之线程状态转换、控制线程和线程同步
  • 自己项目中一直都是用的开源的xUtils...有**Android架构视频+BATJ面试专题PDF+核心笔记等资料。希望能够帮助到大家提升技术。如果大家想要获取的话,可以免费获取哦** [外链图片转存中…(img-W1SLB0Jy-1617455258302)]
  • Android面试线程

    千次阅读 2018-11-20 09:43:35
    Android面试线程篇,由本人整理汇总,后续将推出系列篇,如果喜欢请持续关注和推荐。 开启线程的三种方式? java有三种创建线程的方式,分别是继承Thread类、实现Runable接口和使用线程池 线程池: Android中...
  • Android 面试必备 - 线程

    千次阅读 2019-08-04 22:17:11
    最近准备更新 Android 面试必备基础知识系列,有兴趣的可以关注我的微信公众号 stormjun94,有更新时,第一时间会在微信公众号上面发布,同时,也会同步在 GitHub 上面更新,如果觉得对你有所帮助的话,请帮忙 star...
  • 线程总结: 创建线程的两种方式: 1.继承Thread类,重写Thread的run方法,因为Thread类的run方法没有内容,创建继承Thread的子类调用start方法来启动线程 2.实现Runnable接口,实现接口中的run方法,实现Runnable...
  • Android面试07-消息机制HandlerHandler的工作流程一个线程有几个Handler?一个线程有几个Looper?怎么保证?Handler内存泄漏原因?为什么其他内部类没有出现这个问题?怎么解决handler的内存泄漏?为什么主线程可以...
  • 线程同步的方法有哪些(面试题)

    千次阅读 2017-04-18 21:38:23
    线程同步:  由于同一个进程的多个线程共享同一片存储空间。在带来方便的同时,也带来了如访问冲突问题,如何解决,解决方法有几种?  第一种:  采用synchronized同步锁  第二种:  单利设计模式中的静态...
  • 继续总结多线程同步常用的方法或者类,上一节介绍了CountDownLatch,这次介绍一下它的加强版本CyclicBarriar。CyclicBarriar–循环栅栏CyclicBarriar的一个特别典型的应用场景是:有一个比较大型的任务,需要分配...
  • 前言:最近在做java基础的总结工作,决定把这些java基础中的面试高频考点写出来,方便自己复习,也方便和我一样的新手查阅;笔者之前一直在做android端的opencv图像处理项目,但是由于项目的保密性,截止到上一篇...
  • 死磕synchronized底层实现 ... 面试官没想到一个Volatile,我都能跟他扯半小时 https://juejin.cn/post/6844904149536997384 《我们一起进大厂》系列-ConcurrentHashMap & Hashtable ...《我们一起进大厂》...
  • 在电话里还问了几个技术问题(现在的猎头也懂技术了啊,好奇怪),java的同步锁什么的,这个我还真用过,也解决过这方面的死锁问题,Android里面有的类有用一个大的同步锁锁上整个对象的,就是把变量和方法都加锁了...
  • 引言 接上一篇,Java进阶 ——— Java多线程(二)之如何开启多线程 介绍了Java多线程的开启方法,但是多线程...在第一篇文章中,提到要实现多线程安全,就要实现线程同步,那么线程同步有哪些方法呢? 介绍线程同步...
  • 前言 今年的寒来得格外慢,眼看年关将近,开年就入春了,但西北季风似乎没有往年的无情。 天气和互联网行业的双重寒冷险些让我翻不过身。...好吧,现实狠狠地打了我的脸,年初面试的不断失利,让我明白了自己的不
  • Android面试题之多线程,包括线程、Java同步问题、阻塞队列、线程池、AsyncTask、HandlerThread、IntentService等内容。 本文是我一点点归纳总结的干货,但是难免有疏忽和遗漏,希望不吝赐教。 转载请注明链接...
  • android面试-Android线程和线程池

    千次阅读 2017-10-25 09:43:12
    这一部分参考Android开发艺术探索的总结,所以最好有一本书来看,里面的内容会详细很多 线程与服务的区别: 如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,842
精华内容 6,336
关键字:

安卓线程同步面试