什么是控制并发流程

控制并发流程的工具类,作用就是帮助我们更容易地让线程之间合作,让线程之间相互配合,来满足业务逻辑。比如让线程A等待线程B执行完后再执行等合作策略。

控制并发流程的工具类:

  • Semaphore

    信号量,可以通过控制“许可证”的数量,来保证线程之间的配合。

    线程只有在拿到“许可证”后才能继续运行。相比于其他的同步器,更加灵活。

  • CyclicBarrier

    线程会等待,直到足够多线程达到了事先规定的数目。一旦达到了触发条件,就可以进行下一步的动作。

    适用于线程之间相互等待处理结果就绪的场景。

  • Phaser

    和CyclicBarrier类似,但是计数可变。

    Java7加入的。

  • CountDownLatch

    和CyclicBarrier类似,数量递减到0时,触发动作。

    不可重复使用。

  • Exchanger

    让两个线程再合适时交换对象。

    适用场景:当两个线程工作在同一个类的不同实例上时,用于交换数据。

  • Condition

    可以控制线程“等待”和“唤醒”。

    是Object.wait()的升级版。

CountDownLatch倒计时门闩

  • 作用

    并发流程控制工具,倒数门闩,“拼团购物,人满发货”。倒数结束之前,一直处于等待状态,直到倒计时结束了,次线程才继续工作。

  • 两种典型用法

    CountDownLatch(int count)是仅有的一个的构造函数,参数count为需要倒数的数值。

    调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行。

    countDown()方法将count值减1,直到为0时,等待的线程会被唤醒。

    用法一:一个线程等待多个线程都执行完毕,再继续自己的工作。一等多

    用法二:多个线程等待某一个线程的信号,同时开始执行。多等一

    扩展用法:多个线程等多个线程执行完后,再同时执行。多等多

    注意点:CountDownLatch是不能重用的,如果需要重新计数,可以考虑使用CyclicBarrier或者创建新的CountDownLatch实例。

Semaphore信号量

模拟了操作系统中的信号量功能,可以用来限制或者管理数量有限的资源的使用情况。信号量的作用是维护一个“许可证”的计数,线程可以“获取”许可证,那信号量剩余的许可证就减一,线程也可以“释放”一个许可证,那信号量剩余的许可证就加一,当信号量所拥有的许可证数量为0,那么下一个还想获取许可证的线程就需要等待,直到另外的线程释放了许可证。

  • 使用流程:

    1. 初始化Semaphore并指定许可证数量。

    2. 在需要前在代码前加acquire()或者acquireUninterrptibly()方法。

    3. 任务结束后,调用release()来释放许可证。

  • 主要方法:

    • new Semaphore(int permit,boolean fair):这里设置是否使用公平策略,如果传入true,那么Semaphore会把之前等待的线程放入FIFO的队列里,以便当有了新的许可证,可以分发给之前等了最长时间的线程。

    • acquire():可以响应中断的获取。可以传入参数表示获取多少许可证,但是也要记得归还

    • acquireUninterrptibly():不能响应中断的获取。

    • tryAcquire():看看现在有没有空闲的许可证返回布尔值,如果有就去获取,如果没有也没关系,不必陷入阻塞,可以去做其它的事情。

    • tryAcquire(timeout):和tryAcquire()一样,但是多了一个超时时间。

    • release():不能忘记归还许可证,可以传入参数。

  • 特殊用法

    • 一次获取或者释放多个许可证

      比如TaskA会调用很消耗资源的method1(),而TaskB调用的是不太消耗资源的额method2(),假设我们一共有5个许可证。那么我们就可以要求TaskA获取5个许可证才能执行,而TaskB只需要获取一个许可证就能执行,这样就避免了A和B同时运行的情况,可以根据自己的需求合理分配资源

  • 注意点

    • 获取释放的许可证数量必须一致,否则比如每次都获取2个但是只释放1个甚至不释放,随着时间的推移,到最后许可证书数量不够用,会导致程序卡死。虽然信号量并不对是否和获取的数量做规定,但是这是编程规范,否则容易出错。

    • 注意在初始化Semaphore的时候设置公平性,一般设置为true会更合理。

    • 并不是必须由获取许可证的线程释放那个许可证,事实上,获取和释放许可证对线程并无要求,也许是A获取了,然后由B释放,只要逻辑合理即可。可以跨线程、跨线程池

    • 信号量的作用,除了控制临界区最多同时有N个线程访问以外,另一个作用是可以实现“条件等待”,例如线程1需要在线程2完成准备工作后才能开始工作,那么线程1acquire(),而线程2完成任务后release(),这样的话,相当于是轻量级的CountdownLatch

Condition接口(条件对象)

  • 作用

    当线程1需要等待某个条件的时候,就去执行condition.await()方法,一旦执行了await()方法,线程就进入阻塞状态。

    然后通常会有另外一个线程,假设是线程2,去执行响应的条件,直到这个条件达成的时候,线程2就会去执行condition.signal()方法,这时JVM就会从阻塞的线程中找,找到那些等待该condition的线程,当线程1收到可执行信号的时候,它的线程状态会变成Runable可执行状态。

    signalAll()和signal()的区别:

    signalAll()会唤醒所有正在等待的线程。

    signal()是公平的,只会唤醒那个等待时间最长的线程。

  • 使用

    • 普通用法

    • 使用Condition实现生产者消费者模式

      public class ConditionDemo {

      private int queueSize = 10;
      private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
      private Lock lock = new ReentrantLock();
      private Condition notFull = lock.newCondition();
      private Condition notEmpty = lock.newCondition();

      public static void main(String[] args) {
      ConditionDemo2 conditionDemo2 = new ConditionDemo2();
      Producer producer = conditionDemo2.new Producer();
      Consumer consumer = conditionDemo2.new Consumer();
      producer.start();
      consumer.start();
      }

      class Consumer extends Thread {

      @Override
      public void run() {
      consume();
      }

      private void consume() {
      while (true) {
      lock.lock();
      try {
      while (queue.size() == 0) {
      System.out.println("队列空,等待数据");
      try {
      notEmpty.await();
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      queue.poll();
      notFull.signalAll();
      System.out.println("从队列里取走了一个数据,队列剩余" + queue.size() + "个元素");
      } finally {
      lock.unlock();
      }
      }
      }
      }

      class Producer extends Thread {

      @Override
      public void run() {
      produce();
      }

      private void produce() {
      while (true) {
      lock.lock();
      try {
      while (queue.size() == queueSize) {
      System.out.println("队列满,等待有空余");
      try {
      notFull.await();
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      queue.offer(1);
      notEmpty.signalAll();
      System.out.println("向队列插入了一个元素,队列剩余空间" + (queueSize - queue.size()));
      } finally {
      lock.unlock();
      }
      }
      }
      }

      }
  • 注意点

    实际上,如果说Lock用来代替synchronized,那么Condition就是用来代替相对应的Object.wait/notify的,所以在用法和性质上,几乎都一样。

    await方法会自动释放持有的Lock锁,和Object.wait一样,不需要手动去释放锁。

    调用await的时候,必须持有锁,否则会抛出异常,和Object.wait一样。

CyclicBarrier循环栅栏

  • CyclicBarrier循环栅栏和CountdownLatch很类似,都能阻塞一组线程。

    当有大量线程相互配合,分别计算不同任务,并且需要最后统一汇总的时候,可以使用CyclicBarrier。CyclicBarrier可以构造一个集结点,当某个线程执行完毕以后,它就会到集结点等待,直到所有线程都到了集结点,那么该栅栏就被撤销,所有线程再统一出发,继续执行剩下的任务。

  • CyclicBarrier和CountdownLatch的区别

    作用不同:CyclicBarrier要等固定数量的线程都到达了栅栏位置才能继续执行,而CountdownLatch只需要等待数字到0,也就是CountdownLatch用于事件,而CyclicBarrier是用于线程的。

    可重用性不同:CountdownLatch在倒数到0并出发门闩打开后就不能重复使用了,除非是创建新的实例;而CyclicBarrier就可以重复使用。

AQS

关于AQS

AQS在并发包当中的应用非常多,设计思路很巧妙,是非常值得探究和总结的一个知识点。

为什么需要AQS

  • 锁和协作类的共同点:闸门。比如ReentrantLock和Semaphore就有很多的相似点,例如lock&acquire、tryLock&tryAcquire,支持中断与不支持中断的方法等等。不仅是ReentrantLock和Semaphore,包括CountDownLatch、ReentrantReadWriteLock都有这样的类似的协作(或者说是同步)功能,其实他们底层都是用了一个共同的基类,这就是AQS

  • 因为那些协作类有很多工作都是类似的,所以如果能够提取出一个工具类,那么就可以直接用,对于ReentrantLock和Semaphore而言就可以屏蔽很多细节,只关注它们自己的“业务逻辑”就可以了。

  • 和AQS的关系

    Semaphore内部有一个Sync类,Sync类继承了AQS;

    CountDownLatch也是一样的;

    等等,很多,只要利用了AQS,都是类似形式。

AQS的作用

  • 比喻:面试过程中无论群面还是单面,安排就坐、叫好、先来后到等HR的工作就是AQS做的工作,面试官不会关心两个面试者是不是号码相互冲突了,也不会去管面试者是否需要一个地方坐着休息,这些都是交给HR去做了。

    Semaphore:单面,一个人面完了,后一个人才能进来继续面试。CountDownLatch:群面,等待10人到齐。Semaphore、CountDownLatch等同步工具类,要做的就是写下自己“要人”的规则,比如是“出一个,进一个”,或者说是“凑齐10人,一起面试”。剩下的招呼面试者的杂活交给AQS来做。

  • 如果没有AQS

    • 就需要每个协作工具自己实现:同步状态的原子性管理、线程的阻塞与接触阻塞、队列的管理。

    • 在并发场景下,自己正确且高效地实现这些内容,都是相当有难度的,所以我们使用AQS来帮助搞定这些杂活,而使用的时候只用关注业务逻辑就行了。

  • 总结:

    AQS是一个用于构建锁、同步器、协作工具类的工具类(框架)。有了AQS以后,更多的协作工具,都可以很方便地被写出来。因为AQS解决了大量的细节问题,比如等待线程用先进先出的队列操作,以及一些标准来判断这些线程是等待还是不应等待,以及处理一些竞争问题,解决开销提高吞吐量等等。AQS的设计充分考虑了这些使用场景以及性能问题,所以使用AQS的并发工具类也同时拥有了这些优势。

    有了AQS,构建线程协作类就容易多了。

AQS的重要性以及地位

AbstractQueuedSynchronizer是Doug Lea写的,从JDK1.5加入的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架。

AQS内部原理解析

  • AQS最核心的三个部分:

    • state

      state的具体含义,会根据具体实现类的不同而不同,比如在Semaphore里,它表示“剩余的许可证数量”,而在CountDownLatch里,它表示“还需要倒数的数量”。

      state是volatile修饰的,会被并发修改,所以修改state的方法都需要保证线程安全,比如getState、setState以及compareAndSetState操作来读取和更新这个状态。这些方法都依赖于juc.atomic包的支持。

    • 控制线程抢锁和配合的FIFO队列

      这个队列用来存放"等待的线程”, AQS就是 "排队管理器”,当多个线程争用同一把锁时,必须有排队机制将那些没能拿到锁的线程串在一起。当锁释放时,锁管理器就会挑选个合适的线程来占有这个刚刚释放的锁。

      AQS会维护一个等待的线程队列,把线程都放到这个队列里,这是一个双向形式的队列

    • 期望协作工具类去实现的获取/释放等重要方法

      这里的获取和释放方法,是利用AQS的协作工具类里最重要的方法,是由协作类自己去实现的,并且含义各不相同。

      获取方法:获取操作会依赖state变量,经常会阻塞(比如获取不到锁的时候)。在Semaphore中 ,获取就是acquire方法,作用是获取一个许可证;而在CountDownLatch里面, 获取就是await方法,作用是“等待,直到倒数结束”。

      释放方法:释放操作不会阻塞。在Semaphore中,释放就是release方法,作用是释放一个许可证。CountDownLatch里面,释放就是countDown方法,作用是“倒数1个数”。

应用实例、源码解析

  • AQS用法

    第一步:写一个类,想好协作的逻辑,实现获取/释放方法。

    第二步:内部写一个Sync类继承AbstractQueuedSynchronizer

    第三步:根据是否独占来重写tryAcquire/tryRelease或者tryAcquireShared (int acquires)和tryReleaseShared(intreleases)等方法,在之前写的获取/释放方法中调用AQS的acquire/release或者Shared方法。

  • AQS在CountDownLatch的应用

    • 构造函数

    • getCount

    • countDown

    • await

    //java10
    public class CountDownLatch {
    //构造方法
    public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    //创建Sync实例
    this.sync = new Sync(count);
    } //间接调用sync.getCount()方法
    //最终得到的是state的值
    public long getCount() {
    return sync.getCount();
    } //AQS的releaseShared当中会去调用Sync的tryReleaseShared方法,CAS减少state的数值
    //一旦state为0,tryReleaseShared会返回true
    //根据这个条件的成立,如唤醒队列当中的线程。
    public void countDown() {
    sync.releaseShared(1);
    } //await会去调用AQS的acquireSharedInterruptibly方法,进而调用Sync的tryAcquireShared方法,
    //只要state数值减少到0了已经,tryAcquireShared方法就会返回1,如果state是其他值就返回-1
    //所以如果state不为0,就进入队列,进入阻塞状态
    //state为0,就正常获得锁,而不需要等待了
    public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
    } /**
    * Synchronization control For CountDownLatch.
    * Uses AQS state to represent count.
    */
    private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
    //设置state值
    setState(count);
    }

    int getCount() {
    return getState();
    } public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
    }
    public void countDown() {
    sync.releaseShared(1);
    }

    protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    //这里用一个循环在做CAS的自旋
    for (;;) {
    int c = getState();
    if (c == 0)
    return false;
    int nextc = c - 1;
    if (compareAndSetState(c, nextc))
    return nextc == 0;
    }
    }
    } //省略。。。
    }


    public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable { protected final int getState() {
    return state;
    } public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
    throw new InterruptedException();
    //tryAcquireShared在sync当中实现了
    if (tryAcquireShared(arg) < 0)
    //让当前线程进入等待队列,并且阻塞
    doAcquireSharedInterruptibly(arg);
    //如果不小于0就是正常获得锁,不需要等待了。
    } private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //首先把当前线程包装成一个Node结点
    final Node node = addWaiter(Node.SHARED);
    try {
    for (;;) {
    final Node p = node.predecessor();
    if (p == head) {
    int r = tryAcquireShared(arg);
    if (r >= 0) {
    setHeadAndPropagate(node, r);
    p.next = null; // help GC
    return;
    }
    }
    //阻塞是在这里做的
    //parkAndCheckInterrupt
    if (shouldParkAfterFailedAcquire(p, node) &&
    parkAndCheckInterrupt())
    throw new InterruptedException();
    }
    } catch (Throwable t) {
    cancelAcquire(node);
    throw t;
    }
    } private final boolean parkAndCheckInterrupt() {
    //最终会调用Usafe的park方法
    //就是把当前线程挂起
    LockSupport.park(this);
    return Thread.interrupted();
    } public final boolean releaseShared(int arg) {
    //tryReleaseShared在Sync中实现了
    if (tryReleaseShared(arg)) {
    //这个方法在tryReleaseShared返回true的情况下会把阻塞的线程全部唤醒,也就是count从1减到0的时候闸门打开
    doReleaseShared();
    return true;
    }
    return false;
    } ​ //省略。。。
    }
    • AQS在CountDownLatch的总结

      调用CountDownLatch的await方法时,便会尝试获取"共享锁”,不过一开始是获取不到该锁的,于是线程被阻塞。而“共享锁”可获取到的条件,就是"锁计数器”的值为0。而"锁计数器”的初始值为count ,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将"锁计数器”-1。 count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待获取共享锁的线程才能继续运行。

  • AQS在Semaphore的应用

    在Semaphore中,state表示许可证的剩余数量。

    看tryAcquire方法 ,判断nonfairTryAcquireShared大于等于 0的话,代表成功。这里会先检查剩余许可证数量够不够这次需要的 ,用减法来计算,如果直接不够,那就返回负数,表示失败;如果够了,就用自旋加compareAndSetState来改变state状态,直到改变成功就返回正数;或者是期间如果被其他人修改了导致剩余数量不够了, 那也返回负数代表获取失败。

    //java10
    public class Semaphore implements java.io.Serializable { public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
    } //Sync
    abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;

    Sync(int permits) {
    setState(permits);
    }

    final int getPermits() {
    return getState();
    }

    final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
    int available = getState();
    int remaining = available - acquires;
    if (remaining < 0 ||
    compareAndSetState(available, remaining))
    return remaining;
    }
    }

    protected final boolean tryReleaseShared(int releases) {
    for (;;) {
    int current = getState();
    int next = current + releases;
    if (next < current) // overflow
    throw new Error("Maximum permit count exceeded");
    if (compareAndSetState(current, next))
    return true;
    }
    }

    final void reducePermits(int reductions) {
    for (;;) {
    int current = getState();
    int next = current - reductions;
    if (next > current) // underflow
    throw new Error("Permit count underflow");
    if (compareAndSetState(current, next))
    return;
    }
    }

    final int drainPermits() {
    for (;;) {
    int current = getState();
    if (current == 0 || compareAndSetState(current, 0))
    return current;
    }
    }
    } //非公平
    static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    NonfairSync(int permits) {
    super(permits);
    }

    protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
    //nonfairTryAcquireShared在Sync当中
    }
    } //省略。。。
    }


    public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable { public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
    throw new InterruptedException();
    //tryAcquireShared在Semaphore中根据公平不公平有两种实现
    if (tryAcquireShared(arg) < 0)
    //放入等待队列
    doAcquireSharedInterruptibly(arg);
    } //省略。。。
    }
  • AQS在ReentrantLock的应用

    分析释放锁的方法tryRelease:由于是可重入的,所以state代表重入的次数,每次释放锁,先判断是不是当前持有锁的线程释放的,如果不是就抛异常;如果是的话,重入次数就减一。如果减到了0 ,就说明完全释放了,于是free就是true,并且把state设置为0。

    加锁的方法:回去判断当前state是不是等于0,也会去判断当前线程是不是持有锁的线程,如果都不是,代表目前拿不到这把锁,就放到队列中去,并在以后合适的时机唤醒。

    public class ReentrantLock implements Lock, java.io.Serializable {
    abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L; //释放锁
    public void unlock() {
    sync.release(1);//调用AQS的release方法
    } //加锁
    public void lock() {
    sync.acquire(1);
    }

    /**
    * Performs non-fair tryLock. tryAcquire is implemented in
    * subclasses, but both need nonfair try for trylock method.
    */
    @ReservedStackAccess
    final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    if (compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(current);
    return true;
    }
    }
    else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0) // overflow
    throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
    }
    return false;
    }

    @ReservedStackAccess
    protected final boolean tryRelease(int releases) {
    //state是已经重入的次数
    int c = getState() - releases;
    //判断当前线程是否持有锁,只有持有锁才能解锁,否则抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
    boolean free = false;
    //只有当state为0的时候才释放锁
    if (c == 0) {
    //让当前这把锁恢复自由状态,不被任何线程持有
    free = true;
    setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
    }

    protected final boolean isHeldExclusively() {
    // While we must in general read state before owner,
    // we don't need to do so to check if current thread is owner
    return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final ConditionObject newCondition() {
    return new ConditionObject();
    }

    // Methods relayed from outer class

    final Thread getOwner() {
    return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    final int getHoldCount() {
    return isHeldExclusively() ? getState() : 0;
    }

    final boolean isLocked() {
    return getState() != 0;
    }

    /**
    * Reconstitutes the instance from a stream (that is, deserializes it).
    */
    private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();
    setState(0); // reset to unlocked state
    }
    }

    //省略。。。
    }

    public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable { public final boolean release(int arg) {
    //如果tryRelease返回true,代表这把锁已经被真正释放掉了
    //就会从等待结点中唤醒,去获取锁
    if (tryRelease(arg)) {
    Node h = head;
    if (h != null && h.waitStatus != 0)
    unparkSuccessor(h);
    return true;
    }
    return false;
    } //省略。。。
    }

使用AQS实现一个自己的Latch门闩

简易版CountDownLatch,一次性门闩。

参考资料

从ReentrantLock的实现看AQS的原理及应用

打通Java任督二脉一并发数据结构的基石

一行一行源码分析清楚AbstractQueuedSynchronizer

Java并发之AQS详解

英文论文的中文翻译

Android Studio模拟器无法访问网络的更多相关文章

  1. Android Studio 模拟器(AVD)访问互联网

    模拟器默认是不可以直接访问互联网的,需要为模拟器配置 DNS 服务器. (一)找到模拟器安装的位置 模拟器安装位置在安卓 SDK 下面,进入[SDK Path]/emulator. (二)打开终端输入 ...

  2. Android studio 模拟器中输入中文

    Android studio 模拟器中输入中文 学习了:https://blog.csdn.net/feidie436/article/details/78318752?locationNum=10& ...

  3. Android 使用 HTTP 协议访问网络

    正在看<第一行代码>,记录一下使用 HTTP 协议访问网络的内容吧! 在Android发送Http请求有两种方式,HttpURLConnection和HttpClient. 1.使用Htt ...

  4. Android Studio 模拟器启动问题——黑屏 死机 解决方法

    今天用了下Android Studio,出现了一些问题,现在将启动过程中遇到的问题和解决方案列出来,方便大家参考. 安装过程不多说,网上一搜一大把. 那直接说问题吧: 1. 无法启动,报错:Faile ...

  5. Android studio无法更新 提示网络连接失败

    Android studio 更新时,提示网络问题 “Connection failed. Please check your network connection and try again” 在默 ...

  6. android studio 模拟器无法联网的解决方法

    本人环境 win10 64位+ android studio  自带的模拟器 1.首先把模拟器打开,然后需要把SDK文件夹下的platform-tools添加到系统变量中(此目录一定要不要弄错). 2 ...

  7. Android Studio模拟器磁盘空间不足(Not enough disk space to run AVD)

    在Android Studio中运行模拟器时,提示Error: Not enough disk space to run AVD '....'. Exiting.是说安装模拟的磁盘空间不足,导致无法运 ...

  8. Android使用Http协议访问网络——HttpConnection

    套路篇 使用HttpConnection访问网络一般有如下的套路: 1.获取到HttpConnection的实例,new出一个URL对象,并传入目标的网址,然后调用一下openConnection() ...

  9. Android Studio模拟器的root权限

    前言 一个安卓练习中用自带的sqlite3数据库查看数据的时候,需要通过adb shell进入/data/data/[包名]/databases/目录中,通过查看创建的数据库来查看相应的数据或者表.起 ...

