- synchronized
多个线程使用了同一个资源,java 提供了synchronized关键字来防止资源冲突。当任务要执行被synchronized所保护的代码片段时,它将先检查锁是否可用,然后获取锁,执行代码,释放锁。
public class SynchronizTest implements Runnable{
//共享资源(临界资源)
private int i=0;
public synchronized void increase(){
i++; //i++;操作并不具备原子性
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizTest instance=new SynchronizTest();
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
i++;自增是先读取 i 的值,再进行的+1操作,是分了两部分操作。所以其不具备线程安全,但经synchronized修饰的方法increase却能够保证i++的线程安全。他到底做了什么呢,我们先来分析下synchronized
synchronized能够修饰静态方法,实例方法,与代码块。当他修饰静态方法时
public class SynchronizTest implements Runnable{
public static synchronized void increase(){
i++;
}
...
}
锁是其对应的Class对象相当于
public static void increase(){
synchronized (SynchronizTest.class) {
i++;
}
}
当他修饰实例方法时
public synchronized void increase(){
i++;
}
锁是其对应的实例对象相当于
public void increase(){
synchronized (this){
i++;
}
}
以上例子说明了synchronized 关键字再指定了对象作为锁之后就具有了保证线程安全的功能,那么它是怎么实现的呢?,其实每个对象都具有一个对应的Monitor对象,synchronized 的引用正是指向的Monitor,monoter对象维护着两个队列,_WaitSet 和 _EntryList以及一个计数器count。_WaitSet里面存储的是被wait的线程。_EntryList维护着具有执行权,但在等待锁的线程,当被wait的线程经过notify后,_WaitSet里的某个线程将会添加到_EntryList中等待着锁。当有线程持有锁时count就会赋值为1,在具有锁的代码块中调用具有同一个锁的另一个方法,count会做自增操作。退出方法后count做自减操作,直到count为0就会释放锁。
synchronized的缺点不具备灵活性,开启锁以及关闭锁都不能受控制,并且出现异常时直接抛出异常不能做清理操作于是java 5引入了一个新的锁对象Lock在concurrent包中
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
Lock是一个接口,其定义着显示锁的基本方法,lock()加锁,unlock()释放锁tryLock()尝试获取锁,当锁被其他线程持有时返回false,tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,如下例子
public class SynchronizTest implements Runnable{
//共享资源(临界资源)
private static int i=0;
Lock lock = new ReentrantLock();
public void increase(){
try {
lock.lock();
i++;
}finally {
lock.unlock();
}
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizTest instance=new SynchronizTest();
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
这样我们就可以显示的开启,关闭锁,以及在出异常后正确的在finally中清理某些数据。但你很可能犯这样的错误
public void increase(){
Lock lock = new ReentrantLock();
try {
lock.lock();
i++;
}finally {
lock.unlock();
}
}89
这将导致每次调用increase方法都会使用新的锁,这比synchronized 来说更容易犯错
- 本地线程
共享变量能够被任何线程所改变,如上面的例子中static int i=0;所有的线程都能够改变i 的值,但如果你想保证线程之间的变量处理互不影响。那就可以用到ThreadLocal了,如下例子
public class ThreadLocalTest {
public static ThreadLocal<String> tl = new ThreadLocal<String>();
public static String str = "初始值";
public static void main(String[] args) throws InterruptedException {
ExecutorService es = Executors.newCachedThreadPool();
es.execute(new Task("线程一的任务"));
es.execute(new Task("线程二的任务"));
es.shutdown();
TimeUnit.MILLISECONDS.sleep(1);
System.out.println(tl.get()+str);
}
}
class Task implements Runnable{
private String str = null ;
Task(String str){
this.str = str;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+ThreadLocalTest.tl.get()+ThreadLocalTest.str);
ThreadLocalTest.tl.set(str);
ThreadLocalTest.str = str;
System.out.println(Thread.currentThread().getName()+ThreadLocalTest.tl.get()+ThreadLocalTest.str);
}
}
以上例子public static String str这个变量任何线程都能够影响它的值,它的值是不确定的,public static ThreadLocal tl 只能通过set,get方法访问它的值,对于每个线程来说,其他的线程不能影响它的值,那它是怎么实现的呢?
public T get() {
//取得当前线程
Thread t = Thread.currentThread();
//更具当前线程获取到ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果有这个map
if (map != null) {
//根据ThreadLocal实例取得Entry 实例
//Entry又是ThreadLocalMap的静态内部类
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//取得value
T result = (T)e.value;
return result;
}
}
//如果当前线程还没有维护ThreadLocalMap,初始化一个并返回null
return setInitialValue();
}
以上是ThreadLocal的get方法,ThreadLocalMap是ThreadLoad的静态内部类,Thread里面维护着一个ThreadLocalMap,所以getMap可以通过Thread的实例t取得唯一对应的ThreadLocalMap
// threadLocals正是Thread中ThreadLocal.ThreadLocalMap的变量名
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
如果t.threadLocals取出的是null,当然的初始化一个给Thread
private T setInitialValue() {
//values初始值为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
在ThreadLocalMap中也有一个内部类Entry,真正用于存储数据的
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry继承着一个弱引用,也就是说当ThreadLocal只存在于Entry时,ThreadLocal会被回收,WeakReference的get方法能够取得ThreadLocal实例,继续返回get方法中ThreadLocalMap.Entry e = map.getEntry(this);如果ThreadLocal存在,创建了并且没有被回收,这从Entry 中取出value。对于set,remove方法逻辑差不多,不再赘述
总结一下 ThreadLoacl : ThreadLocal虽说时一个共享的变量,但其内部申明着一个静态内部类ThreadLoaclMap,而每个线程对象都持有着这个内部类实例,所以Thread操作的实际上是属于当前线程下的变量,与共享变量无关了