前情提要
通过本文你可以收获
- 通过多线程计数求和了解synchronized的应用场景,以及其他加锁方式为什么不生效
- 框架 对象锁和类锁
1、同一时刻只有一个线程执行这段代码
2、最基本的互斥同步手段
3、分类 一共有俩种锁:
1、对象锁
1、同步代码块锁
2、方法锁
3 类锁
案例演示与分析:
1、不使用锁 求和会出问题
public class MutilThreadCount implements Runnable{
static int sum = 0;
@Override
public void run() {
//synchronized (this){
for (int i = 0; i < 100000; i++) {
sum++;
}
//}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
预期结果200000 结果小于200000
2、使用volatile也会出问题
public class MutilThreadCount implements Runnable{
static volatile int sum = 0;
@Override
public void run() {
//synchronized (this){
for (int i = 0; i < 100000; i++) {
sum++;
}
//}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
预期结果200000 结果小于200000
volititle有俩个特征:
1、变量对线程是可见的
虽然变量对各个线程是可见的, 但是sum++不是原子的,包含了获取值,自增,赋值 每一个操作都可能被其他线程打断 修改了变量的值
2、防止指令重排
例子就是双重懒加载的单例模式
3、不锁同一个对象 求和会出问题
public class MutilThreadCount implements Runnable{
Object lock1 = new Object();
Object lock2 = new Object();
static int sum = 0;
@Override
public void run() {
synchronized (lock1){
for (int i = 0; i < 100000; i++) {
sum=sum+1;
}
}
synchronized (lock2){
for (int i = 0; i < 100000; i++) {
sum=sum+1;
}
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
预期:400000 结果小于400000 第二个求和循环使用lock1就正确了
4、在方法上使用synchronized 求和正确
public class MutilThreadCount implements Runnable{
static int sum = 0;
@Override
public void run() {
synchronized (this){
for (int i = 0; i < 100000; i++) {
sum++;
}
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
预期200000结果200000
5、使用synchronized锁类 求和正确(锁类意味着锁住了Class对象 这个类所有的实例都被锁了 所以即使使用lock1和lock2也没问题 )
public class MutilThreadCount implements Runnable{
static int sum = 0;
@Override
public void run() {
synchronized (MutilThreadCount.class){
for (int i = 0; i < 100000; i++) {
sum=sum+1;
}
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
下面是新建俩个线程对象 但因为是锁的类 ,所以结果也正确,但是如果锁this就不行了
public class MutilThreadCount implements Runnable{
private Object lock1 = new Object();
private Object lock2 = new Object();
static int sum = 0;
@Override
public void run() {
synchronized (MutilThreadCount.class){
for (int i = 0; i < 100000; i++) {
sum=sum+1;
}
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count1 = new MutilThreadCount();
MutilThreadCount count2 = new MutilThreadCount();
Thread t1 = new Thread(count1);
Thread t2 = new Thread(count2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
预期20000 结果20000
6、使用AtomicInteger(更好的voltitle变量) 求和正确
public class MutilThreadCount implements Runnable{
static AtomicInteger sum = new AtomicInteger();
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
sum.addAndGet(1);
}
}
public static void main(String[] args) throws InterruptedException {
MutilThreadCount count = new MutilThreadCount();
Thread t1 = new Thread(count);
Thread t2 = new Thread(count);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(sum);
}
}
7、使用CAS技术(p263) 学懂aqs需要对照着并发包一起学习
8、使用retreentlock
9、使用futuretask来做
voltitle讲解的不错
https://crossoverjie.top/%2F2018%2F03%2F09%2Fvolatile%2F
https://zhuanlan.zhihu.com/p/56191979
分析
为什么前面俩种结果不对?
sum++不是一个原子操作
分为三步:
读取sum
把sum+1
把sum写回内存
俩个线程会在这三步中任何一步交替执行,扰乱了sum的值
为什么使用volitile也不行?不是传说中volitile是对线程可见的么?
为什么最后一种执行结果没问题:
在run方法中加了锁关键字就正常输出20000了
锁this也就是意味着给当前对象加了锁
synchronized可以简单的理解为同一时刻只有一个线程执行这段代码
肘后备急
synchronized
-
方法抛出异常 jvm会释放锁
-
synchronized 作用于非static的方法 就是锁了对象 和作用于this一样
作用于static方法就是锁了类,锁了类也就是锁了这个类所有的对象实例,和synchronized(xx.class)作用一样
- 可重入:从外层函数获得锁后,内层函数可以直接获取该锁
同一个方法,不是同一个方法,不是一个类的方法都具有可重入性质,重入意味着获取锁的粒度是线程 不是调用。
可重入原理:
-
不可中断:
-
加锁和释放锁的原理
-
可见性原理:
需要了解Java内存模型 共享变量和线程变量
synchronize释放后 会把变量重新写入主内存,另一个线程也会从主内存中把数据拷贝到线程内存,
但是为什么volitile不行?
-
synchronize缺陷 :
1、释放锁的情况少,试图获得锁不能设定超时,不能中断一个正在试图获取锁的线程
lock更好 可以手动释放锁和设置超时
2、不够灵活 读写锁更灵活
3、无法知道是否成功获取到锁
-
synchronize只会在俩种情况下释放锁:
第一就是执行完成任务,第二是抛出出异常
-
使用注意
锁对象不能为空:因为锁的信息是保存在对象头中的
作用域不要太大,会严重降低效率
避免死锁
-
如何选择synchronize和Lock
尽量不要使用这俩个,可以使用并发包下的
优先使用synchronize 因为代码量少?
如果需要Lock特性 那么就使用Lock
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yFY7nzK7-1590563267129)(/Users/wwh/Library/Application Support/typora-user-images/image-20200303001609889.png)]