android 线程详解

一、实现线程的几种方法及区别

进程:简单来说它是线程的载体,其实它不单单是线程的载体,这个想知道可以自行百度,不是我们今天要讨论的重点
线程:程序执行流的最小单元,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源。
线程和进程的关系:同一进程中可以有多个线程,每个线程可与同属一个进程的其它线程共享进程所拥有的全部资源。线程是CPU调度的最小单元,但是线程是一种有限的系统资源;而进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。
一般来说,一个app程序至少一个进程,一个进程至少有一个线程(包含与被包含的关系),通俗来讲就是,在App这个工厂里面有一个进程,线程就是里面的生产线,但主线程只有一条,而子线程可以有多个。进程有自己独立的地址空间,而进程中的线程共享此地址空间,都可以并发执行。

1、var一个Thread对象
·var一个Thread对象
·重写Thread的run方法
·run方法里面写你的代码块

  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        var thread=object : Thread() {
            override fun run() {
                super.run()
                for (i in 0 until 100){
                    println("<<<<<<<"+Thread.currentThread().name+"<<<<"+i)
                }
            }
        }
        thread.start()
    }

2、实现Runnable接口

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        var thread=Thread(myRunnable())
        thread.start()
    }
    class myRunnable : Runnable{
        override fun run() {
            for (i in 0 until 100){
                println("<<<<<<<"+Thread.currentThread().name+"<<<<"+i)
            }
        }

    }

3、实现Callable接口

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //创建单个线程池
        var myPool=Executors.newSingleThreadExecutor()
        var myFuture=myPool.submit(myCallable())
        myFuture.get()
        println("<<<<<<<<<"+myFuture.get())
    }

    class myCallable : Callable<Int>{
        override fun call(): Int {
            var a=0
            for (i in 0 until 100){
                a=i
            }
            return a
        }
    }

二、Java线程池

为什么要使用线程池呢?
我们知道线程的创建和销毁是非常耗费资源的,有时候创建线程消耗的资源比执行任务所要耗费的资源都要大,为了防止资源不足,程序需要一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务,这就是Java线程池产生的原因,也是它要解决的问题。

Java通过Executors提供了四类线程池:
1、newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待,如果线程池中的某个线程由于异常而结束,线程池则会再补充一条新线程。
For example:

//创建线程数为5的线程池
    var myPool=Executors.newFixedThreadPool(5)
    for (i in 0 until 10){
        myPool.execute(Runnable {
            Thread.sleep(500)
            println("<<<<<Name:"+Thread.currentThread().name+"<<<<"+i)
        })
    }

Log:
在这里插入图片描述
结论:
任务在1-5个线程执行

源码:

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

返回了ThreadPoolExecutor对象

2、newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
延迟2秒后执行线程

 var myPool=Executors.newScheduledThreadPool(5)
        myPool.schedule(Runnable {
             println("<<<<<Name:"+Thread.currentThread().name)

        },2,TimeUnit.SECONDS)

延迟1秒后执行线程,之后每两秒执行一次。

var myPool=Executors.newScheduledThreadPool(5)
        myPool.scheduleAtFixedRate(Runnable {
             println("<<<<<Name:"+Thread.currentThread().name)

        },1,2,TimeUnit.SECONDS)

Log:

在这里插入图片描述源码:

 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

