synchronized的用法

多个线程去共享资源、一段代码可能会出现一些不能预料的问题。就比如模拟三个窗口同时售票

package com.smallchili.demo;

public class CreateThread {
	public static void main(String[] args) {						
		ThreadDemo2 t = new ThreadDemo2();
		Thread t1 = new Thread(t,"[窗口1]");//创建线程1实例 
		Thread t2 = new Thread(t,"[窗口2]");//创建线程2实例
		Thread t3 = new Thread(t,"[窗口3]");//创建线程3实例
		
		t1.start();//启动线程2
		t2.start();//启动线程2
		t3.start();//启动线程3
		
	}

}

class ThreadDemo2 implements Runnable{
	
    private int tickets = 10;
    Object object = new Object();
    
	@Override
	public  void run() { 
		while(tickets > 0){	
			tickets--;
			System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数="+tickets);	    			
			}		
	} 
	
 }

结果

[窗口2]卖了一张票,剩余票数=8
[窗口3]卖了一张票,剩余票数=7
[窗口1]卖了一张票,剩余票数=8
[窗口3]卖了一张票,剩余票数=5
[窗口3]卖了一张票,剩余票数=3
[窗口2]卖了一张票,剩余票数=6
[窗口3]卖了一张票,剩余票数=2
[窗口1]卖了一张票,剩余票数=4
[窗口3]卖了一张票,剩余票数=0
[窗口2]卖了一张票,剩余票数=1

结果中第一行就出现了问题,窗口1卖了一张票应该是剩余9张而不是八张,为什么会出现这种情况呢?因为线程2进入了run()方法执行了tickets–,tickets变为9,还没执行到输出,另一个线程又执行了tickets–,tickets变成了8。这时线程2执行到了输出拿到的值是8。

这是多个线程会出现的问题,java提供synchronized关键字来帮助处理多个线程同时访问资源出现的问题,简单来说synchronized关键字是用来上锁的,。保证同一时刻只能有一个线程执行该方法或者某段代码,这样就避免了多个线程同时对资源读写而产生的问题。

synchronized关键字的几种用法

synchronized关键字有四种实现方式
分为两大类,对象锁类锁,两者的区别就是对象锁只是锁某个对象里的方法或代码块,该类的其他对象一样可以获得锁,而类锁是锁了整个类,即使不同对象,保证同一时刻而只能有一个对象一个线程获得锁。

synchronized对象锁有两种实现方法:

  1. 同步代码块锁
  2. 方法锁

1.同步代码块锁

同一时刻,只能有一个线程访问该代码块。
使用方式

...
Object lock = new Object();//创建一个对象
...
synchronized(lock){//需要传入一个对象
//里面放的是要锁住的代码
}

或者


...
synchronized(this){//需要传入一个对象
//里面放的是要锁住的代码
}
...

上面的模拟三个窗口售票加同步代码块锁

class ThreadDemo2 implements Runnable{
	
    private int tickets = 10;
    Object lock = new Object();
    
	@Override
	public  void run() { 
		while(tickets > 0){	
		synchronized(lock){  //同步代码块锁
			if(tickets > 0){
			tickets--;
			System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数="+tickets);	    			
			  }else{
				  System.out.println("票已售完..");
			  }
			}
			
		}
		
	} 
	
 }

2.synchronized关键字加在普通方法上

方法锁和同步代码块锁都是对象锁,区别是方法锁锁了整个方法,而同步代码块可以只锁一小段代码,控制粒度不一样。
用法:

public  synchronized void todoSomething() { 
...
}

3.synchronized关键字加在静态方法上

synchronized关键字加在普通方法和加在静态方法上的含义是不同的,加在静态方法上是类锁。
用法:

public  static synchronized void todoSomething() { 
...
}

4.synchronized(类名.class)代码块

这种用法也是属于类锁。
用法:

public  void todoSomething() { 
...
synchronized(类名.class){
//代码块
}
...
}

拓展

阐述一下我的理解:
所谓的锁就是我假设为一个flag变量,在线程运行到该代码块时或先检查该锁是否被占有,如果没有被占用(flag=0,即锁是打开),那么该线程就可以进入去执行该段代码,并且占用该锁(把flag设为1,锁上),此时如果第二个线程执行到该代码,也会先判断当前锁的状态,发现锁是被占有的(flag=1,即锁是关闭的)就会等待,等待占有锁的线程释放锁。

java中通过synchronized关键字来上锁而已是传入一个对象参数,像synchronized(锁){代码块}这种形式,如果是

  • synchronized(this){代码块}这种就是对象锁。
  • 像 synchronized(类名.class){代码块}这种就是类锁

区别不难看出参数的差异,一个是传唯一的
看个简单的例子吧

package com.smallchili.demo;

public class SynchronizedDemo {

