synchronized同步锁
synchronized同步锁
在多线程运行环境中,因为存在资源共享与竞争,为了合理分配资源以及公平地使用资源,所以需要锁。
多线程情况下,不设置锁出现的问题
public class Test02 {
public static void main(String[] args) {
Thread add = new AddThread();
Thread dec = new DecThread();
//启动线程
add.start();
dec.start();
// 插队
try {
add.join();
dec.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Counter.count);
}
}
class Counter{
public static int count = 0;
}
class AddThread extends Thread{
@Override
public void run() {
for(int i = 0 ;i< 10000; i++) {
Counter.count += 1;
}
}
}
class DecThread extends Thread{
@Override
public void run() {
for(int i = 0 ;i< 10000; i++) {
Counter.count -= 1;
}
}
}
代码执行结果如下:
结果出现异常的图解:
小结:按照正常的逻辑,从0
加10000
次,在减少10000
次,最后得到的值应该为0
,但是由于多线程并发执行,导致数据不准确,相互之间产生冲突,使得最后获取的结果不是理想的结果,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了唯一性和准确性。(优化代码 在 synchronized 锁 用法 中)
synchronized原理
每一个Java对象都可以关联一个Monitor对象,当使用synchronized关键字的时候,该对象就会与一个Monitor对象关联,并且通过monitorenter/monitorexit指令实现监视器机制。
监视器(monitor)
监视器的获取步骤
- 执行monitorenter指令,判断当前monitor监视器的线程进入数量:如果为0,则该线程直接进入监视器owner,代表该线程拥有监视器,同时进入数设置为1;
- 如果线程已经拥有该monitor监视器,重新进入,则进入数+1;如果其它线程尝试获取监视器,则进入阻塞区,线程进入阻塞状态,直到监视器的进入数为0;
- 执行monitorexit指令,进入数-1
synchronized 锁使用
synchronized 的基本用法有以下 3 种:
- 修饰静态方法
- 修饰实例方法
- 修饰代码块()
代码块
通过一个公共对象,作为“锁”
1.创建一个Object类型的公共对象,作为锁实现
class Counter{
//方式1:创建一个公共对象
public static int count = 0;
//锁
public static final Object LOCK = new Object();
}
class AddThread extends Thread{
@Override
public void run() {
for(int i = 0 ;i< 10000; i++) {
//方式1
synchronized (Counter.LOCK) {
Counter.count += 1;
}
}
}
}
class DecThread extends Thread{
@Override
public void run() {
for(int i = 0 ;i< 10000; i++) {
//方式1
synchronized (Counter.LOCK) {
Counter.count -= 1;
}
}
}
}
class Counter{
//只具备原子性的AtomicInteger
public static AtomicInteger count =new AtomicInteger();
}
class AddThread extends Thread{
@Override
public void run() {
for(int i = 0 ;i< 10000; i++) {
Counter.count.incrementAndGet(); //自增
}
}
}
class DecThread extends Thread{
@Override
public void run() {
for(int i = 0 ;i< 10000; i++) {
Counter.count.decrementAndGet(); //自减
}
}
}
添加synchronized锁以后,解决了同步问题,这时获取的结果,就是正常的。
实例方法
通过当前对象this,作为“锁”
public void dosth1() {
//方式1 :使用this当前对象,作为锁
synchronized (this) {
System.out.println("....");
}
}
//实例方法
// 方式2 :使用synchronized关键字(等同于使用this作为锁)
public synchronized void dosth2() {
}
静态方法
通过当前类Class对象,作为“锁”
public static void dosth1() {
//方式1 : 使用当前类Class对象,作为锁
synchronized (Example2.class) {
System.out.println("....");
}
}
//静态方法
//方式2 :使用synchronized关键字(等同于使用类Class对象作为锁)
public synchronized static void dosth2() {
}
案例
模拟卖票场景,此时每个窗口视为一个线程,当每卖出一张票,让子线程休眠1000毫秒,代码实现如下:
public static void main(String[] args) {
TicketPool ticketPool = new TicketPool(20);
new Thread(ticketPool,"窗口1").start();
new Thread(ticketPool,"窗口2").start();
new Thread(ticketPool,"窗口3").start();
}
class TicketPool implements Runnable {
// 当前剩余门票数
private int ticketNum;
// 初始化
public TicketPool(int ticketNum) {
this.ticketNum = ticketNum;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始卖票了");
synchronized (this) {
while (true) {
if (ticketNum <= 0) {
System.out.println("门票已买完。。。");
return;
} else {
System.out.println(Thread.currentThread().getName()+"卖出一张票," + "剩余" + --ticketNum + "张票");
try {
// 休眠过程中,当前线程不会让出持有的("this")锁,此时会出现异常情况,只有一个线程在执行,剩余线程处于Blockteb阻塞状态
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
// 计时等待过程中,当前线程会释放锁
this.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
当子线程sleep休眠时,结果如下
当子线程wait等待时,结果如下
综上所述, 在sleep休眠过程中,当前线程不会让出持有的(“this”)锁,此时会出现异常情况,只有一个线程在执行,剩余线程处于Blockteb阻塞状态,而在wait计时等待过程中,当前线程会释放锁
锁升级(锁膨胀)
偏向(斜)锁
只有一个线程执行的场景,使用偏向锁;
轻量级锁(不能处理并发)
发生多个线程执行的场景,偏向锁升级为轻量级锁;
重量级锁
发现多个线程并发执行的场景,轻量级锁升级为重量级锁;
通过操作系统的“互斥锁”实现。互斥锁实现线程之间的切换,需要从“用户态”切换到“内核态”,付出高昂的代价,会导致性能下降。
同步时的通信
-
wait() :必须通过 notify() / notifyAll()
-
wait(等待时间) :达到计时时间后,自动唤醒
案例
请按照 12A 34B 56C 。。。
输出
思路:将数字,和字母分开,一个线程控制数字输出,另一个线程控制字母输出,即输出数字后,数字线程进入等待,同时唤醒字母线程,反之,输出字母时,字母程进入等待,同时唤醒数字线程。
具体实现如下:
Main
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(new Number(lock),"数字");
Thread t2 = new Thread(new Character(lock),"字母");
t1.start();
t2.start();
}
字母类
public class Character implements Runnable {
private final Object LOCK;
public Character(Object lock) {
LOCK = lock;
}
@Override
public void run() {
synchronized (LOCK) {
for (char c = 'A'; c <= 'Z'; c++) {
System.out.print(c);
//唤醒处于等待状态的所有线程(数字线程)
LOCK.notifyAll();
if(c < 'Z' ){
try {
//字母线程进入等待
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
数字类
public class Number implements Runnable {
private final Object LOCK;
public Number(Object lock) {
LOCK = lock;
}
@Override
public void run() {
synchronized (LOCK) {
for(int i = 1 ;i<= 52;i++) {
if(i % 2 == 1) {
System.out.print(" ");
}
System.out.print(i);
if(i % 2 == 0) {
//唤醒处于等待状态的所有线程(字母线程)
LOCK.notifyAll();
try {
// 数字线程进入等待
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
结果如下: