synchronized笔记
线程安全
主要诱因
- 存在共享数据(也称临界资源)
- 存在多条线程共同操作共享数据
解决问题根本
同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据进行操作。所以可以引入互斥锁去解决问题
互斥锁
在 Java 中,关键字synchronized
可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized
另外一个重要的作用,synchronized
可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile
功能),这点确实也是很重要的
定义
Java语言的关键字,可用来给对象和方法或者代码块加锁(注意,这里加锁是说可以用在对象或代码块上面,但是synchronized
锁的是对象,不是代码,重要!!!),当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。
当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。
分类
根据获取的锁的分类:获取对象锁和类锁
对象锁
获取对象锁的两种用法:
- 同步代码块(
synchronized(this)
,synchronized(类实例对象)
),锁是小括号()中的实例对象。
- 同步非静态方法(
synchronized method
),锁是当前对象的实例对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package indi.greenhat.thread;
public class Synchronized_01 { private int count = 10; private Object obj = new Object();
public void test(){ synchronized (obj){ count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } } }
|
类锁
获取类锁的两种用法:
- 同步代码块(
synchronized(类.class)
),锁是小括号()中的类对象(Class
对象)。
- 同步静态方法(
synchronized static method
),锁是当前对象的类对象(Class对象)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package indi.greenhat.thread;
public class Synchronized_02 { private static int count = 10;
public synchronized static void test1(){ count--; System.out.println(Thread.currentThread().getName() + " count = " + count); }
public static void test2(){ synchronized (Synchronized_02.class){ count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } } }
|
同步与非同步混用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package indi.greenhat.thread;
public class Synchronized_03 {
String name;
double balance;
public synchronized void set(String name, double balance){ this.name = name; try{ Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } this.balance = balance; }
public double getBalance(){ return this.balance; }
public static void main(String[] args) { Synchronized_03 account = new Synchronized_03(); new Thread(()->account.set("zhangsan", 100.0)).start();
try{ Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); }
System.out.println(account.getBalance());
try{ Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); }
System.out.println(account.getBalance()); } }
|
只对写加锁,而不对读加锁,可能产生脏读现象,虽然锁了set()
,但是getBalance()
并没有。所以正确的思路就是对读和写都加锁。
可重入
一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁,也就是说synchronized
获得的锁是可重入的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package indi.greenhat.thread;
public class Synchronized_04 {
synchronized void m1(){ System.out.println("m1 start"); try{ Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); } m2(); System.out.println("m1 end"); }
synchronized void m2(){ System.out.println("m2 start"); try{ Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); }
System.out.println("m2 end"); }
public static void main(String[] args) { new Synchronized_04().m1(); } }
|
1 2 3 4
| m1 start m2 start m2 end m1 end
|
参考:
Java并发编程—synchronized保证线程安全的原理分析 - 掘金