	public static void main(String[] args) {
		 
		ThreadDemo t = new ThreadDemo();
		//t1,t2共享一个ThreadDemo对象
		Thread t1 = new Thread(t,"[线程1]");
		Thread t2 = new Thread(t,"[线程2]");
	
		t1.start();
		t2.start();
	}	

}

class ThreadDemo implements Runnable{
	@Override
	public void run() {
	  System.out.println(Thread.currentThread().getName()+"begin..");
	  System.out.println(Thread.currentThread().getName()+"end..");
	}
	
}

简单创建了两个共享代码的线程,不加锁的情况可能出现以下情况

[线程2]begin…
[线程1]begin…
[线程1]end…
[线程2]end…

是不安全的。
加了对象锁

class ThreadDemo implements Runnable{
	@Override
	public void run() {
	synchronized (this) {//使用当前对象作为锁
	  System.out.println(Thread.currentThread().getName()+"begin..");
	  System.out.println(Thread.currentThread().getName()+"end..");		
	    }
	}
	
}

保证了同一ThreadDemo 对象的线程不互相干预,加了锁不会出现上面的问题

[线程1]begin…
[线程1]end…
[线程2]begin…
[线程2]end…

如果是两个不同的ThreadDemo实例对象间呢,他们会不会互相干扰?

package com.smallchili.demo;

public class SynchronizedDemo {

	public static void main(String[] args) {
		//两个不同实例的ThreadDemo td1,td2
		ThreadDemo td1 = new ThreadDemo();
		ThreadDemo td2 = new ThreadDemo();
		//td1的线程
		Thread t1 = new Thread(td1,"[线程1]");
		Thread t2 = new Thread(td1,"[线程2]");
		//td2的线程
		Thread t3 = new Thread(td2,"[线程3]");//创建线程3实例 
		Thread t4 = new Thread(td2,"[线程4]");//创建线程4实例
		//启动
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}	

}

class ThreadDemo implements Runnable{
	
	@Override
	public void run() {
	synchronized (this) {
	  System.out.println(Thread.currentThread().getName()+"begin..");
	  System.out.println(Thread.currentThread().getName()+"end..");		
	    }
	}
	
}

结果是当然,如下线程1还没执行完线程3、4就中途插入,没有保证同一时刻一个线程访问该代码块。之所以出现这种情况是因为加的是对象锁,不同实例之间获取的锁是不同的。上面的代码他们synchronized关键字 传入的锁对象不一样,线程1,2的锁是synchronized (对象td1)。而3,4的锁是synchronized (对象td2)。即每个ThreadDemo对象都是不一样的锁。

也就是说他们的锁不一样而导致他们同一时刻可以并行执行。

[线程1]begin…
[线程3]begin…
[线程3]end…
[线程4]begin…
[线程4]end…
[线程1]end…
[线程2]begin…
[线程2]end…

这时可以改为用类锁,所谓的类锁,就是让不同对象都使用同一把锁
修改synchronized (this)为synchronized (ThreadDemo.class)

class ThreadDemo implements Runnable{
	
	@Override
	public void run() {
	synchronized (this) {
	  System.out.println(Thread.currentThread().getName()+"begin..");
	  System.out.println(Thread.currentThread().getName()+"end..");		
	    }
	}
	
}

运行结果

[线程1]begin…
[线程1]end…
[线程4]begin…
[线程4]end…
[线程3]begin…
[线程3]end…
[线程2]begin…
[线程2]end…

不同对象之间使用同一把锁了,这就是类锁。
很容易懂synchronized (ThreadDemo.class)里的ThreadDemo.class是唯一的,一个类可以有很多个实例对象,但只有一个类对象(类名.class)

按照这个理解,我大开脑洞,发现类锁也可以这样写:

class ThreadDemo implements Runnable{
	private static String myLock = "I am Lock";//静态变量是所有对象共享的
	
	@Override
	public void run() {
	synchronized (myLock) {//用静态字符串对象作为锁
	  System.out.println(Thread.currentThread().getName()+"begin..");
	  System.out.println(Thread.currentThread().getName()+"end..");		
	    }
	}
	
}

上面的myLock 是一个字符串对象。
还可以这样

synchronized ("我是类锁") {
	  System.out.println(Thread.currentThread().getName()+"begin..");
	  System.out.println(Thread.currentThread().getName()+"end..");		
	    }

虽然可以这样写但是不建议,因为没见过这样写的,只是拿来理解而已。
同样还有synchronized关键字加在方法上和加在静态方法上的区别,就不详细讲了。加在普通方法上默认是使用this加锁属于对象锁,加在静态方法上,由于静态方法是类共享的所以锁也是一样的,属于类锁。

如果再深入猜想,锁也许是内存地址,不好说,就到这里,喜欢我可以给我转qqq1726581875
【完】

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值