一、实现线程的几种方法及区别
进程:简单来说它是线程的载体,其实它不单单是线程的载体,这个想知道可以自行百度,不是我们今天要讨论的重点
线程:程序执行流的最小单元,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源。
线程和进程的关系:同一进程中可以有多个线程,每个线程可与同属一个进程的其它线程共享进程所拥有的全部资源。线程是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());
}
}
- Creates an {@code AbortPolicy}.
阅读源码,得出结论: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请看上篇文章:
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并没有被初始化,就会导致程序出错。
从上面可以看出,指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。