多线程---3

1.线程的状态

在这里插入图片描述
在这里插入图片描述- 可运行态:就绪态+可运行态

  • java进程无法决定线程在某个时间点一定处于运行态(由系统调度决定)
  • 有三个阻塞态,线程表现得特性都是相同得,线程暂停/挂起来, 划分为三种状态,只是jdk/底层实现有区别。
  • Java线程得状态:
  • NEW
  • RUNNABLE 可运行态:就绪态+运行态
  • BLOCKED
  • WAITING
  • TIMED——WAITING
  • TERMINATED

2.线程安全

  • 线程安全的概念:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。
  • 多线程是同时并发并行执行多行代码指令,要考虑所有情况都符合我们的预期,才是安全的。

2.1线程不安全原因

  • 运行一个 java进程(系统分配java进程的内存空间),会启动main线程执行main函数,加载class字节码到java进程的内存,java程序(java虚拟机),执行字节码指令(一行一行的翻译到机器码,CPU执行机器码)
  • 内存(栈,堆,方法区)
  • 栈:(线程私有)的包含局部变量表(局部变量+基础类型的值)
  • 堆:(线程共享的),包含对象和数组(虽然是共享,但是线程能不能用,取决于线程是否能够获取到对象的引用)
  • 方法区(线程共享):方法区和Java堆一样,是各个线程共享的内存区域,他用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。(字符串常量池+静态变量,jdk1.7之前在方法区里面,在1.7之后在堆里面)

2.1.1线程不安全从代码层面上

  • 代码层面的:多个变量对同一个变量进行操作(读,写),有一个写操作,就有线程安全的问题。
public class UnsafeThread {
    private static int n =0;
    public static void main(String[] args) {

        for(int i =0;i<10;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i =0;i<1000;i++) {
                        n++;
                    }
                }
            }).start();
        }
        //等待子线程执行完毕,在打印多个变量++计数的值
        while(Thread.activeCount()>1) Thread.yield();
        System.out.println(n);
    }
}

2.1.2原子性(从原理上考虑)

  • 原子性:多行代码指令,执行时,是一组不可再分的最小执行单位,多个线程同时并发并行的执行代码指令,可能在一个线程操作一个共享变量时,是由前后的依赖关系,指令间有其他线程的操作,就会导致线程不安全。
  • 原子性表达出一组操作需要具有不可拆分(其他线程并发并行在中间插入指令),这种现象也叫做同步互斥,表示在操作的时候是互相排斥的。
  • 注意特殊的非原子行操作(java代码表示为一行代码,可能在真正运行的时候,是多行指令)
  1. n++,n–,--n,++n
    • 分解为三步:
    • 从主存读取变量值(int tmp =从主存读取的值)
    • 修改(tmp=tmp+1)
    • 写回主存
  2. Object o=new Object();
    • 分解为3步:
    • 分配对象的内存空间(java进程)
    • 对象的初始化(成员变量、实例代码块、构造方法,这些会编译成初始化指令)
    • 赋值给变量
  3. long等等64位的变量,不具有原子性。

2.1.3可见性

在这里插入图片描述

  • 主存:线程都使用的共享区域,对其中变量/对象的操作
  • 工作内存:线程之间互相不可见,CPU执行线程中代码指令,是从主存复制到CPU高速缓存。

2.1.4代码顺序性

  • 代码重排序:java代码顺序是固定的,但是jvm执行的字节码或cpu执行机器码,都可能重排序指令顺序,目的是提高运行效率。
  • 但是重排序是会考虑我们指令的前后的依赖关系。(站在一个线程的角度,看线程的指令中:都是有序的(其实表达的意思,是指运行结果符合预期结果的,内部还是有指令的重排序),站在其他线程的角度,看某个线程的指令执行:都是无序的)

2.2如何解决线程安全问题

  • 一组代码,如果存在多线程对共享变量的操作,都需要考虑线程安全问题,一般把多线程操作的共享变量,称为临界资源。一组代码,称为临界区
  • 思路:把临界区加锁,多个线程执行临界区的代码,最终的表现是,就是一个线程申请加锁,执行代码,释放锁,其他线程申请失败,需要等待(不一定是阻塞态,也可以是运行态)
  • 一个线程依次的执行临界区代码
    在这里插入图片描述
  • 同一个对象:
 public static void main(String[] args) {
        Person p1 = new Person();
        p1.name="ABC";
        Person p2 = new Person();
        p2.name="ABC";
        //判断两个业务是否相等(业务上的)
        System.out.println(p1.equals(p2));
        //判断是否是同一个对象(引用的堆里面的对象,是不是同一个——
        System.out.println(p1==p2);
        //判断是否为同一个对象==
    }
  • Class和类加载的关系
		//类加载:new 对象,静态方法调用,类,静态变量,如果没有类加载,先进行类加载
        //类加载是在运行期执行的,并且只执行一次
        //类加载做的事情:1.把class字节码加载到java进程的方法区
        //2.在堆里面,生成一个Class对象(类对象)
        Class<Person> c2 = Person.class;
        Class<Person> c1 = Person.class;
        System.out.println(c1==c2);//相等true

2.3解决多线程安全问题

  • synchronized关键字,同步的关键字:依次执行某段代码
  • 作用:对同一个对象加锁的线程,造成同步互斥的作用(多个线程依次执行临界区代码),注意:如果不是使用同一个对象加锁,意味着能同时执行(并发并行)

2.3.1在方法上

  • 在静态方法上:加锁整个方法,所对象为当前的类对象(Class<当前类型>为类对象)
public class SynchronizedThread {

    private static int COUNT;

    public static void main(String[] args) {
        for(int i =0;i<9;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i =0;i<1000;i++) {
                        increment1();
                    }
                }
            }).start();
        }
        //等待子线程执行完毕,在打印多个变量++计数的值
        while(Thread.activeCount()>1) Thread.yield();
        System.out.println(COUNT);
    }

    private  synchronized static void increment1() {
        COUNT++;
    }
}

  • 在实例方法上:加锁整个方法,锁对象为this(this指的是哪个对象调用该实例方法,this就指谁)

2.3.2同步代码块

  • synchronized(锁对象){}
private   static void increment2() {
        synchronized (SynchronizedThread.class){
            COUNT++; 
        }
    }
  • 锁对象为:SynchronizedThread.class
  • 不同的对象加锁,不会产生同步互斥的效果;
private  static void increment3() {
        Object o = new Object();
        synchronized (o){
            COUNT++;
        }
    }
  • 应该改为
static Object o = new Object();
    private   static void increment3() {
        synchronized (o){
            COUNT++;
        }
    }

2.4 synchronized加锁的部分原理

  1. 本质上是对对象头进行加锁,对同一个对象加锁的线程,是同步互斥的。(一个对象加锁只能由一个线程加锁后,必须释放锁,其他线程才能获取到)。
  2. 底层实现原理是基于操作系统的锁实现。
  3. synchronized申请是可重入的。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值