这篇博客可能会比较长,想看结论可直接调到总结。
1.synchronized
synchronized大家应该都很熟悉,是java中一个常用的关键字。(而lock是JUC中的接口,后面会讲到)
这里用一个死锁的demo熟悉一下synchronized的用法。
public class DeadLockRunable implements Runnable{
public int num;
//资源(同时又两只筷子才可以吃饭)
private static Chopsticks chopsticks1 = new Chopsticks();
private static Chopsticks chopsticks2 = new Chopsticks();
/**
* num = 1 拿到 chopsticks1,等待 chopsticks2
* num = 2 拿到 chopsticks2,等待 chopsticks1
*/
@Override
public void run() {
if (num == 1){
System.out.println(Thread.currentThread().getName()+"拿到了筷子1,等待筷子2");
//锁定资源-chopsticks1
synchronized (chopsticks1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (chopsticks2){
System.out.println(Thread.currentThread().getName()+"用餐完毕!");
}
}
}
if (num == 2){
System.out.println(Thread.currentThread().getName()+"拿到了筷子2,等待筷子1");
//锁定资源-chopsticks2
synchronized (chopsticks2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (chopsticks1){
System.out.println(Thread.currentThread().getName()+"用餐完毕!");
}
}
}
}
}
test类
public class MyTest {
public static void main(String[] args) {
DeadLockRunable deadLockRunable1 = new DeadLockRunable();
deadLockRunable1.num = 1;
DeadLockRunable deadLockRunable2 = new DeadLockRunable();
deadLockRunable2.num = 2;
new Thread(deadLockRunable1,"李雷").start();
new Thread(deadLockRunable2,"韩梅梅").start();
}
}
运行会发现程序不会停止,两个线程相互等待对方锁住的资源,故处于死锁的状态。要注意的是:synchronized锁的对象一定要满足一定条件才会生效:synchronized锁住的对象一定是多个线程共享且唯一不变的元素,即内存中独一份。(个人理解)
当然,为了避免以上死锁,也很简单:
test类稍作修改,既保证两个线程拿资源不冲突就可:
public class MyTest {
public static void main(String[] args) {
DeadLockRunable deadLockRunable1 = new DeadLockRunable();
deadLockRunable1.num = 1;
DeadLockRunable deadLockRunable2 = new DeadLockRunable();
deadLockRunable2.num = 2;
new Thread(deadLockRunable1,"李雷").start();
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(deadLockRunable2,"韩梅梅").start();
}
}
2.Lock之前记一下lambda 表达式
1中的代码通过lambda 表达式可以直接写在一个类中的:
public class MyTest {
public static void main(String[] args) {
new Thread(()->{
for(int i=0;i<100;i++) {
System.out.println("+++++++++++Runnable");
}
}).start();
new Thread(()->{
for(int i=0;i<100;i++) {
System.out.println("===========Runnable");
}
}).start();
}
}
这种方式写出来的代码既优雅又简洁,实现了任务和业务逻辑的解耦合。
3.Lock
Lock是JUC(java.util.concurrent包里的)java并发工具包。
Lock 使⽤频率最⾼的实现类是 ReentrantLock(重⼊锁),可以重复上锁。
做一个小demo熟悉一下ReentrantLock:
public class ReentrantLockTest {
public static void main(String[] args) {
Acount acount = new Acount();
new Thread(()->{
acount.lock();
}).start();
new Thread(()->{
acount.lock();
}).start();
}
}
class Acount{
private static int num;
private Lock lock = new ReentrantLock();
public void lock(){
lock.lock();
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是第"+ num + "位访客");
lock.unlock();
}
}
输出结果是正确的,说明lock有效果的。你把lock()那里去掉会发现,结果两个线程都是第一位访客。因为jvm内存模型的关系。也有可能出现并发修改错误(concurrentModifiedException).
Lock的特点:
- Lock 上锁和解锁都需要开发者⼿动完成。
- 可以重复上锁,上⼏把锁就需要解⼏把锁。
- ReentrantLock 除了可以重⼊之外,还有⼀个可以中断的特点:可中断是指某个线程在等待获取锁的过程中可以主动过终⽌线程。
public class ReentrantLockTest {
public static void main(String[] args) {
StopLock stopLock = new StopLock();
Thread t1 = new Thread(()->{
stopLock.service();
},"A");
Thread t2 =new Thread(()->{
stopLock.service();
},"B");
t1.start();
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
t2.interrupt();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class StopLock {
private ReentrantLock reentrantLock = new ReentrantLock();
public void service() {
try {
reentrantLock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "get lock");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
运行可发现,第二个线程过了一秒还没有拿到锁,就主动中断了线程,抛出InterruptedException异常。
4.总结
Synchronized和Lock的异同:
- ReentrantLock 就是对 synchronized 的升级,⽬的也是为了实现线程同步。
- ReentrantLock 是⼀个类,synchronized 是⼀个关键字。
- ReentrantLock 是 JDK 实现,synchronized 是 JVM 实现。
- synchronized 可以⾃动释放锁,ReentrantLock 需要⼿动释放。
- synchronized ⽆法判断是否获取到了锁,Lock 可以判断是否拿到了锁。
- synchronized 拿不到锁就会⼀直等待,Lock 不⼀定会⼀直等待
- synchronized 是⾮公平锁,Lock 可以设置是否为公平锁。
公平锁:很公平,排队,当锁没有被占⽤时,当前线程需要判断队列中是否有其他等待线程。
⾮公平锁:不公平,插队,当锁没有被占⽤时,当前线程可以直接占⽤,⽽不需要判断当前队列中是否
有等待线程。