返回了ScheduledThreadPoolExecutor对象,那我们再来看看ScheduledThreadPoolExecutor的源码

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {

ScheduledThreadPoolExecutor又继承了ThreadPoolExecutor对象
so,没有意外,没有惊喜,还是返回了ThreadPoolExecutor对象

3、newSingleThreadExecutor:创建一个单线程的线程池,即这个线程池永远只有一个线程在运行,这样能保证所有任务按指定顺序来执行。如果这个线程异常结束,那么会有一个新的线程来替代它。

var myPool=Executors.newSingleThreadExecutor()
for (i in 0 until 10){
myPool.execute(Runnable {
Thread.sleep(500)
println("<<<<<Name:"+Thread.currentThread().name+"<<<<"+i)
})
}

Log:

在这里插入图片描述源码:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

返回了ThreadPoolExecutor对象

4、newCachedThreadPool:创建一个可缓存线程池,当线程池中有之前创建的可用线程就重用之前的线程,否则就新建一条线程,。如果线程池中的线程在60秒未被使用,就会把它从线程池中移除,可灵活回收空闲线程。

var myPool=Executors.newCachedThreadPool()
for (i in 0 until 10){
    myPool.execute(Runnable {
        Thread.sleep(500)
        println("<<<<<Name:"+Thread.currentThread().name+"<<<<"+i)
    })
}

Log:
在这里插入图片描述源码:

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

返回了ThreadPoolExecutor对象

三、深入理解java线程池

每个线程池都返回了ThreadPoolExecutor,通过观察ThreadPoolExecutor源码,我们发现:

ThreadPoolExecutor 继承自AbstractExecutorService

public class ThreadPoolExecutor extends AbstractExecutorService {

ThreadPoolExecutor提供了四种构造方法:

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
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;
    }

仔细观察这个四个构造器,我们发现,前三个构造器都是调用第四个构造器来进行初始化的,所以我们要着重研究最后一个构造器,下面解释下一下构造器中各个参数的含义:

corePoolSize:核心线程池大小,创建了线程池后,默认情况下,线程池中没有任何线程(注意:是默认情况哦,证明还是其他情况哦),而是等待有任务到来才创建线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程;

keepAliveTime:表示线程池中的线程没有任务执行时最多保持多久时间会终止。默认,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS //天
TimeUnit.HOURS //小时
TimeUnit.MINUTES //分钟
TimeUnit.SECONDS //秒
TimeUnit.MILLISECONDS //毫秒
TimeUnit.MICROSECONDS //微秒
TimeUnit.NANOSECONDS  //纳秒

workQueue:一个阻塞队列,用来存储等待执行的任务,线程池的排队策略与BlockingQueue有关。
我们回到上面四种线程池源码看看,发现:
newFixedThreadPool使用了LinkedBlockingQueue
newSingleThreadExecutor使用了LinkedBlockingQueue
newCachedThreadPool使用了SynchronousQueue

threadFactory:这个很好理解,字面意思就可以理解,主要用来创建线程;
handler:RejectedExecutionHandler拒绝执行处理程序,当拒绝处理任务时的策略,有以下四种取值:
1.ThreadPoolExecutor.AbortPolicy():

 private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

AbortPolicy()源码:

/**

  • A handler for rejected tasks that throws a

  • {@code RejectedExecutionException}.
    /
    public static class AbortPolicy implements RejectedExecutionHandler {
    /
    *

    • Creates an {@code AbortPolicy}.
      */
      public AbortPolicy() { }

    /**

    • Always throws RejectedExecutionException.
    • @param r the runnable task requested to be executed
    • @param e the executor attempting to execute this task
    • @throws RejectedExecutionException always
      */
      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      throw new RejectedExecutionException("Task " + r.toString() +
      " rejected from " +
      e.toString());
      }
      }

阅读源码,得出结论:AbortPolicy()会丢弃当前任务并抛出RejectedExecutionException异常

2.ThreadPoolExecutor.DiscardPolicy()
DiscardPolicy()源码:

    /**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
 public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

阅读源码,得出结论:DiscardPolicy()会丢弃当前任务但不抛出异常

3.ThreadPoolExecutor.DiscardOldestPolicy()
DiscardOldestPolicy()源码:

/**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries {@code execute}, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }
}

再来看看e.getQueue().poll()是干什么的,来上源码:

/**
     * Retrieves and removes the head of this queue,
     * or returns {@code null} if this queue is empty.
     *
     * @return the head of this queue, or {@code null} if this queue is empty
     */
    E poll();

Retrieves and removes the head of this queue, or returns {@code null} if this queue is empty:如果此队列为空,则检索并删除此队列的头部,或返回{@ code null}

阅读源码,得出结论:DiscardOldestPolicy()会丢弃队列最前面的任务,然后重新尝试执行新任务。简单来说就是:抛弃旧任务,执行新任务。

4.ThreadPoolExecutor.CallerRunsPolicy()

