1. 为什么需要锁?
举个例子,如下图,当同一个公共资源,被三个线程调用,每个线程都要对它set不同的值,那这个公共资源到底改听谁的?
2. 怎么上锁
2.1 自动锁
使用 synchronized 关键字。这种方法无需手动解锁。可以锁对象,也可以锁类。
对象锁: 一般使用this(锁住本类锁创建的实例(对象),并不是锁住类下的所有对象)或者在类中新建一个Obj变量来上锁,两种方法未发现差别。
可以使用 Synchronized() 可以修饰一段代码块,当程序执行到这段代码时,就会检查synchronized的参数是否被其他线程占用,从而让判断是否需要进入阻塞状态。实际操作中,通常灵活使用Obj,来配置多把锁,见如下代码。
可以使用Synchronize修饰方法,相当于Synchronized(this)
//上一把锁的情况
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
@Override
public void run() {
synchronized (this) {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
输出结果: 我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
//上两把锁的情况
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
Object block1 = new Object();
Object block2 = new Object();
@Override
public void run() {
// 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行
synchronized (block1) {
System.out.println("block1锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");
}
synchronized (block2) {
System.out.println("block2锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
输出结果: block1锁,我是线程Thread-0
block1锁,Thread-0结束
block2锁,我是线程Thread-0 // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把
block1锁,我是线程Thread-1
block2锁,Thread-0结束
block1锁,Thread-1结束
block2锁,我是线程Thread-1
block2锁,Thread-1结束
类锁: 锁住静态方法,或者使用Syncchronize(SyncchronizeObjLock.Class) 锁住本类。这时候,性和各类下面无论有多少个对象,他们之间都是竞争关系。
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
// synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
public static synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
@Override
public void run() {
// 所有线程需要的锁都是同一把
synchronized(SynchronizedObjectLock.class){
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
2.2 手动锁
使用 Lock 接口或 ReentrantLock 方法,手动进行上锁和解锁。
Lock lock=new ReentrantLock();
try{
lock.lock ();
for(int i = 0; i < 10000; i++){
numObj.setNum (++num);
}
}catch (Exception e) {
}finally {
//必须要将解锁放在finally中。
//这样的话,即使线程出了问题(比如陷入死循环),这个线程最终也必然会解锁。不影响其他线程使用这个资源。
lock.unlock ();
}
public static void main(String[] args) {
Object obj1=new Object ();
Num numobj = new Num ();
// 如果1 没有执行的时候 2 3 可以同时执行 没有锁
ThreadSet threadSet=new ThreadSet(numobj,obj1);
Thread t1 = new Thread (threadSet);
Thread t2 = new Thread (threadSet);
Thread t3 = new Thread (threadSet);
t1.start ();
t2.start ();
t3.start ();
//由于main本身也是个线程。所以要使用join,确保先执行线程再执行剩下的代码(比如print)
try {
t1.join ();
t2.join ();
t3.join ();
} catch (InterruptedException e) {
e.printStackTrace ();
}
//一个threadSet可以把num加到1000,使用线程执行了3遍,应该是3万。
//如果不使用线程,会在随机的时候输出。
System.out.println ("ThreadNum:"+ThreadSet.num);
}
3. 锁的类型
3.1 可重入锁(递归锁)
可以锁两次,比如synchronized就是一个可重入锁。例如,在一个类中有两个方法都被synchronized修饰,方法1可以调用方法2。如果是不可重入锁,则不可以这样做。
/*这里同样也是一个关于重入锁的例子。其实被synchronized修饰的方法也是一个道理,
*当整个方法被synchronized修饰时,也就相当于在开头加了lock(),在结尾加了unlock()
*/
public static void main(String[] args) throws InterruptedException {
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock ();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
lock.writeLock().lock();
System.out.println("111");
lock.writeLock().unlock();
}
});
lock.writeLock().lock();
lock.writeLock().lock(); //只有这种支持重入的,才能加两遍锁
t.start();
Thread.sleep(200);
System.out.println("222");
lock.writeLock().unlock();
lock.writeLock().unlock();
//如果加了2个unlock(),输出就是111 换行 222
//如果只有1个unlock(),输出就是111
//如果不支持重入,那么这种加锁方法就是错的
}
3.2 读写锁
顾名思义,按照读写的实际需求,只有写的时候上锁
public class Num {
private int num=0;
ReentrantReadWriteLock rwLock=new ReentrantReadWriteLock();
public int getNum() {
try {
rwLock.readLock().lock();
System.out.println(Thread.currentThread().getName()+"正在读取");
return num;
}finally {
System.out.println(Thread.currentThread().getName()+"读取完毕");
rwLock.readLock().unlock();
}
}
public void setNum(int a) {
try {
rwLock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+"正在写入");
for (int i=0;i<a;i++){
num++;
}
}finally {
System.out.println(Thread.currentThread().getName()+"写入完毕");
rwLock.writeLock().unlock();
}
}
}
3.3 公平锁和非公平锁
公平锁故名思意,当资源被释放,各个线程公平竞争。非公平锁则是在线程创建时会形成一个队列,先到先得。
3.4 锁升级和降级
ReentrantReadWriteLock支持锁降级,但是不支持锁升级。(写锁比读锁级别高)
public static void main(String[] args) {
//这个测试用例输出为111,并没输出222,说明不支持锁升级
ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.readLock().lock();
System.out.println("111");
//读锁还没有释放,就再上一把更严实的写锁,成为锁升级
rtLock.writeLock().lock();
System.out.println("222");
}