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;
            }
        }
    }
}
  1. 使用线程安全的类型来存储内容,不用自己实现锁(只具备原子性的AtomicInteger)
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();
					}
				}
			}
		}
	}
}

结果如下:

在这里插入图片描述

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值