在 java 中,线程由 Thread 类表示,用户创建线程的唯一方式是创建 Thread 类的一个实例,每一个线程都和这样的一个实例关联。在相应的 Thread 实例上调用 start() 方法将启动一个线程。
如果没有正确使用同步,线程表现出来的现象将会是令人疑惑的、违反直觉的。这个章节将描述多线程编程的语义问题,包括一系列的规则,这些规则定义了在多线程环境中线程对共享内存中值的修改是否对其他线程立即可见。java编程语言内存模型定义了统一的内存模型用于屏蔽不同的硬件架构,在没有歧义的情况下,下面将用内存模型表示这个概念
这些语义没有规定多线程的程序在 JVM 的实现上应该怎么执行,而是限定了一系列规则,由 JVM 厂商来满足这些规则,即不管 JVM 的执行策略是什么,表现出来的行为必须是可被接受的。
操作系统有自己的内存模型,C/C++ 这些语言直接使用的就是操作系统的内存模型,而 Java 为了屏蔽各个系统的差异,定义了自己的统一的内存模型。
简单说,Java 开发者不再关心每个 CPU 核心有自己的内存,然后共享主内存。而是把关注点转移到:每个线程都有自己的工作内存,所有线程共享主内存。
同步(synchronization)
Java 提供了多种线程之间通信的机制,其中最基本的就是使用同步 (synchronization),其使用监视器 (monitor) 来实现。java中的每个对象都关联了一个监视器,线程可以对其进行加锁和解锁操作。在同一时间,只有一个线程可以拿到对象上的监视器锁。如果其他线程在锁被占用期间试图去获取锁,那么将会被阻塞直到成功获取到锁。同时,监视器锁可以重入,也就是说如果线程 t 拿到了锁,那么线程 t 可以在解锁之前重复获取锁;每次解锁操作会反转一次加锁产生的效果。
synchronized 有以下两种使用方式:
-
synchronized 代码块。synchronized(object) 在对某个对象上执行加锁时,会尝试在该对象的监视器上进行加锁操作,只有成功获取锁之后,线程才会继续往下执行。线程获取到了监视器锁后,将继续执行 synchronized 代码块中的代码,如果代码块执行完成,或者抛出了异常,线程将会自动对该对象上的监视器执行解锁操作。
-
synchronized 作用于方法,称为同步方法。同步方法被调用时,会自动执行加锁操作,只有加锁成功,方法体才会得到执行。如果被 synchronized 修饰的方法是实例方法,那么
这个实例的监视器会被锁定。如果是 static 方法,线程会锁住相应的Class 对象的监视器。方法体执行完成或者异常退出后,会自动执行解锁操作。
Java语言规范既不要求阻止死锁的发生,也不要求检测到死锁的发生。如果线程要在多个对象上执行加锁操作,那么就应该使用传统的方法来避免死锁的发生,如果有必要的话,需要创建更高层次的不会产生死锁的加锁原语。(原文:Programs where threads hold (directly or indirectly) locks on multiple objects should use conventional techniques for deadlock avoidance, creating higher-level locking primitives that do not deadlock, if necessary.)
java 还提供了其他的一些同步机制,比如对 volatile 变量的读写、使用 java.util.concurrent 包中的同步工具类等。
同步这一节说了 Java 并发编程中最基础的 synchronized 这个关键字,大家一定要理解 synchronize 的锁是什么,它的锁是基于 Java 对象的监视器 monitor,所以任何对象都可以用来做锁。有兴趣的读者可以去了解相关知识,包括偏向锁、轻量级锁、重量级锁等。
小知识点:对 Class 对象加锁、对对象加锁,它们之间不构成同步。synchronized 作用于静态方法时是对 Class 对象加锁,作用于实例方法时是对实例加锁。
面试中经常会问到一个类中的两个 synchronized static 方法之间是否构成同步?构成同步。
2. 等待集合 和 唤醒(Wait Sets and Notification)
每个 java 对象,都关联了一个监视器,也关联了一个等待集合。等待集合是一个线程集合。
当对象被创建出来时,它的等待集合是空的,对于向等待集合中添加或者移除线程的操作都是原子的,以下几个操作可以操纵这个等待集合:Object.wait, Object.notify, Object.notifyAll。
等待集合也可能受到线程的中断状态的影响,也受到线程中处理中断的方法的影响。另外,sleep 方法和 join 方法可以感知到线程的 wait 和 notify。
这里概括得比较简略,没看懂的读者没关系,继续往下看就是了。
这节要讲Java线程的相关知识,主要包括:
Thread 中的 sleep、join、interrupt
继承自 Object 的 wait、notify、notifyAll
还有 Java 的中断,这个概念也很重要
2.1 等待
等待操作由以下几个方法引发:wait(),wait(long millisecs),wait(long millisecs, int nanosecs)。在后面两个重载方法中,如果参数为 0,即 wait(0)、wait(0, 0) 和 wait() 是等效的。
如果调用 wait 方法时没有抛出 InterruptedException 异常,则表示正常返回。
我们在线程 t 中对对象 m 调用 m.wait() 方法,n 代表加锁编号,同时还没有相匹配的解锁操作,则下面的其中之一会发生:
- 如果 n 等于 0(如线程 t 没有持有对象 m 的锁),那么会抛出 IllegalMonitorStateException 异常。
注意,如果没有获取到监视器锁,wait 方法是会抛异常的,而且注意这个异常是IllegalMonitorStateException 异常。这是重要知识点,要考。
- 如果线程 t 调用的是 m.wait(millisecs) 或m.wait(millisecs, nanosecs),形参 millisecs 不能为负数,nanosecs 取值应为 [0, 999999],否则会抛出 IllegalArgumentException 异常。
(毫秒,纳秒)
-
如果线程 t 被中断,此时中断状态为 true,则 wait 方法将抛出 InterruptedException 异常,并将中断状态设置为 false。
-
否则,下面的操作会顺序发生:
注意:到这里的时候,wait 参数是正常的,同时 t 没有被中断,并且线程 t 已经拿到了 m 的监视器锁。
- 线程 t 会加入到对象 m 的等待集合中,执行 加锁编号 n 对应的解锁操作
- 为什么要线程安全
因为多线程情况下容易出现如数据库一样的脏读,会使某个任务拿到错误的数据进而产生不安全的因素
- 为什么要同步
解决安全问题,牺牲效率,线程执行时会先获取锁,没有获取锁则等候
- Atomic Integer和Integer区别
Atomic下java8一共有16个类,可以原子更新int long boolean,其他char,float,double,short,byte可以通过Unsafe类的3种CAS方法实现,除了这些,还提供了对数组,属性,引用的原子更新,通过CAS保证线程安全
atomic这个包里面提供了一组原子变量类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。
其内部实现不是简单的使用synchronized,而是一个更为高效的方式
CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。使用非阻塞算法实现并发控制,在高并发程序中非常适合
https://www.jianshu.com/p/f9fea6295db8
1、Integer是Int的包装类,Int是八种基本数据类型之一。
2、Integer变量必须实例化以后才可以使用,而Int变量不需要实例化。
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象,而Int是直接存储数据值。
4、Integer的默认值是null,Int的默认值是0。
- 装箱拆箱
1.5












网友评论