final
关键字对其进行修饰,就可以保证它是不可变的。substring()
、replace()
、concat()
等操作,返回的是新的String对象,原始的String对象的值不受影响。而如果对StringBuffer
或者StringBuilder
对象进行substring()
、replace()
、append()
等操作,直接对原对象的值进行改变。final
类型。如java.lang.Integer
类中将value
定义为final
类型。private final int value;
final
关键字修饰的基本数据类型String
类型Short
、Integer
、Long
、Float
、Double
、Byte
、Character
等BigInteger
、BigDecimal
AtomicInteger
和 AtomicLong
则是可变的。Collections.unmodifiableXXX()
方法来获取一个不可变的集合。Collections.unmodifiableMap(map)
获的一个不可变的Map类型。Collections.unmodifiableXXX()
先对原始的集合进行拷贝,需要对集合进行修改的方法都直接抛出异常。put()
、remove()
、clear()
操作,则会抛出UnsupportedOperationException
异常。get()
、add()
、remove()
都是有synchronized
关键字修饰。有时调用时也需要手动添加同步手段,保证多线程的安全。import java.util.Vector;
/**
* @Author: lucy
* @Date: 19-8-5 下午9:02
* @Version 1.0
*/
public class VectorTest {
public static void main(String[] args) {
Vector<Integer> vector = new Vector<>();
while(true){
for (int i = 0; i < 10; i++) {
vector.add(i);
}
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
System.out.println("获取vector的第" + i + "个元素: " + vector.get(i));
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<vector.size();i++){
System.out.println("删除vector中的第" + i+"个元素");
vector.remove(i);
}
}
}).start();
while (Thread.activeCount()>20)
return;
}
}
}
ArrayIndexOutOfBoundsException
异常,原因:某个线程恰好删除了元素i,使得当前线程无法访问元素i。Exception in thread "Thread-1109" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 1
at java.util.Vector.remove(Vector.java:831)
at VectorTest$2.run(VectorTest.java:28)
at java.lang.Thread.run(Thread.java:745)
synchronized (vector){
for (int i = 0; i < vector.size(); i++) {
System.out.println("获取vector的第" + i + "个元素: " + vector.get(i));
}
}
synchronized (vector){
for (int i=0;i<vector.size();i++){
System.out.println("删除vector中的第" + i+"个元素");
vector.remove(i);
}
}
Vector
、HashTable
、通过Collections.synchronizedXXX()
方法包装的集合。suspend()
和resume()
(已经被JDK声明废除),System.setIn()
和System.setOut()
等。enum
关键字修饰的数据类型,叫枚举类型。values()
方法遍历枚举类型,通过name()
或者toString()
获取枚举类型的名称ordinal()
方法获取枚举类型中元素的序号public class EnumData {
public static void main(String[] args) {
for (Family family : Family.values()) {
System.out.println(family.name() + ":" + family.ordinal());
}
}
}
enum Family {
GRADMOTHER, GRANDFATHER, MOTHER, FATHER, DAUGHTER, SON;
}
getter
方法。ordinal()
方法获取的元素序号不会发生改变。public class EnumData {
public static void main(String[] args) {
for (Family family : Family.values()) {
System.out.println(family.name() + ":实际值" + family.getValue() +
", 实际序号" + family.ordinal());
}
}
}
enum Family {
GRADMOTHER(3), GRANDFATHER(4), MOTHER(1), FATHER(2), DAUGHTER(5), SON(6);
private final int value;
Family(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
Mutex Exclusion & Synchronization
)是一种常见的并发正确性保障手段。Critical Section
)、互斥量(Mutex
)、信号量(Semaphore
)。synchronized
关键字,其次是JUC包中的ReentrantLock
。monitorenter
指令,结束处或异常处会添加monitorexit
指令。monitorenter
和monitorexit
指令中都包含一个引用类型的参数,分别指向加锁或解锁的对象。如果是同步代码块,则为synchronized括号中明确指定的对象;如果为普通方法,则为当前实例对象;如果为静态方法,则为类对应的class对象。monitorenter
指令时,要先尝试获取锁:如果对象没被锁定或者当前线程已经拥有该对象的锁,则锁计数器加1;否则获取锁失败,进入阻塞状态,等待持有锁的线程释放锁。monitorexit
指令时,锁计数器减1,直到计数器的值为0,锁被释放。(synchronized
是支持重进入的)ReentrantLock
:synchronized
关键字相比,它是API层面的互斥锁(lock()
、unlock()
、try...finally
)。synchronized
关键字相比,具有可中断、支持公平与非公平性、可绑定多个Condition对象的高级功能。synchronized
关键字被优化,二者的性能差异并不是很大,如果不是想使用ReentrantLock
的高级功能,优先考虑使用synchronized
关键字。Test and Set
)Fetch and Increment
)Swap
)Compare and Swap,即CAS
)Load Linked/ Store Conditional,即LL/SC
)Compare and Swap
,需要借助处理器的cmpxchg
指令完成。sun.misc.Unsafe
中的compareAndSwapInt()
、compareAndSwapLong()
等几个方法包装提供。实际无法调用这些方法,需要采用反射机制才能使用。compareAndSet(expect, update)
、getAndIncrement()
等方法。这些方法内部都使用了Unsafe类的CAS操作。++i
或者i++
可以实现计数器的自增,在多线程环境下,这样使用是非线程安全的。public class UnsafeCount {
private int i = 0;
private static final int THREADS_COUNT = 200;
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_COUNT];
UnsafeCount counter = new UnsafeCount();
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
counter.count();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("多线程调用计数器i,运行后的值为: " + counter.i);
}
public void count() {
i++;
}
}
AtomicInteger
的原子自增运算。import java.util.concurrent.atomic.AtomicInteger;
public class SafeCount {
private AtomicInteger atomic = new AtomicInteger(0);
private static final int THREAD_COUNT = 200;
public static void main(String[] args) {
SafeCount counter = new SafeCount();
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int j=0;j<10000;j++){
counter.count();
}
}
});
threads[i].start();
}
while (Thread.activeCount()>1){
Thread.yield();
}
System.out.println("多线程调用线程安全的计数器atomic:"+counter.atomic);
}
public void count() {
// 调用compareAnSet方法,使用循环的CAS操作实现计数器的原子自增
for (; ; ) {
int expect = atomic.get();
int curVal = expect + 1;
if (atomic.compareAndSet(expect, curVal)) {
break;
}
}
}
}
i
,更换成具有CAS操作的AtomicInteger
类型的计数器变量atomic
。atomic
的原子自增。atomic.get(
)获取expect的值,将expect加一得到新值,然后通过atomic.compareAndSet(expect, curVal)
这一方法实现CAS操作。compareAndSet()
返回的true
或者false
,表示此次CAS操作是否成功。如果返回false,则不停地重复执行CAS操作,直到操作成功。AtomicInteger
原子自增,可以只需要调用incrementAndGet()
一个方法就能实现。public void count() {
// 调用incrementAndGet方法,实现AtomicInteger的原子自增
atomic.incrementAndGet();
}
incrementAndGet()
方法,封装了通过循环的CAS操作实现AtomicInteger
原子自增的代码。public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
1. ABA问题
1A --> 2B --> 3A
。CAS操作就能检测到共享变量的ABA问题了。AtomicStampedReference
来解决ABA问题。AtomicStampedReference
的compareAndSet()
方法会首先比较期待的引用是否等于当前引用,然后检查期待的标记是否等于当前标记。如果全部相等,则以原子操作的方式将新的引用和新的标记更新到当前值中。AtomicStampedReference
目前比较鸡肋,如果想解决AB问题,可以使用锁。2. 循环时间过长,开销大
3. 只能保证一个共享变量的原子操作
AtomicReference
类保证引用对象之间的原子性,可以将多个变量放在一个对象中进行CAS操作。Pure Code
),可在代码执行的任何时候中断他它,转去执行另外一段代码(包括递归调用它本身),控制权返回后,原来的程序不会出现任何错误。Thread Local Storage
):java.lang.ThreadLocal
类提供线程本地存储功能。ThreadLocalMap
对象,该对象将ThreadLocal
对象的hashCode
值作为key,即ThreadLocal.threadLocalHashCode
,将本地线程变量作为value,构成键值对。ThreadLocal
对象是当前线程ThreadLocalMap
对象的访问入口,通过threadLocal.set()
为本地线程添加独享变量;通过threadLocal.get()
获取本地线程独享变量的值。ThreadLocal
、ThreadLocalMap
、Thread
的关系:Thread
对象中包含ThreadLocalMap
对象,ThreadLocalMap
对象中包含多个键值对,每个键值对的key是ThreadLocal
对象的hashCode,value是本地线程变量。ThreadLocal
的编程实例:ThreadLocal
对象在该线程中进行添加,构造出的键值对自动存入该线程的map中;ThreadLocal
对象对应的value。ThreadLocal
对象重复为某个线程添加键值对,会覆盖之前的value。public class TLS {
public static void main(String[] args) {
ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
// 设置当前线程的本地线程变量
threadLocal1.set("thread1");
threadLocal2.set(1);
System.out.println(threadLocal1.get() + ": " + threadLocal2.get());
// 使用完毕后要删除,避免内存泄露
threadLocal1.remove();
threadLocal2.remove();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal1.set("thread2");
threadLocal2.set(2);
System.out.println(threadLocal1.get() + ": " + threadLocal2.get());
threadLocal1.remove();
threadLocal2.remove();
}
});
thread1.start();
thread2.start();
// 没有通过ThreadLocal为主线程添加过本地线程变量,获取到的内容都是null
System.out.println(threadLocal1.get()+": "+threadLocal2.get());
}
}
ThreadLocal
的正确理解:ThreadLocal
适用于线程需要有自己的实例变量,该实例变量可以在多个方法中被使用,但是不能被其他线程共享的场景。ThreadLocal
从理论上讲,不是用来解决多线程并发问题的。ThreadLocal
的实现:ThreadLocal
维护线程与实例的映射。既然通过ThreadLocal
对象为线程添加本地线程变量,那就将ThreadLocalMap
放在ThreadLocal
中。ThreadLocal
中的Map,需要添加锁。这是, JDK 未采用该方案的一个原因。Thread
维护ThreadLocal与实例的映射。Map是每个线程所私有,只能在当前线程通过ThreadLocal
对象访问自身的Map。不存在多线程并发访问同一个Map的情况,也就不需要锁。ThreadLocalMap
每个Entry对ThreadLocal
对象是弱引用,对每个实例是强引用。当ThreadLocal
对象被回收后,该Entry的键变成null,但Entry无法被移除。使得实例被Entry引用无法回收,造成内存泄露。