同步
-
小概念
临界资源:当多线程访问同一个对象时, 这个对象叫做临界资源
原子操作:在临界资源中不可分割的操作叫原子操作
线程不安全:多线程同时访问同一个对象, 破坏了不可分割的操作, 就可能发生数据不一致
线程安全:多个线程访问同一份资源确保资源的安全。
内存可见性:关于内存可见性问题要先从内存和cpu的配合谈起,内存是一个硬件,执行速度比CPU慢几百倍,所以在计算机中,CPU在执行运算的时候,不会每次运算都和内存进行数据交互,而是先把一些数据写入CPU中的缓存区(寄存器和各级缓存),在结束之后写入内存。这个过程是及其快的,单线程下并没有任何问题。但是在多线程下就出现了问题,一个线程对内存中的一个数据做出了修改,但是并没有及时写入内存(暂时存放在缓存中);这时候另一个线程对同样的数据进行修改的时候拿到的就是内存中还没有被修改的数据,也就是说一个线程对一个共享变量的修改,另一个线程不能马上看到,甚至永远看不到。 -
线程同步
传统的锁 synchronzied
同步代码块
每个java对象都有一个互斥锁标记,用来分配给线程,synchronized(o){ } 对o加锁的同步代码块,只有拿到锁标记的线程才能够进入对o加锁的同步代码块。
同步方法
synchronized作为方法修饰符修饰的方法被称为同步方法,表示对this加锁的同步代码块(整个方法都是一个代码块)。
JDK1.5的锁 Lock
ReentrantLock
ReentrantLock具有和synchronized相似的作用,但是更加的灵活和强大。
它是一个重入锁(synchronized也是),所谓重入就是可以重复进入同一个函数,这有什么用呢?
假设一种场景,一个递归函数,如果一个函数的锁只允许进入一次,那么线程在需要递归调用函数的时候,应该怎么办?退无可退,有不能重复进入加锁的函数,也就形成了一种新的死锁。
重入锁的出现就解决了这个问题,实现重入的方法也很简单,就是给锁添加一个计数器,一个线程拿到锁之后,每次拿锁都会计数器加1,每次释放减1,如果等于0那么就是真正的释放了锁。
//创建一个锁对象
Lock lock = new ReentrantLock();
//上锁(进入同步代码块)
lock.lock();
//解锁(出同步代码块)
lock.unlock();
//尝试拿到锁,如果有锁就拿到,没有拿到不会阻塞,返回false
tryLock();
复制代码ReadWriteLock
读写锁,读写分离。分为readLock和writeLock两把锁。对于readLock来说,是一把共享锁,可以多次分配;但是当readLock锁上的时候,调用writeLock是会阻塞的,反之亦然,另,写锁是一把普通的互斥锁,只可以分配一次。
synchronized和ReentrantLock的区别
两者都是互斥锁,所谓互斥锁:同一时间只有一个拿到锁的线程才能够去访问加锁的共享资源,其他的线程只能阻塞
都是重入锁,用计数器实现
ReentrantLock独有特点
ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁
ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程
ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制
volatile关键字
volatile 修饰符 用来保证可见性
当一个共享变量被volatile修饰的时候,他会保证变量被修改之后立马在内存中更新,另一线程在取值的时候需要去内存中读取新的值。
注意:尽管volatile 可以保证变量的内存可见性,但是不能够保存原子性,对于b++这个操作来说,并不是一步到位的,而是分为好几步的,读取变量,定义常量1,变量b加1,结果同步到内存。虽然在每一步中获取的都是变量的最新值,但是没有保证b++的原子性,自然无法做到线程安全
以下代码实现了三种同步的方式
package com.company;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程同步的运用
*
* @author XIEHEJUN
*
*/
public class SynchronizedThread {
//synchronized关键字修饰的代码块和方法
/* class Bank {
private int account = 100;
public int getAccount() {
return account;
}
*//**
* 用同步方法实现
*
* @param money
*//*
public synchronized void save(int money) {
account += money;
}
*//**
* 用同步代码块实现
*
* @param money
*//*
public void save1(int money) {
synchronized (this) {
account += money;
}
}
}*/
//使用特殊域变量(volatile)实现线程同步
/* a.volatile关键字为域变量的访问提供了一种免锁机制,
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量*/
/* class Bank {
//需要同步的变量加上volatile
private volatile int account = 100;
public int getAccount() {
return account;
}
//这里不再需要synchronized
public void save(int money) {
account += money;
}
}*/
//使用重入锁实现线程同步ReenreantLock
// 创建一个ReentrantLock实例
// lock() : 获得锁
// unlock() : 释放锁
class Bank {
private int account = 100;
//需要声明这个锁
private Lock lock = new ReentrantLock();
public int getAccount() {
return account;
}
//这里不再需要synchronized ReentrantLock() :
public void save(int money) {
lock.lock();
try {
account += money;
} finally {
lock.unlock();
}
}
}
class NewThread implements Runnable {
private Bank bank;
public NewThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// bank.save1(10);
bank.save(10);
System.out.println(i + "账户余额为:" + bank.getAccount());
}
}
}
/**
* 建立线程,调用内部类
*/
public void useThread() {
Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
System.out.println("线程1");
Thread thread1 = new Thread(new_thread);
thread1.start();
System.out.println("线程2");
Thread thread2 = new Thread(new_thread);
thread2.start();
}
public static void main(String[] args) {
SynchronizedThread st = new SynchronizedThread();
st.useThread();
}
}