并发编程–synchronized、volatile、ThreadLocal的使用
1.synchronized内置锁
加锁目标对象
- 对象锁
- 类锁
加锁的注意事项:
- 各个线程竞争锁的目标对象必须是同一个。
- 各个线程竞争锁的目标对象不能更改,例如:
/**
* 类说明:错误的加锁和原因分析
*/
public class TestIntegerSyn {
public static void main(String[] args) throws InterruptedException {
Worker worker=new Worker(1);
//Thread.sleep(50);
for(int i=0;i<5;i++) {
new Thread(worker).start();
}
}
private static class Worker implements Runnable{
private Integer i;
private Object o = new Object();
public Worker(Integer i) {
this.i=i;
}
@Override
public void run() {
synchronized (o) {
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"--@"
+System.identityHashCode(i));
i++;
System.out.println(thread.getName()+"-------"+i+"-@"
+System.identityHashCode(i));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName()+"-------"+i+"--@"
+System.identityHashCode(i));
}
}
}
}
注意:i++时,Integer内部会对i对象进行修改,i++ == Interger.valueOf(i) + 1 ,然而valueOf(i)会new Integer(i),所以上面代码中,i的对象会发生变化,导致各个线程竞争锁的目标对象没有保持不变。
2. volatile 的使用(适用于一读多写)
volatile 保证了对象在各个线程间的可见性,不能保证原子性,但能保持可见性,是最轻量的同步机制。
测试代码:
public class VolatileTest {
private static boolean flag;
//private static volatile boolean flag;
static class VolatileTestRunable implements Runnable{
@Override
public void run() {
System.out.println("test runable is starting flag: "+flag);
while (!flag){}
System.out.println("enter volatile ,modify is effective flag: "+ flag);
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new VolatileTestRunable()).start();
TimeUnit.SECONDS.sleep(1);
flag = true;
TimeUnit.SECONDS.sleep(1);
System.out.println("main thread is end");
}
}
运行结果:(不加volatile)
test runable is starting flag: false
main thread is end
运行结果:(加volatile)
test runable is starting flag: false
enter volatile ,modify is effective flag: true
main thread is end
3.ThreadLocal 的使用
package cn.enjoyedu.ch1.threadlocal;
/**
*类说明:演示ThreadLocal的使用
*/
public class UseThreadLocal {
private static ThreadLocal<Integer> intLocal
= new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
private static ThreadLocal<String> stringThreadLocal;
/**
* 运行3个线程
*/
public void StartThreadArray(){
Thread[] runs = new Thread[3];
for(int i=0;i<runs.length;i++){
runs[i]=new Thread(new TestThread(i));
}
for(int i=0;i<runs.length;i++){
runs[i].start();
}
}
/**
*类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
*/
public static class TestThread implements Runnable{
int id;
public TestThread(int id){
this.id = id;
}
public void run() {
System.out.println(Thread.currentThread().getName()+":start");
Integer s = intLocal.get();
s = s+id;
intLocal.set(s);
System.out.println(Thread.currentThread().getName()
+":"+ intLocal.get());
//intLocal.remove();
}
}
public static void main(String[] args){
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}
}
运行结果:
Thread-0:start
Thread-2:start
Thread-2:3
Thread-1:start
Thread-0:1
Thread-1:2
ThreadLocal Map使用原理:
ThreadLocal Map底层原理:
ThreadLocal导致的内存泄露:
(原因分析)由于Entry(k,v),k为虚引用,当发生Gc的时候,threadLocal instance 被回收,不能通过threadLocal instance访问currentThread中的Entry[]时,但是Value在ThreadLocalMap中被声明为强引用,value是不会被回收的,导致value不能通过任何路径来访问,所以导致内存泄漏。
(补救措施)threadLocalref的get,set,remove方法中会调用 replaceStaleEntry(key, value, i)方法对key为空的entry进行回收,减少内存泄漏。
为何不声明成强引用?
答:如果把threadLocalinstance 声明成强引用,当对entry进行回收的时候,entry会对threadLocalinstance强依赖,导致内存泄漏,如果声明成虚引用,还有可能使用replaceStaleEntry进行内存回收。
扩展:ThreadLocal在事务中的使用:参考:spring在事务中的使用