一、使用原因
多线程使用原因:
一块大蛋糕太大了,一天内还需要吃完,不然就坏了,一个人吃不完,所以需要两个人吃,但是只有一个勺子,如果A吃的时候不小心把勺子弄丢了。需要花时间找,那么B就不能吃。所以就需要使用多线程。给两个勺子,就算A吃的时候丢了,B也可以不受到影响。继续执行下去。
同步锁的使用原因:
吃蛋糕的时候,因为两个人不停的吃。A吃的快,B吃的慢。就导致了,A吃撑了了,B反而没吃饱。不是我们想看到的事情。所以需要一个抢盘子(同步锁),A抢到盘子先吃四分之一,B等着。等A吃完后,B继续抢。如果抢到了,B吃。如果没抢到,那么A再吃四分之一后,就让给B。
二、代码体现
同步代码块:
synchronized(Object obj){
//code
}
同步方法:
public synchronized void method(){//code}
三、代码案例
package com.synchronized_test;
/**
* 人类
*/
public class Person implements Runnable {
/* 蛋糕对象 */
private Cakes c = new Cakes();
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (c) {
while(c.num>0) {
eat();
try {
//问题!!!后面有讲解
c.wait(1000);//让A线程停一下,给B线程得到执行的机会
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/*吃蛋糕的动作*/
public void eat() {
System.out.println(Thread.currentThread().getName() + " eat第" + c.num-- + "份");
}
}
package com.synchronized_test;
/**
* 蛋糕类
* */
public class Cakes{
/*数量,分4份*/
int num=4;
}
package com.synchronized_test;
/**
* 测试
* */
public class Demo {
public static void main(String[] args) {
Person p=new Person();
Thread A=new Thread(p);
Thread B=new Thread(p);
A.start();
B.start();
}
}
代码解释:
1.共有3个类,测试类,蛋糕类,人类
2.蛋糕类:分成4份。所以属性就是num,为了让代码稍微优化点,就没封装了。
3.人类:线程类,行为是eat(),还有一个实现Runnable接口的run()。eat()中写的是打印输出哪个线程吃的,并且使数量减一。run()中写是只要num>0就不停循环。
4.测试类:创建Person类实例,创建两个线程,并将p作为参数传入两个线程中,开启后,两个线程进入就绪状态。CPU会随机 给予执行权。
问题:为什么要写wait(long millis)而不是sleep(long millis),两个代码作用很像。但是会使两个线程处于不同的状态。
sleep(long millis):线程暂时处于TIME_WAITING状态,但是不释放对象锁。也就是说,我就算不继续执行,但我手里还是握着这个对象的锁。我就算不执行,你也别想执行。那么如果这里写sleep(),也只会使A线程执行4次而已。B线程一口都吃不到。
wait():线程释放对象锁,且处于WAITING状态,除非被手动唤醒,否则将不会拥有CPU的执行资格,更别说CPU执行权了。
wait(long millis):线程释放对象锁,处于TIME_WAITING状态,与无参的wait()的区别就是等到millis的时间到了之后,会被自动唤醒。重新争夺对象锁。
四、使用区别
既然同步代码块和同步方法都能同步。那使用哪个?
代码案例:
package com.synchronized_methods;
/**
* Synchronized1
* */
public class SynchronizedMethod {
private AnotherClass a=new AnotherClass();
/*同步代码块1*/
public void showA() {
System.out.println("showA");
synchronized(a) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/*同步函数2*/
/*同步函数的对象锁是固定的this,跟ShowA不是一个对象锁*/
public synchronized void showB() {
System.out.println("showB");
}
/*同步代码块2*/
/*与showB的对象锁是同一个对象锁*/
public void showC() {
synchronized(this) {
System.out.println("showC");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package com.synchronized_methods;
/**
* Synchronized2
* */
public class AnotherClass {
/*同步代码块1*/
public void showA() {
/*与SynchronizedMethod中的showA方法是同一把锁*/
synchronized (this) {
System.out.println("showA2");
}
}
}
package com.synchronized_methods;
/**
* 测试类
* */
public class Demo {
public static void main(String[] args) {
SynchronizedMethod s=new SynchronizedMethod();
AnotherClass a=new AnotherClass();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
s.showA();
a.showA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
s.showC();
s.showB();
}
}).start();
}
}
运行结果:
执行情况:先执行了showA()和showC()后,执行了隔了1秒执行了showB(),再隔了2秒执行了showA()
showA
showC
showB
showA2
代码解释:
问题1:为什么执行了showA()后没有暂停又直接执行了showC()?
解答:很明显,Synchronized代码块就是为了对象锁不让其他线程在执行中间插入。所以之所以能直接执行showC(),完全是因为两个方法用的不是同一个锁。showA()用的是AnotherClass类的对象,而showC()用的是当前对象。所以能够无视showA()方法中的Thread.sleep(3000)
问题2:那为什么可以之后停了1秒之后又执行了showB()呢?
解答:因为showB()中,没有写同步代码块,而是用同步修饰了方法,所以之所以等1秒,也是因为同步方法的对象锁,跟showC()中的对象锁是同一个对象锁。所以说,同步方法的对象锁其实就是this