1.Synchronized锁重入
关键字synchronized拥有锁重入额功能,也就是说在使用synchronized关键字的时候,当一个线程得到一个锁的对象后,再次请求此对象时是课再次得到该对象的锁。如果说在执行锁内容的过程中出现了异常的话,那么锁将会被自动释放。
示例1
/**
* synchronized的重入
*
* @author bruceliu
* @create 2019-02-26 22:39
*/
public class SynchronizedDemo1 {
public synchronized void method1() {
System.out.println("method1.." + Thread.currentThread().getName());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
method2();
}
public synchronized void method2() {
System.out.println("method2.." + Thread.currentThread().getName());
method3();
}
public synchronized void method3() {
System.out.println("method3.." + Thread.currentThread().getName());
}
public static void main(String[] args) {
final SynchronizedDemo1 sd = new SynchronizedDemo1();
Thread t1 = new Thread(new Runnable() {
public void run() {
sd.method1();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
sd.method1();
}
});
t1.start();
t2.start();
}
}
示例2
/**
* synchronized的重入
* @author bruceliu
* @create 2019-02-26 22:44
*/
public class SynchronizedDemo2 {
static class Main {
public int i = 10;
public synchronized void operationSup(){
try {
i--;
System.out.println(Thread.currentThread().getName()+"====>Main print i = " + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Sub extends Main {
public synchronized void operationSub(){
try {
while(i > 0) {
i--;
System.out.println(Thread.currentThread().getName()+"====>Sub print i = " + i);
Thread.sleep(100);
this.operationSup();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final Sub sub = new Sub();
Thread t1 = new Thread(new Runnable() {
public void run() {
sub.operationSub();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
sub.operationSub();
}
});
t1.start();
t2.start();
}
}
对于文本应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序的业务逻辑产生严重的错误,比如你现在在执行一个队列任务,很多对象都在等待第一个对象正确的执行完毕后再去释放锁,但是第一个对象由于有异常出现,导致业务逻辑没有正常的执行完,释放了锁,那么可想而知后续的对象执行的都是错误的逻辑。所以这一点一定要引起注意和关注。在编写代码的时候,一定要考虑周全。
示例3
/**
* @author bruceliu
* @create 2019-02-26 22:51
*/
public class SynchronizedException {
private int i = 0;
public synchronized void operation() {
while (true) {
try {
i++;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " , i = " + i);
if (i == 20) {
//Integer.parseInt("a");
throw new RuntimeException();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final SynchronizedException se = new SynchronizedException();
Thread t1 = new Thread(new Runnable() {
public void run() {
se.operation();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
se.operation();
}
}, "t2");
t1.start();
t2.start();
}
}
2.Synchronized代码块
使用Synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个很长的业务逻辑,那么B线程就必须等待较长的时间才能执行,这样的情况下可以使用Synchronized代码块去优化代码的执行时间,也就是说减小锁的粒度。
示例1
/**
* 使用synchronized代码块减小锁的粒度,提高性能
*
* @author bruceliu
* @create 2019-02-26 23:14
*/
public class SynchronizedDemo3 {
public void doLongTimeTask() {
try {
System.out.println("当前线程开始:" + Thread.currentThread().getName() + ", 正在执行一个较长时间的业务操作,其内容不需要同步");
Thread.sleep(2000);
synchronized (this) {
System.out.println("当前线程:" + Thread.currentThread().getName() + ", 执行同步代码块,对其同步变量进行操作");
Thread.sleep(1000);
}
System.out.println("当前线程结束:" + Thread.currentThread().getName() + ", 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final SynchronizedDemo3 otz = new SynchronizedDemo3();
Thread t1 = new Thread(new Runnable() {
public void run() {
otz.doLongTimeTask();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
otz.doLongTimeTask();
}
}, "t2");
t1.start();
t2.start();
}
}
Synchronized可以使用任意的Object进行加锁,用法比较灵活。
示例2
/**
* @author bruceliu
* 使用synchronized代码块加锁,比较灵活
* @create 2019-02-26 23:17
*/
public class ObjectLock {
public void method1(){
synchronized (this) { //对象锁
try {
System.out.println("do method1..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void method2(){ //类锁
synchronized (ObjectLock.class) {
try {
System.out.println("do method2..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private Object lock = new Object();
public void method3(){ //任何对象锁
synchronized (lock) {
try {
System.out.println("do method3..");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final ObjectLock objLock = new ObjectLock();
Thread t1 = new Thread(new Runnable() {
public void run() {
objLock.method1();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
objLock.method2();
}
});
Thread t3 = new Thread(new Runnable() {
public void run() {
objLock.method3();
}
});
t1.start();
t2.start();
t3.start();
}
}
需要额外的注意一个问题,在Java中是有常量池缓存的功能的,就是说如果我先声明了一个String str1 = “a”; 再声明一个一样的字符串的时候,取值是从原地址去取的,也就是说是同一个对象。这也就导致了在锁字符串对象的时候,可以会取得意料之外的结果(字符串一样会取得相同锁),下面看一个例子介绍
示例3
/**
* @author bruceliu
* @create 2019-02-26 23:21
* synchronized代码块对字符串的锁,注意String常量池的缓存功能
*/
public class StringLock {
public void method() {
//new String("字符串常量")
synchronized ("字符串常量") {
try {
for (int i = 1; i <=10; i++) {
System.out.println("当前线程 : " + Thread.currentThread().getName() + "开始");
Thread.sleep(1000);
System.out.println("当前线程 : " + Thread.currentThread().getName() + "结束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final StringLock stringLock = new StringLock();
Thread t1 = new Thread(new Runnable() {
public void run() {
stringLock.method();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
stringLock.method();
}
}, "t2");
t1.start();
t2.start();
}
}
锁对象改变的问题,当时用一个对象进行加锁的时候,要注意对象本身发生改变,那么一旦对象改变,持有的锁就不同,如果对象本身没有发生改变,那么依然是同步的,及时是对象的属性发生了修改。
示例4
/**
* 同一对象属性的修改不会影响锁的情况
* @author bruceliu
* @create 2019-02-26 23:39
*/
public class ModifyLock {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public synchronized void changeAttributte(String name, int age) {
try {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 开始");
this.setName(name);
this.setAge(age);
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 修改对象内容为: "
+ this.getName() + ", " + this.getAge());
Thread.sleep(2000);
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final ModifyLock modifyLock = new ModifyLock();
Thread t1 = new Thread(new Runnable() {
public void run() {
modifyLock.changeAttributte("张三", 20);
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
modifyLock.changeAttributte("李四", 21);
}
}, "t2");
t1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
死锁问题:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
当然死锁的产生是必须要满足一些特定条件的:
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
示例5
/**
* 死锁问题,在设计程序时就应该避免双方相互持有对方的锁的情况
* @author bruceliu
* @create 2019-02-26 23:41
*/
public class DeadLock implements Runnable{
private String tag;
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public void setTag(String tag){
this.tag = tag;
}
public void run() {
if(tag.equals("a")){
synchronized (lock1) {
try {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock1执行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock2执行");
}
}
}
if(tag.equals("b")){
synchronized (lock2) {
try {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock2执行");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock1执行");
}
}
}
}
public static void main(String[] args) {
DeadLock d1 = new DeadLock();
d1.setTag("a");
DeadLock d2 = new DeadLock();
d2.setTag("b");
Thread t1 = new Thread(d1, "t1");
Thread t2 = new Thread(d2, "t2");
t1.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}