1. synchronized对某个对象加锁
public class Demo01 {
private int count = 10;
private Object object = new Object();
public void test(){
//任何线程要执行下面的代码,必须先拿到object对象的锁
synchronized (object) {
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
知识点:
1.synchronized关键字锁定的是对象不是代码块,demo中锁的是object对象的实例
2.锁定的对象有两种情况:①类的实例 ②类的字节码(.class)
3.关于线程安全:加synchronized关键字之后不一定能实现线程安全,具体还要看锁定的对象是否唯一。demo中如果只开启一条线程进行访问时是线程安全的,但是如果开启不同的两条线程同时访问时就会出现线程不安全的问题。
2. synchronized锁定当前对象
public class Demo02 {
private int count = 10;
public void test(){
synchronized (this) { //任何线程要执行下面的代码,必须先拿到Demo02对象实例的锁
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
知识点:
1.synchronized(this)锁定的是当前类的实例,demo中锁定的是Demo02类的实例
2.此demo中如果Demo02类是单例的话可以保证在多线程访问时是线程安全的, 如果存在有多个Demo02的实例的话在多线程中不能保证线程安全,因为方法中的锁不唯一了。
3. synchronized关键字修饰普通方法等同于synchronized(this)。
public class Demo03 {
private int count = 10;
public synchronized void test(){//等同于synchronized(this),锁定的是Demo03对象的实例
count --;
System.out.println(Thread.currentThread().getName() + " count =" + count);
}
}
4. synchronize关键字修饰静态方法
public class Demo04 {
private static int count = 10;
public synchronized static void test1(){ //这里等同于synchronized(Demo04.class)
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void test2(){ //考虑一下这里写synchronize(this)是否可以
synchronized (Demo04.class) {
count --;
}
}
}
知识点:
1.synchronize关键字修饰静态方法锁定的是类的.class文件
2.静态方法中synchronize锁定代码块,锁定的对象不能是类的实例,只能是类的.class文件。原理如同在静态方法中不能直接调用非静态方法
3.类的.class文件是唯一的,所以说synchronize修饰静态方法或者锁定的对象是类的.class文件的时候在多线程中是可以实现线程安全的
5. synchronize与线程安全
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();
}
}
}
知识点:
1.run()方法没加synchronized关键字时,多个线程同时访问count,线程是不安全的
2.run()方法加上synchronized关键字后,锁定的是Demo05对象的实例,因为只创建了 一个Demo05的实例,多个线程访问时都要拿到Demo05的锁标记才能执行,在多个线程同时访问时也是线程安全的。
6. 同步方法和非同步方法可以同时调用
public class Demo07 {
public synchronized void test1(){
System.out.println(Thread.currentThread().getName() + " test1 start..........");
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " test1 end........");
}
public void test2(){
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "test2 execute......");
}
public static void main(String[] args) {
Demo07 demo07 = new Demo07();
new Thread(demo07 :: test1,"t1").start(); //JDK1.8新特性
new Thread(demo07 :: test2,"t2").start(); //JDK1.8新特性
}
}
7.对业务写方法加锁,同时也要对业务读方法加锁,否则容易产生脏读问题
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;
}
public 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"));
}
}
8.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();
}
}
知识点:
1.一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁,也就是说synchronized获得的锁是可重入的
9.子类调用父类同步方法
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.......");
}
}
注意:上面程序运行过程中父类方法锁定的对象仍是子类对象,和同步方法调用另一个同步方法类似。
10.异常会导致锁被释放
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();
}
}
注意:程序在执行过程中,如果出现异常,默认情况锁会被释放。所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。比如在一个web app处理过程中,多个servlet线程共同访问通一个资源,这是如果异常处理不合适在第一个线程中抛出异常,其他线程就会进入同步代码去,有可能访问到异常产生是的数据。因此要非常小心的处理同步业务逻辑中的异常。
11.同步代码块中的语句越少越好
public class Demo16 {
int count = 0;
public synchronized void test1() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
count++;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void test2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
//采用细粒度的锁,可以是线程争用时间变短,从而提高效率
synchronized (this) {
count++;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
说明:采用细粒度的锁,可以是线程争用时间变短,从而提高效率
12.避免将锁定对象的引用指向另外一个对象
public class Demo17 {
Object o = new Object();
public void test() {
synchronized (o) {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
Demo17 demo17 = new Demo17();
//启动第一个线程
new Thread(demo17::test, "t1").start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动第二个线程
Thread t2 = new Thread(demo17::test, "t2");
//锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程t2将永远得不到执行机会
demo17.o = new Object();
t2.start();
}
}
说明:锁定某对象o,如果o的属性发生改变,不影响锁的使用。但是如果o变成另外一个对象,则锁定的对象发生改变,新指向的对象未上锁。 应该避免将锁定对象的引用变成另外一个对象
13.不要以字符串常量作为锁定的对象
public class Demo18 {
String s1 = "hello";
String s2 = "hello";
public void test1() {
synchronized (s1) {
}
}
public void test2() {
synchronized (s2) {
}
}
}
在上面的例子中,test1和test2其实锁定的是同一个对象。这种情况还会发生比较诡异的现象,比如你用到了一个类库,在该类库中代码锁定了字符串”hello”, 但是你读不到源码,所以你在自己的代码中也锁定了”hello”,这时候就有可能发生非常诡异的死锁阻塞,因为你的程序和你用的的类库不经意间使用了同一把锁。