private int count = 10;
public void test(){
synchronized (this) { //任何线程要执行下面的代码,必须先拿到Demo02对象实例的锁
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
public synchronized void test(){//等同于synchronized(this),锁定的是Demo03对象的实例
count --;
System.out.println(Thread.currentThread().getName() + " count =" + count);
}
synchronized是对当前对象Object加了一把锁,在当前线程使用过程中其他线程无法调用,只有上一个线程释放锁后,其他线程才可以使用,所以synchronized是互斥锁。在这里需要注意的是,snchronized锁定的是一个对象,而不是一个类或者代码,这个对象是可以自己自定义的,这里是用this锁定了自己。以上两个等同。
public static void test2(){ //考虑一下这里写synchronize(this)是否可以
synchronized (Demo04.class) {
count --;
}
}
synchronized当锁定静态方法时,那么此时锁定的当前类的class。
public class Demo05 implements Runnable{
private int count = 10;
@Override
public /*synchronized*/ void run(){
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
Demo05 demo05 = new Demo05();
for (int i = 0; i < 5; i++) {
new Thread(demo05,"THREAD" + i).start();
}
}
}
以上代码Demo05线程,当定义5个线程时,共同调用count,造成的重用,无法进行资源的共享,当添加 synchronized 时,对run方法加了把锁,一次只能一个线程进行调用,不允许其他线程访问,可避免count 的重用,线程的重入。synchronized修饰的代码块是原子操作。
注意:在多线程中,同步方法和非同步方法时可以同时调用的,这两者之间互不影响。
案例:当对业务写方法加锁,而不对读方法加锁时,容易产生脏读问题。
public class Demo08 {
String name;
double balance;
public synchronized void set(String name, double balance){
this.name = name;
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public synchronized double getBalance(String name){
return this.balance;
}
pblic static void main(String[] args) {
Demo08 demo08 = new Demo08();
new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(demo08.getBalance("zhangsan")); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(demo08.getBalance("zhangsan")); } }
在某些业务情况下,当对读取的数据要求不高时,允许脏读的情况下,可以不使用synchronized的情况下,提升性能。
一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候 仍然会得到该对象的锁也就是说synchronized获得的锁是可重入的。
public class Demo09 {
synchronized void test1(){
System.out.println("test1 start.........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test2();
}
synchronized void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2 start.......");
}
public static void main(String[] args) {
Demo09 demo09 = new Demo09();
demo09.test1();
}
}
继承中有可能发生的情形,子类调用父类的同步方法
public class Demo10 {
synchronized void test(){
System.out.println("test start........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test end........");
}
public static void main(String[] args) {
new Demo100().test();
}
}
class Demo100 extends Demo10{
@Override
synchronized void test() {
System.out.println("child test start.......");
super.test();
System.out.println("child test end.......");
}
}
总而言之:synchronized修饰的锁是可重入的,可以被其他synchronized方法调用。
死锁:简而言之,假如有一个线程需要依次锁定A和B,另一个线程需要依次锁定B和A,此时,第一个线程无法锁定B,第二个线程无法锁定A,这样就产生了死锁。
程序在执行过程中,如果出现异常,默认情况锁会被释放,所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。比如,在一个web app处理过程中,多个servlet线程共同访问通一个资源,这是如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码去,有可能访问到异常产生是的数据,因此要非常小心的处理同步业务逻辑中的异常。
public class Demo11 {
int count = 0;
synchronized void test(){
System.out.println(Thread.currentThread().getName() + " start......");
while (true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch处理,然后让循环继续
}
}
}
public static void main(String[] args) {
Demo11 demo11 = new Demo11();
Runnable r = new Runnable() {
@Override
public void run() {
demo11.test();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}