volatile修饰的变量保持内存可见性和防止指令重排序,就是任意一个线程修改了值,会马上同步到别的线程中,但是不保证非原子操作的一致性,比如 i++ 拆分执行是 先读取 然后修改 最后赋值
指令重排序就是编译器的一种优化手段,可能实际执行的顺序和我们编写的代码顺序不一致,有时候就会导致一些问题,比如两个线程,一个线程负责加载资源,加载完成后就将某个变量的值改为true,而另一个线程循环判断这个变量是否为true
如果为true就代表资源已经加载完成,可以进行别的操作,如果指令重排序了,有可能先执行了将变量值改为true,这就会导致还没有加载资源,而另一个线程发现为true以为加载资源完成了 进行操作就会引发异常
在 java.util.concurrent.atomic 包下有很多原子类 比如AtomicInteger , 这个其实是在内部维护了一个以volatile修饰的int 变量,可以和使用Integer类一样使用,但是对这个类的非原子操作 比如++ 就会是线程安全的
这个只能用来做一个计数器,在多个线程操作这个值,比如都进行++操作 最终值也会是正确的,但是不能用来作为逻辑判断
常用方法有: incrementAndGet() 当前值+1 相当于i ++操作 ,decrementAndGet() 当前值-1 相当于 i- -操作
可以等待一些线程执行完毕以后再继续执行,举例一个应用场景就是 一个超市,每天要计算各种不同商品类型的销售数量和销售金额,计算完每一种商品销售的数量和金额以后要进行一个汇总,得到当天总共销售了多少商品,销售金额是多少
这时候可以开启多个线程去计算每个商品类型的销售数量和金额,当所有类型的计算完毕后,在进行汇总计算.
内部是通过一个计数器来实现的,初始值就是要执行任务的线程的数量,每一个线程执行完毕该值-1,当值为0时就代表所有线程已执行完毕,所以在实例化这个对象的时候 必须要传入线程的数量
常用方法: countDown() 内部计数器值-1,一般放在线程执行任务完毕时调用,代表线程执行完毕. await() 调用这个方法的线程会阻塞,等待计数器值为0,也就是所有线程执行完毕,一般是主线程开启别的线程进行计算,然后调用这个方法,阻塞等待别的线程执行
,还有一个重载方法await(long timeout, TimeUnit unit) 这个方法只是多了可以设置一个最长等待时间,第一个参是最长等待时间,第二个参是等待时间单位,都会抛出一个InterruptedException(线程中断异常)
unnable接口的run()方法是没有返回值的并且也不能抛出异常,所以在jdk1.5的时候新增了Callable<V>接口,可以返回值和抛出异常 泛型代表了返回值,但是由于启动线程必须要new一个Thread对象调用start()方法,而Thread对象只能接收Runable接口,
所以就需要new一个FutureTask<V>的对象,这个对象可以传入Callable接口,而这个FutureTask<V> 这个类也实现了 Runable接口,所以就可以传入Thread类的构造方法,然后调用start()启动线程,线程启动以后要获得返回值需要调用这个FutureTask<V>对象的get()方法,调用get()方法获取返回值,而线程还未执行完毕,那么会阻塞当前调用这个方法的线程,直到哪一个线程执行完毕返回值了以后,当前调用线程才会继续执行.这个方法也有一个重载方法get(long timeout, TimeUnit unit) 第一个参是最长等待时间,第二个参是等待时间单位 都会抛出InterruptedException(中断异常)和ExecutionException(执行异常)
java提供的一个关键字,用于解决多线程的安全问题,也就是多个线程操作一个同一个数据(变量)成为共享数据会引发问题.
举例比如卖电影票,电影票的数量就是一个共享数据,多个线程同时卖票,代码逻辑肯定要判断当前票的余额是否大于0, 大于0才能减少票的数量,如果不进行同步,假如当前只剩下1张票了,两个线程同时执行这个方法,因为当前票的数量大于0,
就会执行卖票的逻辑,只有1张票,两个线程都进行卖票肯定会出现票的数量变成-1,这样显然就不正确了.
原文:https://www.cnblogs.com/java888/p/12258729.html