Lock
Java5的concurrent包引入的接口: java.util.concurrent.locks,其功能与synchronize类似,在开始代码处调用lock() 方法获取锁,在结尾处调用unlock()释放锁。
public class MainClass { private Lock lock; private Object shared; public void operation() { lock.lock(); //获得锁,如果无法得到,线程将进入不可中断的等待状态 try { operate( shared ); //操作共享资源 } finally { lock.unlock(); //释放锁 } } }
lock接口比synchronize关键字提供了更加细致的控制方法,它提供了一下方法
- lock() : 获取锁
- tryLock() :尝试获取锁,如果未获取锁直接返回false,不进行等待
- tryLock( Long time,TimeUnit unit ) : 尝试获取锁,如果获取不成功进行等待,在等待的时长中获取到锁,返回true,否则返回false 。可以响应中断异常
- lockinterruptibly():尝试获取锁,无法获取锁时进行等待,线程出于等待时,可以调用interrupt方法中断线程的等待过程。
- unlock():释放锁
- newCondition() :创建一个与此锁关联的Condition对象,Condition可以代替Object.wait / signal / notify / notifyAll
- 可重入锁:如果当先线程已经持有锁L,那么调用另外一个需要该锁的方法时,不需重新的释放锁在获取锁,而是在当前锁的计数上++,退出该方法是在锁的计数上--。这于synchronize关键字的语意一致。不是所有的lock的实现都支持可重入语义,ReentrantLock提供一下方法:
- 公平锁:尽可能的按照线程的请求顺序来获取锁,即等待时间长的线程先获取锁,这样可以避免饥饿现象出现。new ReentrantLock(true) 可以构建公平锁,它基本上是按照先入先出的顺序来服务锁的请求者。开发者可以实现锁类来实现其他公平锁的策略。synchronize就是非公平锁。
- 读写锁: 读写锁将锁的临界资源分成连个部分,一个读锁和一个写锁,正因为有了读写锁,才使得多线程下的读操作不会发生冲突。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock是它的实现,readLock()是获取读锁,WriteLock()是获取写锁。在没有写锁的情况下,多个线程可以同时获取读锁,但是当有读锁或者写锁时,线程在获取写锁的时候必须等待其余线程将锁释放掉才能获取。
- 语句重排:JVM在不影响语义的情况下对语句进行重排。这种重排是不考虑并发的。
private int i; private int j; public void set() { //下面两个语句的顺序JVM可以重排 i = 0; j = 1; //该语句可以重排到该方法的任何地方 if ( i == 0 ) //i = 0的指令必须在这一句前面 i = 1; }
这种指令重排可能导致多线程下结果和预期不符。因此不要依靠代码的顺序来避免同步,典型的反例是双重检查惯例,synchronize关键字可以防止指令重排。
JSR-133中,volatile语义加强,禁止了volatile变量和普通变量之间的重排序。因此在Java5后,volatile和synchronize具有一样的语义--针对单个变量的读写锁。
Atomic类
Java5的concurrent包引入了若干atomic类,这些类支持原子的方式进行操作,可以避免同步,提高性能。
Atomic家族内部实现上使用了一种乐观锁的机制:假设当前线没有被其他线程修个的共享变量,并计算变量和新值,在尝试更新变量时,检查是否有其他线程修个该值,如果有,获取新值,重复上面的计算。
线程的优先级
- 线程优先级1-10,数字越大优先级越高,默认的优先级为5
- 优先级无法保证线程的执行顺序,只不过优先级高的线程获取cpu执行的几率较大,优先级低的线程并非没有机会获取
- 由调度决定,程序中那个线程执行
- thread.setPriority()是用来设置线程优先级的
- 设置线程的优先级应该在调用start()方法前
- 可以使用常量来设置线程的优先级,如:MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY
yield 和join 方法的区别
yield() 是cpu当前执行的线程,让出cpu让等待的线程共同竞争cpu,当前线程有可能重现活的cpu使用权,继续执行
join() 是当前主线程进入waiting状态,等待子线程执行完毕后在进行执行。
线程状态
Initial : 初始化状态,从线程对象被调用一直到调用start()方法;
Runnable:线程可运行状态,线程调用start()方法后就处于该状态,JDK提供了好多方法可以让线程改变Runnable状态。处于该状态下的线程不一定占用cpu,某些情况对Runnable状态进行细分,Runnable是等待获取cpu,Running是已经获取了cpu正在执行。
Blocked:线程阻塞,处于该状态的线程不能运行,因为它在等待某种事件的发生(定时器、I/O)等。下列情况会进入阻塞状态:
线程等待I/O,如读取socket;
尝试进入一个被其它线程占据的临界区(Synchronize)受阻
尝试其它线程占据的Locked受阻
执行了线程Thread的 sleep()、join() 方法或者Object.wait()方法
在某些情况下,Blocked状态如下细分:
Blocked: 等待I/O 线程等待进入临界区;
Waiting: 调用Object.wait()方法;
Sleeping:调用Thread.sleep()方法;
Exiting: 一旦线程从run方法返回,或者线程调用stop方法,就进入该状态。某些情况下也程Dead状态。