/**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

阅读源码,得出结论:CallerRunsPolicy ()会重新添加当前的任务,他会自动重复调用execute()方法。

AbstractExecutorService源码:

public abstract class AbstractExecutorService implements ExecutorService 

发现:AbstractExecutorService实现了ExecutorService接口,来看看ExecutorService源码:

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

而ExecutorService又是继承了Executor接口,我们看一下Executor接口的实现:

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

得出结论,ThreadPoolExecutor继承自AbstractExecutorService,AbstractExecutorService 又实现了ExecutorService接口,ExecutorService接口又继承了Executor接口。

Executor接口里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;
然后ExecutorService接口继承自Executor接口,并且声明了一些方法:submit提交、invokeAll调用所有、invokeAny调用任何一个以及shutDown关闭线程池等等等等;

抽象类AbstractExecutorService实现了ExecutorService接口,实现了ExecutorService中声明的方法;

ThreadPoolExecutor继承了AbstractExecutorService。

介绍下ThreadPoolExecutor中几个重要的方法:
execute():execute()方法实际上是Executor接口中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

submit():submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。

shutdown():当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

shutdownNow():执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,shutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

还有其他方法:getQueue()、getPoolSize()、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查看源码。

四、Java内存模型

Java的内存模型规定了如何访问和何时访问其他线程修改后的共享变量。
Java内存模型把虚拟机划分为线程栈和堆,如图所示:

在这里插入图片描述每一个运行在Java虚拟机的线程都拥有自己的一个独立的栈(线程栈),这个栈里面包含了这个线程调用的方法当前执行点的信息。每个线程只能访问自己的线程栈,每个线程创建的本地变量对于其他线程都不可见,即使是俩个线程执行的是同一个函数,俩个线程仍然在自己的线程栈中创建本地变量,So,每个线程拥有每个本地变量的独有版本。
这么讲貌似有些难理解,举个例子:
张三(线程)和李小花(李小花属于张三的本地变量)结婚了,他俩做了羞羞的事情,然后生了几个孩子,他们有了一个家(家就相当于线程栈,里面保存了张三调用“生孩子”这个方法当前执行点的信息),但是别人不知道张三是否结婚并且有孩子(每个线程创建的本地变量对于其他线程都不可见)
所有原始类型的本地变量都存放在线程栈上,因此对其它线程不可见。一个线程可能向另一个线程传递一个原始类型的变量的拷贝,但是它不能共享这个原始类型变量自身。
堆上包含在Java程序中创建的所有对象
一个本地变量也可能是指向一个对象的一个引用。在这种情况下,引用(这个本地变量)存放在线程栈上,但是对象本身存放在堆上。
在这里插入图片描述两个线程拥有一些列的本地变量。其中一个本地变量(Local Variable 2)执行堆上的一个共享对象(Object 3)。这两个线程分别拥有同一个对象的不同引用。这些引用都是本地变量,因此存放在各自线程的线程栈上。这两个不同的引用指向堆上同一个对象。

五、CPU内存架构

现在大多数计算机硬件架构的简图:

在这里插入图片描述现在的计算机,基本都是多个CPU,并且有些CPU还是多核的,因此你的Java程序中,每个CUP执行一个线程,并且俩个或者俩个以上的CPU在同时执行任务,这种情况就是我么所说的:并发。

什么是寄存器,点击访问,百度百科:寄存器
每个CPU都有自己的寄存器,CPU在寄存器上执行操作的速度,远远大于在主内存中。

每个CPU还有自己的高速缓存层,CPU访问缓存层的速度快于访问主存的速度,但比访问内部寄存器的速度还要慢一点。

一个计算机还包含一个主存。所有的CPU都可以访问主存。主存通常比CPU中的缓存大得多。

通常情况下,当CPU需要读取主内存的时候,他会将部分数据读到,CPU缓存中,甚至可以将CPU缓存中的部分数据读到寄存器中,然后在寄存器中操作,操作完成后,需要将数据写入主存中的时候,先将数据刷新至CPU缓存中,然后在某个时间点将数据刷新到主存中。

当CPU需要在缓存层存放一些东西的时候,存放在缓存中的内容通常会被刷新回主存。CPU缓存可以在某一时刻将数据局部写到它的内存中,和在某一时刻局部刷新它的内存。它不会再某一时刻读/写整个缓存。

For example:
int i=i+1
当线程执行这个语句时,会先从主内存中读取i的值,然后复制一份到CPU的高速缓存中,然后CPU执行指令对i进行加1的操作,然后将数据写入高速缓存,最后将最新的i值刷新到主存当中。

六、多线程下的缓存一致性问题

Int count=0
count=count+1

如果count=count+1在单线程里面运行,这个是没有任何问题的,但是在多线程中运行就会有问题,会有什么问题呢?

当线程执行count=count+1时会先从主内存读取count的值,然后复制一份到CPU的高速缓存中,对count进行+1操作,将count的结果写入高速缓存中,再将i的值刷新到主内存当中。

如果有俩个线程同时执行这个代码,我们期望的结果为2,到底会出现什么情况呢?我们继续分析。

开始时,俩个线程分别读取count的值到各自的CPU高速缓存当中,线程1和线程2对count进行+1操作,线程1将count的结果写入高速缓存中,再将i的值刷新到主内存当中,此时线程2高速缓存中,count的值还是0,进行加1操作之后,count的值为1,然后线程2把count的值写入内存,这个时候count的值还为1。

最终结果i的值是1,而不是2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。

在多线程编程的时候,如果一个变量在多个CUP中都有缓存,就可能会出现缓存不一致性问题。

问题清楚了,我们如何来解决这个问题呢?

来先上个例子,至于为什么不用Int请看上篇文章:

文章链接:Java线程并发小例子的思考,寻求大佬答疑解惑

public Integer count = 0;
public int TestVolatile(){
    final CountDownLatch countDownLatch = new CountDownLatch(1000);
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                }

                count++;
                countDownLatch.countDown();
            }
        }).start();
    }
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("<<<<<"+count);
    return count;
}

Log:

03-18 03:00:16.098 5569-5569/com.example.myapplication I/System.out: <<<<<863
03-18 03:01:55.414 5569-5569/com.example.myapplication I/System.out: <<<<<1000
03-18 03:01:58.210 5569-5569/com.example.myapplication I/System.out: <<<<<976
03-18 03:02:00.426 5569-5569/com.example.myapplication I/System.out: <<<<<925

我们期望count结果等于1000,结果看log,都是小于1000的,那么我们如何能让结果等于我们期望的1000呢:

第一种:
采用synchronized:

public Integer count = 0;
    public Integer TestVolatile() {
        final CountDownLatch countDownLatch = new CountDownLatch(1000);
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                    }

                    increase();
                    countDownLatch.countDown();
                }
            }).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("<<<<<" + count);
        return count;
    }
    public synchronized void increase() {
        count++;
    }

Log:

03-25 14:20:02.426 15465-15465/? I/System.out: <<<<<1000
03-25 14:20:09.868 15465-15465/com.example.myapplication I/System.out: <<<<<1000
03-25 14:20:13.214 15465-15465/com.example.myapplication I/System.out: <<<<<1000

第二种:采用Lock:

 public Integer count = 0;
    Lock lock = new ReentrantLock();
    public Integer TestVolatile() {
        final CountDownLatch countDownLatch = new CountDownLatch(1000);
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                    }

                    increase();
                    countDownLatch.countDown();
                }
            }).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("<<<<<" + count);
        return count;
    }
    public void increase() {
        lock.lock();
        try {
            count++;
        } finally{
            lock.unlock();
        }

    }

第三种:采用AtomicInteger:

public AtomicInteger count = new AtomicInteger();
    public AtomicInteger TestVolatile() {
        final CountDownLatch countDownLatch = new CountDownLatch(1000);
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                    }

                    increase();
                    countDownLatch.countDown();
                }
            }).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("<<<<<" + count);
        return count;
    }
    public void increase() {
        count.getAndIncrement();

    }

七、原子操作和指令重排

1.原子性操作
来个例子:
张三账户有1000大洋,李四账户有2000大洋,张三要给李四转账100大洋,在这个过程中:
if成功:
1.张三账户:1000-100=900
2.李四账户:2000+100=2100
if失败:
1.张三账户:1000
2.李四账户:2000
我们把这种要么同时成功,要么同时失败要么同时成功的操作叫做具有原子性的操作。

2.指令重排
天天听大神说:指令重排,指令重排到底是个什么鬼?
举个例子:

int i = 0;              
boolean flag = false;
i = 1;                //语句1  
flag = true;          //语句2

上面代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)。

下面解释一下什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。

但是要注意,虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,那么它靠什么保证的呢?再看下面一个例子:

int a = 10;    //语句1
int r = 2;    //语句2
a = a + 3;    //语句3
r = a*a;     //语句4

这段代码有4个语句,那么可能的一个执行顺序是:在这里插入图片描述那么可不可能是这个执行顺序呢: 语句2 语句1 语句4 语句3

不可能,因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。

虽然重排序不会影响单个线程内程序执行的结果,但是多线程呢?下面看一个例子:

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2
 
//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此是线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomethingwithconfig(context)方法,而此时context并没有被初始化,就会导致程序出错。

从上面可以看出,指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android线程是指在Android操作系统中运行的线程线程是一种轻量级的执行单元,它可以在程序中并发地执行多个任务。Android系统中有两种类型的线程:UI线程和后台线程。 1. UI线程 UI线程是指在Android应用程序中与用户交互的主线程,也称为主线程。它负责处理用户界面的所有事件和更新UI界面。在UI线程中,应该避免执行长时间的操作,否则会导致UI界面无响应。 2. 后台线程 后台线程是指在Android应用程序中不与用户交互的线程。后台线程负责执行耗时的操作,如网络请求、文件读写等。在后台线程中执行操作可以避免UI线程的阻塞,提高用户体验。 3. 线程的创建和启动 在Android应用程序中,可以通过继承Thread类或实现Runnable接口来创建线程。创建线程的步骤如下: a. 创建Thread或Runnable对象。 b. 调用Thread类的start()方法或将Runnable对象传递给Thread类的构造方法,并调用start()方法。 4. 线程的同步 在多线程环境下,可能会出现数据竞争和线程安全问题。为了保证线程安全,需要使用同步机制。在Android应用程序中,可以使用synchronized关键字或Lock接口来实现同步。 5. 线程的停止 停止线程是一个比较复杂的问题,因为线程的停止需要考虑线程的安全性和资源的释放。在Android应用程序中,可以通过设置标志位或调用Thread类的interrupt()方法来停止线程。 总之,Android线程Android应用程序中非常重要的一个概念,它可以提高程序的并发性和响应性。在编写Android应用程序时,需要注意线程的创建、同步和停止,以确保程序的正确性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值