随机推荐

  1. 为什么阿里Java规约要求谨慎修改serialVersionUID字段

    serialVersionUID简要介绍 serialVersionUID是在Java序列化.反序列化对象时起作用的一个字段.Java的序列化机制是通过判断类的serialVersionUID来验证版 ...

  2. MyBatis插入时获取自增长主键

    在某些场景下,我们需要使用mybatis返回生成的主键值.Mybatis在insert和update标签中就提供了这种功能. 方法1: <insert id=”indetifyId” useGe ...

  3. 任意两点之间的最短路(floyed)

    F.Moving On Firdaws and Fatinah are living in a country with nn cities, numbered from 11 to nn. Each ...

  4. java猜拳

    import java.util.Scanner; public class startGuess { Person jia; Computer yi; int count; public void ...

  5. Vue - @import css 加载第三方css

    @import '~@/assets/css/style.css' CSS loader 会把把非根路径的url解释为相对路径, 加~前缀才会解释成模块路径.

  6. windows下用libevent 开发一个echo服务

    #include <stdio.h> #include <string.h> #include <errno.h> #include <iostream> ...

  7. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 按钮:按钮标签

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. 设备树DTS 学习:3-常用的DTS 函数

    Linux内核中目前DTS相关的函数都是以of_前缀开头的,它们的实现位于内核源码的drivers/of下面 void __iomem*of_iomap(struct device_node *nod ...

  9. P1058 选择题

    P1058 选择题 转跳点:

  10. redis 模糊查询与删除

    创建一条数据 set  name1  zhangsan 查询 get name1 在创建一条数据 set name2 lisi 查询 get name2 模糊查询 keys name* 查询结果  n ...