栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

线程day2

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

线程day2

一.线程的上下文切换

概念:CPU切换前把当前任务的状态保存下来,以便下次切换回这个任务时可以再次加载这个任务的状态,然后加载下一任务的状态并执行,任务的状态保存及再加载。


一个CPU的内核一个时间只能运行一个线程中的一个指令

当CPU内核在多个线程间来回切换运行,切换速度很快时,达到同时运行的效果(因为CPU是不停的在多个线程来回切换,切换的过程过于频繁,性能会降低)

每个线程都有一个程序计数器(记录要上次执行的行数)

一组寄存器(保存当前线程的工作变量)

堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。

二.线程的安全(同步)问题

线程安全问题指,存在共享资源和多个线程共同操作共享数据(多个线程,同一时间,执行同一段指令或修改同一个变量),就是当多个线程同时操作同一个可共享的资源时,导致指令不能完整的执行,整体数据不一致。

package com.hopu.Thread;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Bank {
    //模拟100个银行账户
        private int[] accounts = new int[100];
        Lock lock = new ReentrantLock();
    {
        //初始化
        for (int i = 0; i < 100; i++) {
            accounts[i] = 3000;
        }
    }

    
    public void transfer(int start,int end,int temp){
        if(accounts[start] < temp){
            throw new RuntimeException("余额不足");
        }
        
            accounts[start] -= temp;
            System.out.printf("%d转出%dn",start,temp);
            accounts[end] += temp;
            System.out.printf("%d转入%dn",end,temp);
            System.out.println("银行总余额" + sum());
        

    }

    
    public int sum(){
        int sum = 0;
        for (int i = 0; i < accounts.length; i++) {
            sum += accounts[i];
        }
        return sum;
    }
    
    public static void main(String[] args) {
        Bank bank = new Bank();
        Random random = new Random();
        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                int number1 = random.nextInt(100);
                int number2 = random.nextInt(100);
                int money = random.nextInt(1500);
                bank.transfer(number1,number2,money);
            }).start();
        }
        for (int i = 0; i < bank.accounts.length; i++) {
            System.out.println(bank.accounts[i]);
        }
    }
}
三.线程的安全问题的解决方法

核心思想:将程序进行上锁,当前线程完整的执行全部指令后,在释放锁,其他线程才能执行

三种上锁方法 1.同步方法

synchronized调用方法对线程进行上锁,其他线程无法执行,只有该线程结束后其他进程才能运行(在方法添加synchronized即可)

锁对象:非静态方法(this )静态方法 (当前类.class)  不能修饰构造器、成员变量等。

public synchronized void transfer(int start,int end,int temp)
2.同步代码块

将代码上锁,(//代码)为上锁部分 obj为锁对象,任何对象都可以作为锁,局部变量不可以

synchronized(obj){
	//代码
}

synchronized的基本的原理:

一旦代码被synchronized包含,JVM会启动监视器(monitor)对这段指令进行监控

线程执行该段代码时,monitor会判断锁对象是否有其它线程持有,如果其它线程持有,当前线程就无法执行,等待锁释放

如果锁没有其它线程持有,当前线程就持有锁,执行代码

3.同步锁

定义同步锁对象

上锁lock.lock();

释放锁lock.unlock();//一定要释放锁

public void transfer(int start,int end,int temp){
        lock.lock();
        if(accounts[start] < temp){
            throw new RuntimeException("余额不足");
        }
        try{
            accounts[start] -= temp;
            System.out.printf("%d转出%dn",start,temp);
            accounts[end] += temp;
            System.out.printf("%d转入%dn",end,temp);
            System.out.println("银行总余额" + sum());
        }finally {
            lock.unlock();
        }

    }

三种锁对比:

  • 粒度

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

  • 编程简便

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

  • 性能

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

  • 功能性/灵活性

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

四.悲观锁和乐观锁

悲观锁是认为别人每次拿数据都会进行修改

悲观锁因为拿一次就要上锁一次,锁定和释放就需要更多的资源,会降低程序的运行速度

乐观锁认为别人每次拿数据都不会修改(两种实现方式)

1.每次修改会记录数据的版本号(更新次数),更新后版号++,线程修改数据会对版本号和更新次数作比较,返回false就不更新数据

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

并发修改共享数据时,一个线程将共享内存修改后,另一线程尝试对共享内存的修改会失败。

每次更新时,通过要更新的值及原始值的比较,CAS会比较原始值和当前获取到的值。如果相等,那么将值更新为要设置的值,如果不相等,就不修改

两者的区别

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

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

ABA问题

举个简单的例子,理发店对本店会员有一个老会员返利活动,凡是卡内金额低于20元的,会往卡内充值20元,当店家(初始进程)充值完成后,该会员卡内有(假设某一会员卡内有15元)35元,此时该会员将在店内消费了18元,卡内金额变成17元,店家检查(新的进程)会员返利情况,发现该会员还没有进行返利,但是这个会员是已经进行了返利的,这个会员再次消费到卡内金额低于20元,店家(新的进程)检查,以此类推,程序就会进入一个死循环

ABA问题解决

线程对公共变量的修改,给公共变量加一个版本号,每修改前先取到公共变量的版本号,对公共变量进行修改时,先比较当前的版本号是否和之前的公共变量版本号一致,如果一致,则进行修改,并将版本号进行自增

懒汉式单例模式

编写懒汉式的单例模式,创建100个线程,每个线程获得一个单例对象,看是否存在问题(打印对象的hashCode,看是否相同)

package com.hopu.Thread;

public class MySingleton {

    private static MySingleton instance = null;

//    private MySingleton() {
//    }

    public static MySingleton getInstance(){
        if (instance == null){
            instance = new MySingleton();
        }
        return instance;
    }
}


package com.hopu.Thread;

public class Demo {
    
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                MySingleton instance = MySingleton.getInstance();
                System.out.println(instance.hashCode());
            }).start();
        }

    }
}

输出前两次单例模式对象hashCode值不一样,主要原因是第一个线程获取单例对象时,第二个线程抢占CPU时间片,第一个和第二个线程获取的单例对象instance都为空,所有输出的单例对象hashCode值都为空,后面的线程在去获取单例对象时,都为原本创建的单例对象,则输出的hashCode值则相同

 解决办法对线程进程加锁,乐观锁,悲观锁都可以

总结

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/643825.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号