关于线程的学习第二部分

线程的上下文切换

 一个cpu的内核同一时间只能执行一个线程中的一个指令  

 线程并发

CPU内核会在多个线程间来回切换运行,切换速度非常快,达到同时运行的效果

线程的(同步)安全问题

cpu在多个线程之间来回切换,可能导致某些重要的命令不能完整的执行,出现数据的遗失等问题,在多个线程同一时间,同时运行,执行修改一段指令或者同一变量就会出现以下问题

public class BankDemo {
    private int[] accounts = new int[100];

    {
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = 10000;
        }
    }

    public  void transfer(int from, int to, int money) {
        if (accounts[from] < money) {
            throw new RuntimeException("余额不足");
        }
        accounts[from] -= money;
        System.out.printf("从%d转账了%d%n", from, money);
        accounts[to] += money;
        System.out.printf("%d被转账了%d%n", to, money);
        System.out.println("银行总额为" + getTotal());
    }

    public int getTotal() {
        int sun = 0;
        for (int i = 0; i < accounts.length; i++) {
            sun += accounts[i];
        }
        return sun;
    }

    public static void main(String[] args) {
        BankDemo bankDemo = new BankDemo();
        Random random = new Random();
        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                int from = random.nextInt(100);
                int to = random.nextInt(100);
                int money = random.nextInt(2000);
                bankDemo.transfer(from, to, money);
            }).start();
        }
    }
}

会出现银行总金额发生变化的问题,这是因为当线程发起转账之后,别的线程在此时抢占了cpu,也进行了转账,然后之前的线程未执行入账,这样就造成了数据丢失解决方法如下

线程安全问题的解决方法

解决方法:给程序上锁,让当前线程完整执行一段指令,执行完释放锁,其它线程再执行

几种不同上锁方法:

  • 同步方法

  • 同步代码块

  • 同步锁

同步方法 

给方法添加synchronized关键字

作用是给整个方法上锁

过程:

当前线程调用方法后,方法上锁,其它线程无法执行,调用结束后,释放锁。

public class BankDemo {
    private int[] accounts = new int[100];

    {
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = 10000;
        }
    }

    public synchronized void transfer(int from, int to, int money) {
        if (accounts[from] < money) {
            throw new RuntimeException("余额不足");
        }
        accounts[from] -= money;
        System.out.printf("从%d转账了%d%n", from, money);
        accounts[to] += money;
        System.out.printf("%d被转账了%d%n", to, money);
        System.out.println("银行总额为" + getTotal());
    }

    public int getTotal() {
        int sun = 0;
        for (int i = 0; i < accounts.length; i++) {
            sun += accounts[i];
        }
        return sun;
    }

    public static void main(String[] args) {
        BankDemo bankDemo = new BankDemo();
        Random random = new Random();
        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                int from = random.nextInt(100);
                int to = random.nextInt(100);
                int money = random.nextInt(2000);
                bankDemo.transfer(from, to, money);
            }).start();
        }
    }
}

锁对象的方法是,非静态方法使用 this关键字,静态则 当前类.class

同步代码块

粒度比同步方法小,粒度越小越灵活,性能更高 。锁对象,可以对当前线程进行控制,如:wait等待、notify通知。任何对象都可以作为锁,对象不能是局部变量

  //同步代码块
        synchronized (lock) {
            accounts[from] -= money;
            System.out.printf("从%d转出%d%n", from, money);
            accounts[to] += money;
            System.out.printf("向%d转入%d%n",to,money);
            System.out.println("银行总账是:" + getTotal());
        }

synchronized的基本的原理:

一旦代码被synchronized包含,JVM会启动监视器(monitor)对这段指令进行监控,线程执行该段代码时,monitor会判断锁对象是否有其它线程持有,如果其它线程持有,当前线程就无法执行,等待锁释放,如果锁没有其它线程持有,当前线程就持有锁,执行代码

同步锁

在java.concurrent并发包中的

Lock接口

基本方法:

  • lock() 上锁

  • unlock() 释放锁

常见实现类

  • ReentrantLock 重入锁

  • WriteLock 写锁

  • ReadLock 读锁

  • ReadWriteLock 读写锁

使用方法:

  1. 定义同步锁对象(成员变量)

  2. 上锁

  3. 释放锁

Lock lock = new ReentrantLock();

//方法内部上锁
lock.lock();
try{
	代码...
}finally{
	//释放锁
	lock.unlock();
}

三种锁对比:

  • 粒度

    同步代码块=同步锁 < 同步方法

  • 编程简便

    同步方法 > 同步代码块 > 同步锁

  • 性能

    同步锁 > 同步代码块 > 同步方法

  • 功能性/灵活性

    同步锁(有更多方法,可以加条件) > 同步代码块 (可以加条件) > 同步方法

悲观锁和乐观锁

悲观锁

认为线程的安全问题非常容易出现,会对代码上锁

前面所讲的锁机制都属于悲观锁

悲观锁的锁定和释放需要消耗比较多的资源,降低程序的性能

乐观锁

认为线程的安全问题不是非常常见的,不会对代码上锁

有两种实现方式:

  • 版本号机制

    利用版本号记录数据更新的次数,一旦更新版本号加1,线程修改数据后会判断版本号是否是自己更新的次数,如果不是就不更新数据。

  • CAS (Compare And Swap)比较和交换算法

    • 通过内存的偏移量获得数据的值

    • 计算出一个预计的值

    • 将提交的实际值和预计值进行比较,如果相同执行修改,如果不同就不修改

悲观锁和乐观锁对比

  • 悲观锁更加重量级,占用资源更多,应用线程竞争比较频繁的情况,多写少读的场景

  • 乐观锁更加轻量级,性能更高,应用于线程竞争比较少的情况,多读少写的场景

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值