java 多线程求和的几种实现方式。

前情提要

通过本文你可以收获

  1. 通过多线程计数求和了解synchronized的应用场景,以及其他加锁方式为什么不生效
  2. 框架 对象锁和类锁

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、防止指令重排
  例子就是双重懒加载的单例模式

img

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

  1. 方法抛出异常 jvm会释放锁

  2. synchronized 作用于非static的方法 就是锁了对象 和作用于this一样

作用于static方法就是锁了类,锁了类也就是锁了这个类所有的对象实例,和synchronized(xx.class)作用一样

  1. 可重入:从外层函数获得锁后,内层函数可以直接获取该锁

同一个方法,不是同一个方法,不是一个类的方法都具有可重入性质,重入意味着获取锁的粒度是线程 不是调用。

可重入原理:

  1. 不可中断:

  2. 加锁和释放锁的原理

  3. 可见性原理:

    需要了解Java内存模型 共享变量和线程变量

    synchronize释放后 会把变量重新写入主内存,另一个线程也会从主内存中把数据拷贝到线程内存,

    但是为什么volitile不行?

  4. synchronize缺陷 :

    1、释放锁的情况少,试图获得锁不能设定超时,不能中断一个正在试图获取锁的线程

    lock更好 可以手动释放锁和设置超时

    2、不够灵活 读写锁更灵活

    3、无法知道是否成功获取到锁

  5. synchronize只会在俩种情况下释放锁:

    第一就是执行完成任务,第二是抛出出异常

  6. 使用注意

    锁对象不能为空:因为锁的信息是保存在对象头中的

    作用域不要太大,会严重降低效率

    避免死锁

  7. 如何选择synchronize和Lock

    尽量不要使用这俩个,可以使用并发包下的

    优先使用synchronize 因为代码量少?

    如果需要Lock特性 那么就使用Lock

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yFY7nzK7-1590563267129)(/Users/wwh/Library/Application Support/typora-user-images/image-20200303001609889.png)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值