采用互斥的方式,让同一时刻最多只有一个线程能持有这把锁,其他线程再想获取这把锁就会被阻塞住,这样就可以保证拥有锁的线程可以唯一执行临界区的代码
锁住Object类对象
Object object=new Object();
synchronized(object)
{
}
锁住普通方法
public synchronized void buy()
{
}
锁住static方法:
public static synchronized void buy()
{
}
Synchronized关键字使用的四种情景:
(1)锁住我们自己new出来的一个Object对象
public class test
{
//lock是一个Object类型的对象
Object lock=new Object();
public void methodA()
{
//锁住Object对象lock,然后把代码放到这里面
synchronized(lock)
{
pass;
}
}
}
(2)锁住当前对象this
public class test
public void methodA()
{
synchronized(this)
{
pass;
}
}
}
下面两种加在方法前面的方式,本质还是锁住当前对象this,可以改成锁住this的等价方式
也就是说:synchronized只能锁对象,把synchronized加在方法(不管是静态方法还是非静态方法)前面,其实锁住的是this对象
(3)加在成员方法(也就是非static方法)前面
public class test
{
public synchronized void methodA()
{
}
}
//等价于
public class test
{
public void methodA()
{
synchronized(this)
{
..........
}
}
}
(4)加在static静态方法上
public class test
{
public static synchronized void methodA()
{
}
}
//等价于
public class test
{
public static void methodA()
{
synchronized(this)
{
........
}
}
}
实际案例:
public class test
{
static int counter=0;
public static void main(String[] args) throws InterruptedException
{
Thread thread1=new Thread(()->{
for(int i=0;i<5000;i++)
{
counter++;
}
} );
Thread thread2=new Thread(()->{
for(int i=0;i<5000;i++)
{
counter--;
}
} );
thread1.start();
thread2.start();
Thread.sleep(10000);
System.out.println(counter);
}
}
方式一:将我们自己new出来的Object类对象作为锁
public class test
{
static int counter=0;
static Object lock=new Object();
public static void main(String[] args) throws InterruptedException
{
Thread thread1=new Thread(()->{
for(int i=0;i<5000;i++)
{
synchronized (lock)
{
counter++;
}
}
} );
Thread thread2=new Thread(()->{
for(int i=0;i<5000;i++)
{
synchronized(lock)
{
counter--;
}
}
} );
thread1.start();
thread2.start();
Thread.sleep(10000);
System.out.println(counter);
}
}
方式二:将当前对象this作为锁
class Room
{
private int counter=0;
public void add()
{
synchronized (this)
{
counter++;
}
}
public void minus()
{
synchronized (this)
{
counter--;
}
}
public int getCounter()
{
synchronized (this)
{
return counter;
}
}
}
public class test
{
public static void main(String[] args) throws InterruptedException
{
Room room=new Room();
Thread thread1=new Thread(()->{
for(int i=0;i<5000;i++)
{
room.add();
}
} );
Thread thread2=new Thread(()->{
for(int i=0;i<5000;i++)
{
room.minus();
}
} );
thread1.start();
thread2.start();
Thread.sleep(10000);
System.out.println(room.getCounter());
}
}
方式三:把synchronized加在非static方法前面
class Room
{
private int counter=0;
public synchronized void add()
{
counter++;
}
public synchronized void minus()
{
counter--;
}
public synchronized int getCounter()
{
return counter;
}
}
public class test
{
public static void main(String[] args) throws InterruptedException
{
Room room=new Room();
Thread thread1=new Thread(()->{
for(int i=0;i<5000;i++)
{
room.add();
}
} );
Thread thread2=new Thread(()->{
for(int i=0;i<5000;i++)
{
room.minus();
}
} );
thread1.start();
thread2.start();
Thread.sleep(10000);
System.out.println(room.getCounter());
}
}
构造方法不能使用 synchronized 关键字修饰。
构造方法本身就属于线程安全的,不存在同步的构造方法一说
一个实例:三个线程来模拟三个窗口卖票
public class Test
{
public static void main(String[] args) throws InterruptedException
{
Ticket ticket=new Ticket();
Thread t1= new Thread(ticket,"t1");
Thread t2=new Thread(ticket,"t2");
Thread t3=new Thread(ticket,"t3");
t1.start();
t2.start();
t3.start();
}
static class Ticket implements Runnable
{
int ticket=100;
@Override
public void run()
{
while(true)
{
if(ticket>0)
{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+" is selling ticket"+ticket--);
}
}
}
}
}
出现了-1
方法一,new一个Object类对象,然后对这个对象上锁,synchronized锁住一个对象
public class Foo
{
public static void main(String[] args) throws InterruptedException
{
Ticket ticket=new Ticket();
Thread t1= new Thread(ticket,"t1");
Thread t2=new Thread(ticket,"t2");
Thread t3=new Thread(ticket,"t3");
t1.start();
t2.start();
t3.start();
}
static class Ticket implements Runnable
{
int ticket=100;
Object object=new Object();
@Override
public void run()
{
synchronized(object)
{
while(true)
{
if(ticket>0)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+" is selling ticket"+ticket--);
}
}
}
}
}
}
但这样只有1个线程可以卖票
方法二: synchronized锁住一个方法:
public class Test
{
public static void main(String[] args) throws InterruptedException
{
Ticket ticket=new Ticket();
Thread t1= new Thread(ticket,"t1");
Thread t2=new Thread(ticket,"t2");
Thread t3=new Thread(ticket,"t3");
t1.start();
t2.start();
t3.start();
}
static class Ticket implements Runnable
{
int ticket=100;
@Override
public void run()
{
buy();
}
public synchronized void buy()
{
while(true)
{
if(ticket>0)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+" is selling ticket"+ticket--);
}
}
}
}
}
这样也只有1个线程可以卖票
方法三:使用Reentrantlock进行加锁
public class Test
{
public static void main(String[] args) throws InterruptedException
{
Ticket ticket=new Ticket();
Thread t1= new Thread(ticket,"t1");
Thread t2=new Thread(ticket,"t2");
Thread t3=new Thread(ticket,"t3");
t1.start();
t2.start();
t3.start();
}
static class Ticket implements Runnable
{
int ticket=100;
Reentrantlock lock=new Reentrantlock();
@Override
public void run()
{
while(true)
{
lock.lock()
if(ticket>0)
{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+" is selling ticket"+ticket--);
}
lock.unlock();
}
}
}
}
sychronized的两个特性:可重入性和不可中断性
可重入性:
可以重复拿到一把锁(一把锁拿两次)
Object obj=new Obj();
synchronized(obj)
{
synchronized(obj)
{
.......
}
}
可重入特性可以避免死锁
如果没有可重入性,第一次拿到锁之后就会被阻塞住,无法第二次拿到锁
被锁住的对象内部有一个计数器记录线程获取几次锁了
synchronized经常会和单例模式(加锁双重校验懒汉模式)结合起来考察
synchronized原理
synchronized(obj)
{
.......
}
被锁住的Object类对象会关联(引用)一个monitor对象(monitor对象也会引用这个Object类对象),这个才是真正的锁,不是java对象,而是一个c++对象,这个对象不是我们主动创建的,而是由jvm创建的
这个monitor对象里面有两个重要的成员变量:owner 拥有锁的线程,recursions 记录一个线程拿了几次锁(可重入的话,recursion可能为2)
假设t1线程来执行,那monitor对象的owner就会变成t1
如果此时t2线程来,发现monitor对象的owner是t1,不是自己,那t2就会进入阻塞状态
多个线程竞争锁,没有竞争到锁的线程会进入EntryList阻塞队列,进入blocked状态,拿到锁的线程调用wait()方法后,进入WaitSet等待队列,进入waiting等待状态,有其他线程调用notify()方法唤醒处于waiting的线程,被唤醒的线程就会进入EntryList阻塞队列进入阻塞状态共同竞争锁
sunchronized锁升级:
jdk1.6之前,synchronized使用的就是重量级锁
1.6之后就有个锁升级(也叫锁膨胀)的过程:
(1)任何对象都能充当锁,你刚把对象创建出来的时候,对象处于无锁的状态
(2)当有一个线程(只有一个线程)过来想要获取锁的时候,这个充当锁的对象就要升级成偏向锁
偏向锁认为:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得这把锁,当这个线程再次请求锁的时候,直接将锁给它
(3)当有多个线程竞争锁的时候,升级成轻量级锁(也叫自旋锁),没拿到锁的线程进行自旋(忙循环),自旋完再尝试去拿锁,如果此时发现所还是被占用不可获得,继续自旋
没有抢到锁的线程不会进入到阻塞态(因为线程切换到阻塞态的开销很大,所以尽量避免进入阻塞状态),自旋锁认为锁只会被拿走很短的时间,过了这个很短的时间拿到锁,访问完共享数据的线程就会归还锁
(4)自旋超过阈值(默认是10次),就会升级成重量级锁
面试题:synchronized 异常会释放锁吗?会,会执行monitorexit释放锁对象