一、为什么要使用线程同步
1、什么是同步
同步就是协同步调,按预定的先后次序进行运行。如:你用完,其它人才能用。“同”字从字面上容易理解为一起
其实不是,“同”字应是指协同、协助、互相配合。
当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。
2、如果不用同步产生的问题
当多个线程同时读写同一份共享资源的时候,有可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到。比如下面不使用同步会产生的问题
-
举个栗子
public class Bank {
/** * 账户余额 */ private int count = 0; /** * 存钱 * * @param money */ public void addMoney(int money) {
count += money; System.out.println(System.currentTimeMillis() + "存进:" + money); } /** * 取钱 * * @param money */ public void delMoney(int money) {
if (count - money < 0) {
System.out.println("余额不足"); return; } count -= money; System.out.println(System.currentTimeMillis() + "取出:" + money); } /** * 查询 */ public void lookMoney() {
System.out.println("账户余额:" + count); } }
// 两个线程 不停的存钱 取钱 public class SyncThreadBankExample { public static void main(String[] args){ final Bank bank=new Bank(); // 存钱线程 Thread tadd=new Thread(() -> { while(true){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 存100块到账户 bank.addMoney(100); // 查看余额 bank.lookMoney(); System.out.println("\n"); } }); // 取钱的线程 Thread tsub = new Thread(() -> { while(true){ bank.delMoney(100); bank.lookMoney(); System.out.println("\n"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }); tsub.start(); tadd.start(); } }
1564304836893取出:100 账户余额:1200 1564304836893存进:100 账户余额:1200
3、什么时候使用同步
-
多个线程执行的时候需要同步,如果是单线程则不需要同步。
-
多个线程在执行的过程中是不是使用同一把锁。
4、常见的同步方案
- synchronized(同步)
- ReentrantLock (可重入锁)
- Atomic( 原子类,效率较高,可用于优化 )
- ThreadLocal (线程本地变量)
二、同步(synchronized)
1、说明
synchronized 是 Java 中的关键字,是利用锁的机制来实现同步的。synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,这是synchronized实现同步的基础。
每个进程中访问临界资源的那段代码称为临界区(临界资源是同一时刻一次仅允许一个进程使用的共享资源)
2、锁机制有如下两种特性:
-
互斥性:
即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。
-
可见性
必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。
3、类锁和对象锁
-
类锁
在代码中的方法上加了static和synchronized
的锁,或者synchronized(xxx.class)
-
对象锁
在代码中的方法上加了synchronized
的锁,或者synchronized(this)
的代码段
-
注意
- 类锁和对象锁不会产生竞争,二者的加锁方法不会相互影响。
- 一个实例对象一把锁,多个实例对象多把锁.多线程解决高并发只能通过一个加锁实例实现
4、根据修饰对象用法分类
- 修饰代码块
- synchronized(this|object) {}
- synchronized(类.class) {}
- 修饰方法