锁对象是什么?
锁对象指的是被锁的对象。
使用锁的实质
多线程执行枷锁的方法或代码的时候,会先判断使用的锁是否被占用,如果没有被占用就获取并使用该锁,其他需要该锁的线程就处于等待状态;如果占用就需要等待该锁被释放重新尝试获取锁对象,知道获取到锁对象才能执行后面的代码或者方法。锁应该理解成一张通行证,有才能继续走,没有就要等这张通行证
锁对象是一切的关键
使用锁的效果和怎么使用
代码段级别的锁对象,是可以随意定义的,可以是当前对象,可以是当前类的其他实例对象,可以是当前类的字节码文件,可以是其他任意引用对象。一旦锁住了锁对象,其他需要该锁对象的线程必须等待。
方法级别的同步锁(被synchronized修饰的方法),锁住的是对象。只要是使用该对象作为锁对象的代码都会产生互斥效果。
互斥:如果一个线程正在执行某一部分操作,那么其他线程就不可以再执行这部分操作。
Java使用关键字synchronized来执行线程的互斥处理
synchronized方法
如果声明一个方法的时候,在前面加上synchronized,那么这个方法就只能由一个线程运行。只能由一个线程运行是每次只能由一个线程运行的意思,并不是说仅能让某一特定线程运行。
同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以代替volatile功能),这点确实也是很重要
关于锁和监视
线程的互斥机制称为监视(monitor)。另外,获取锁有时也叫做“拥有(own)监视”或“持有(hold)锁”
当前线程是否已获取某一对象的锁可以通过Thread.holdsLock方法来确认。当前线程已获取对象obj的锁时,可使用assert来表示
assert Thread.holdsLock(obj);
synchronized的三种应用方式
synchronized关键字最主要有以下三种应用方式
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同部代码前要获得当前类对象的锁
- 修饰代码块,指定加锁对象,对给定对象枷锁,进入同步代码库前要获得给定对象的锁
synchronized作用于实例方法
所谓的实例对象锁就是用synchronized修饰实例对象中的实例方法,注意是实例方法不包括静态方法
public class AccountingSync implements Runnable{
//共享资源(临界资源)
static int i = 0;
/**
* synchronized 修饰实例方法
* /
public synchronized void increase()
{
i++;
}
public void run()
{
for(int j = 0; j < 10000000;j ++)
{
increase1();
}
}
public static void main(String[] args) throws InterruptedException {
AccountingSync instance = new AccountingSync();
Thread t1 = new Thread(new AccountingSyncBad(instance));
Thread t2 = new Thread(new AccountingSyncBad(instance));
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
在上面的代码中,开启了两个线程操作同一个共享资源变量i,由于i++;操作并不具备原子性,该操作是先读取值,然后写回一个新值,相当于原来的值加上1,分两部完成,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会与第一个线程一起看到同一个值,并执行相同值的加1操作,这也就造成了线程安全失败,因此对于increase()方法必须使用synchronized进行修饰,以保证线程安全。
synchronized作用于静态方法
当synchronized作用于静态方法,其锁就是当前类的class对象锁。由于静态成员不转属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态成员的并发操作。需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态synchronized方法,是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的class对象,而访问非静态synchronized方法重用的是当前对象锁
public class AccountingSyncClass implements Runnable{
static int i = 0;
/**
*作用于静态方法,锁是当前class对象,也就是
*AccountingSyncClass类对应的class对象
*/
public static synchronized void increase()
{
i++;
}
public synchronized void increase4Obj()
{
i++;
}
public void run()
{
for(int j = 0; j < 10000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
//new新实例
Thread t1=new Thread(new AccountingSyncClass());
//new心事了
Thread t2=new Thread(new AccountingSyncClass());
//启动线程
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
值得注意的是这里的静态Increase()方法,与修饰实例方法不同的是,它的锁的对象是当前类的class对象。 对于increase4Obj()方法的是实例方法,其中它的对象锁是当前实例对象,如果别的线程调用这个方法,不会产生互斥现象。但是应该注意到的是线程共享资源i,会产生线程安全问题。
synchronized同部代码块
除了使用关键字修饰实例方法和静态方法外,还可以使用同步代码块,在某些情况下,我们编写的方法体可能比较大,还存在比较耗时的操作,需要同部的代码又只有一小部分,这时候可以利用synchronized关键字对这部分代码进行加上互斥锁
public class AccountingSync implements Runnable{
static AccountingSync instance = new AccountingSync();
static int i = 0;
public void run(){
synchronized(instance){
for(int j = 0; j < 10000000; j ++)
{
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}