目录
- 8锁问题
- 锁1:多个线程使用同一对象分别调用不同带有带synchronized关键字且非静态的方法
- 锁2:在锁1基础上,增加A线程执行的方法的执行时间,使得B有机会参与执行
- 锁3:多个线程使用同一对象,一个线程执行带有对象锁的方法,一个线程执行普通方法
- 锁4:多个线程使用不同对象分别调用不同带有对象锁的方法
- 锁5:多个线程同一个对象分别调用对各自静态锁的方法
- 锁6:多个线程同多个对象分别调用对各自静态锁的方法
- 锁7:多个线程同一个对象分别调用对象锁的方法和静态锁的方法
- 锁8:多个线程不同对象分别调用对象锁的方法和静态锁的方法
8锁问题
关注点有两个:锁的类型,锁的是谁
锁的类型:静态锁针对此类所有实例的对象,对象锁只针对当前对象
锁的是谁:对象锁锁的是此类中所有带synchronized关键字
且非静态
的方法,静态锁锁的是此类所有带synchronized
关键字且静态
的方法
针对上面两点,这样在判断时要注意:是否是同一个对象,对象调用的方法是那种锁类型
锁1:多个线程使用同一对象分别调用不同带有带synchronized关键字且非静态的方法
分析:对象锁只针对当前对象,锁的是是所有带synchronized关键字且非静态的方法。
public class Application {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{phone.sendSMG();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone.sendCall();},"B").start();
}
}
class Phone{
synchronized void sendSMG(){
System.out.println("发送消息");
}
synchronized void sendCall(){
System.out.println("打电话");
}
}
结果:
思考1:为什么A线程与B线程之间启动要睡一会?
在代码中是顺序执行的,但线程的启动时操作系统能决定的,虽然A先进入就绪状态,但操作系统可能选择B先启动。让A线程与B线程启动时睡一会,可以确保一定是A线程先启动。因为操作系统处理是极快的,即使是1s的间隔,对于操作系统会有很大的时间差,是AB不在一起,让A极大的概率先启动。
思考2:为什么结果是这样?
两个关键点:
是同一个对象,锁的类型相同
A线程先启动,拿到对象锁,这时即使B线程启动了,但拿不到对象锁(A线程在没有执行完之前不会释放对象锁),调用不了带有synchronized对象锁的方法,只能进行等待。直到A线程执行完毕释放锁,B线程才能够获取锁继续去执行。
思考3:既然sendSMG()方法先执行,如果在方法中睡一会,sendCall()方法会不会先执行?
前面已经说到,A线程拿到锁后先执行sendSMG()方法,因为在方法中sleep()不会释放锁,即使执行时间过长,没有执行完毕,就不会释放锁,B就无法拿到锁执行带有synchronized对象锁的方法
思考4:如果Phone有一个普通方法,没有带synchronized锁,B线程启动后即使sendSMG()方法没有执行完毕,这个普通方法会被执行吗?
普通方法没有对象锁,B在执行时不用去等待A释放锁,因此只要B启动了就可以立即执行。如果A线程调用的也是这个普通方法,那么A,B两个线程可能同时在此方法内。
锁2:在锁1基础上,增加A线程执行的方法的执行时间,使得B有机会参与执行
验证上面的思考3,证明sendSMG()方法先执行并不是A线程先启动执行快,赶在了B线程调用sendCall()方法的问题,而是A不执行完sendSMG()方法释放对象锁,B根本不会有机会执行sendCall()方法。因为B线程一定后启动,使的对象锁被A先拿到。只要拿到锁后,一个对象内,多个线程只能有一个线程去执行带有synchronized对象锁的方法。
public class Application {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{phone.sendSMG();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone.sendCall();},"B").start();
}
}
class Phone{
synchronized void sendSMG(){
try { TimeUnit.SECONDS.sleep(2); // 注意:我这里特意为2s使得这是B线程一定启动了 测试:B线程的sendCasll方法会不会先执行。} catch (InterruptedException e) {}
System.out.println("发送消息");
}
synchronized void sendCall(){
System.out.println("打电话");
}
}
代码只是在锁1的基础上增加了sendMsg()的执行时间。结果和锁1相同,先等待1s后,控制台出现"发送消息",然后紧接着出现"打电话"
思考: 这样会发生什么现象?
A线程抢到对象锁后,执行sendSMG()方法,在控制台输出"发送消息",然后睡1s,释放锁。B线程拿到锁,执行sendCall()方法,立即在控制台输出"打电话"
锁3:多个线程使用同一对象,一个线程执行带有对象锁的方法,一个线程执行普通方法
分析:对象锁,锁住的是带有synchronized的非静态方法,普通方法不再锁的范围
猜想:普通方法不受synchronized的影响,哪个线程执行的快,就可以先调用。
public class Application {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{phone.sendSMG();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone.sendHello();},"B").start();
}
}
class Phone{
synchronized void sendSMG(){
try { TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {}
System.out.println("发送消息");
}
synchronized void sendCall(){
System.out.println("打电话");
}
void sendHello(){
System.out.println("hello");
}
}
结果:
B线程的执行不需要等待A线程对象锁的释放,因为普通方法执行时根本不需要获取对象锁。
思考:如果A线程也调用普通方法,然后将普通方法改装成这样,会发生什么?
结果:
以上使用的是同一个对象,探讨synchronized对象锁的问题,下面使用不同
对象,探讨synchronized对象锁的问题
锁4:多个线程使用不同对象分别调用不同带有对象锁的方法
分析:对象锁:只针对当前对象有效,所以在不同对象间使用无效
public class Application {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{phone1.sendSMG();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone2.sendCall();},"B").start();
}
}
class Phone{
synchronized void sendSMG(){
try { TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {}
System.out.println("发送消息");
}
synchronized void sendCall(){
System.out.println("打电话");
}
void sendHello(){
System.out.println(Thread.currentThread().getName()+"进入");
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { }
System.out.println("hello");
System.out.println(Thread.currentThread().getName()+"离开");
}
}
结果:
思考:明明A线程先启动获取了对象锁,为什么B线程的方法在A线程没有执行完毕就开始执行?
关注点有两个:锁的类型,锁的是谁
在A,B线程使用的是不同对象,因此对于对象锁是无效的!B线程操作的对象有B线程自己的锁,A线程操作的对象有A线程自己的锁,二者不是同一个对象,因此锁不同。对于执行对象锁类型的方法,A线程的对象锁不能够锁住B线程的对象锁
思考:如果锁住不同对象的?
这时,就要使用类锁,因为不同对象也是由同一个类实例化出来的,可以使用类锁,生成一个不同对象也能识别的锁。
下面将引入静态锁
锁5:多个线程同一个对象分别调用对各自静态锁的方法
分析:静态锁,此类所有对象都有效。锁住的是带有synchronized的静态方法
public class Application {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{phone.sendSMG();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone.sendCall();},"B").start();
}
}
class Phone{
synchronized static void sendSMG(){
try { TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {}
System.out.println("发送消息");
}
synchronized static void sendCall(){
System.out.println("打电话");
}
}
效果:
方法都加上了static就相当于都是同一个类锁,不管是不是同一个对象,只要是此类实例化出的对象,在这个对象获取锁没有释放之前,任何对象都不能去执行带类锁的方法
锁6:多个线程同多个对象分别调用对各自静态锁的方法
效果和锁5相同,在都是类锁的方法上,不会区分是否是同一个对象。只要有一个对象在执行,不管是不是同一个对象,都只能等待去执行此类中带有类锁的方法。
思考:如果线程A使用对象1调用的是静态锁方法,线程B使用对象2调用的是普通方法,A先启动,那么对象2调用普通方法是需要等待A释放静态锁吗?
当然不会,因为普通方法根本没锁,类锁限制不了它
下面将静态锁与对象锁混合
锁7:多个线程同一个对象分别调用对象锁的方法和静态锁的方法
分析:有对象锁和静态锁,二者没有包含关系。
还是注意两个关键点:是否是同一个对象?是;调用的方法是否是相同锁类型?不是
,一个是静态锁,一个是对象锁;
两个不同的方法,因为锁的类型不同,之间根本没有关系
public class Application {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{phone.sendSMG();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone.sendCall();},"B").start();
}
}
class Phone{
synchronized void sendSMG(){
try { TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {}
System.out.println("发送消息");
}
static synchronized void sendCall(){
System.out.println("打电话");
}
}
效果:
A线程先执行,但A线程拿到的是对象锁,B线程启动后,拿到的是类锁,对象锁无法锁住类锁,因此B不必等待A执行完毕释放类锁后再去执行。
思考1:如果是两个线程调用的同一个方法加上静态锁呢?
这就和是否是同一个对象没有关系了,因为在静态方法上加锁是类级别的锁,不管是否是同一个对象,只要有一个线程拿到锁,在执行完毕释放锁之前,其他线程都不能执行此方法。但可以执行其他非静态锁方法。
思考2:如果A先拿到类锁,B后拿到对象锁,那B用等到A释放锁后再去执行吗?也就是说类锁与对象锁是否有包含关系?
(不同对象)
证明:类锁与对象锁是作用范围不同的两种锁(对象锁只针对当前对象,类锁针对此类所有实例的对象),但不是类锁包含对象锁
锁8:多个线程不同对象分别调用对象锁的方法和静态锁的方法
和锁7思考2相同,线程A这个拿到的是属于这个对象1的对象锁,线程B拿到的是属于所有对象的类锁。二者调用方法的锁的类型不同,谁执行的快谁先输出,不存在等不等的问题。