java碎碎念

java多线程

Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

五、以上规则对其它对象锁同样适用.

内存可见
1.清空该线程的变量值
2.从主线程获取新值
3.更新该变量值
4.更新主线程值

缓存一致性协议:最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

CAS(compareAndSet)算法及简单应用AtomicInteger

如果是每次加一的话,current的每个值只能有一个线程设置成功,这样前面保存的current才会返回,假设保存current后,其他线程改变了current, getAndIncrement不会成功,这样保存的值无效,只能进行下次尝试。只有保存完current后,current没有变化,这样getAndIncrement成果后,才会返回current的。因此,返回的值肯定是新值的前一个值。设置每个新值的操作是惟一的,因此,返回值也是唯一的。
备份current后,getAndIncrement成功,没有其他线程改变current的值,可以返回current;否则,其他线程改变了current,只能进行下一次的尝试。

相同:ReentrantLock提供了synchronized类似的功能和内存语义。

不同:

1.ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。

2.ReentrantLock必须在finally中释放锁,否则后果很严重,编码角度来说使用synchronized更加简单,不容易遗漏或者出错。

3.ReentrantLock 的性能比synchronized会好点。

4.ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。

1、Lock的某些方法可以决定多长时间内尝试获取锁,如果获取不到就抛异常,这样就可以一定程度上减轻死锁的可能性。

如果锁被另一个线程占据了,synchronized只会一直等待,很容易错序死锁

2、synchronized的话,锁的范围是整个方法或synchronized块部分;而Lock因为是方法调用,可以跨方法,灵活性更大

3、便于测试,单元测试时,可以模拟Lock,确定是否获得了锁,而synchronized就没办法了

concurrentHashMap 结构是多个hashtable组成,每个hashtable是sychronized的,这个技术即是锁分离技术

多个线程操作的情况下如果hash到不同的段(hashtable)上就可以实现并发

Executors.newCachedThreadPool();
Executors.newFixedThreadPool(5);
Executors.newSingleThreadPool();
Callable多线程获取返回值

volatile实现原则
1.lock前缀指令会引起处理器缓存回写到内存
2.一个处理器的缓存回写会导致其他处理器的缓存无效

避免死锁
1.避免一个线程同时获取多个锁
2.避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
3.尝试使用定时锁,lock.tryLock(timeout)来替代使用内部锁机制
4.对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

锁的种类
1.偏向锁
2.轻量级锁
3.重量级锁

共享锁【S锁】
又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

排他锁【X锁】
又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

1.阻塞(悲观锁)

2.无饥饿(公平锁)

3.无障碍(乐观锁)

4.无锁(cas)

5.无等待(read-copy-update、threadlocal)

join()的本质是让调用线程wait()在当前线程对象实例上。注:类似Ajax中Async=true

wait与sleep区别

wait可以被唤醒,wait可以释放目标对象的锁,sleep不是放任何资源

ArrayList线程非安全:多线程访问冲突,使得保存容器大小的变量被多线程不正常访问,初始容量是10每次,每次扩容1.5

vector实现:

HashMap:for(;;e=e.next){}链表成环,死循环

reentrantlock重入锁

防止死锁:中断响应-lockinterruptiblty()、锁申请限时等待

公平锁需要队列,性能低

Semaphore信号量

ReadWriteLock读写锁:读操作远大于写时用

内存泄漏:

1
2
3
4
5
6
7
Vector v=new Vector(10);
for (int i=1;i<100; i++)
{
Object o=new Object();
v.add(o);
o=null;
}

//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。
首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

java引用:

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。 Object o = new Object();
弱化强引用 o= null; 帮助GC
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 SoftReference softRef=new SoftReference(str); // 软引用
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

String str=new String("abc");      
WeakReference<String> abcWeakRef = new WeakReference<String>(str);  
str=null;

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

PhantomReference 有两个好处, 其一, 它可以让我们准确地知道对象何时被从内存中删除, 这个特性可以被用于一些特殊的需求中(例如 Distributed GC, XWork 和 google-guice 中也使用 PhantomReference 做了一些清理性工作).

其二, 它可以避免 finalization 带来的一些根本性问题, 上文提到 PhantomReference 的唯一作用就是跟踪 referent 何时被 enqueue 到 ReferenceQueue 中, 但是 WeakReference 也有对应的功能, 两者的区别到底在哪呢 ?
这就要说到 Object 的 finalize 方法, 此方法将在 gc 执行前被调用, 如果某个对象重载了 finalize 方法并故意在方法内创建本身的强引用, 这将导致这一轮的 GC 无法回收这个对象并有可能
引起任意次 GC, 最后的结果就是明明 JVM 内有很多 Garbage 却 OutOfMemory, 使用 PhantomReference 就可以避免这个问题, 因为 PhantomReference 是在 finalize 方法执行后回收的,也就意味着此时已经不可能拿到原来的引用, 也就不会出现上述问题, 当然这是一个很极端的例子, 一般不会出现.

zhang dong wechat
关注我的微信来交流技术问题吧!