这里大部分的解释都在代码中。
demo1
package synchornizeds;
public class Demo1 {
private int count;
private Object o = new Object();
public void m() {
synchronized (o) { //任何线程执行一下代码都要申请o对应的锁。此处的o是堆内存中new出来的对象,而不是声明的对象引用
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
public static void main(String[] args) {
}
}
demo2:
package synchornizeds;
public class Demo2 {
private int count;
public void m() {
synchronized (this) { //任何线程执行一下代码都要申请this对应的锁。
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
/*
* synchronized 关键字锁定的是一个对象 而不是一个代码块
*
*
* 打一个比方: 多个人去上厕所,只有一个马桶 ,不可能所有的人同时上吧,只能是一个一个的,当第一个人进厕所之后,它用锁将门锁住,
* 不让外面的人进来 ,等他结束之后,第二个人进来,继续锁上厕所的门。 这把锁就相当于this
* 也就是说当前对象调用自身类中的方法,需要加锁。this就是指的是当前对象。synchronized锁住的是对象,而不是执行的代码块,
* 就想上面的,锁,锁住的是厕所的门 而不是马桶。。。。。
* */
}
}
demo3:
package synchornizeds;
public class Demo3 {
private int count = 10;
public synchronized void m () { //等同以demo2中的写法
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
demo4:
package synchornizeds;
public class Demo4 {
private static int count = 10;
public synchronized static void m () { //相当于锁定demo4.class
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void mm() {
synchronized(Demo4.class) { //考虑一下这里写成synchronized(this)是否可行????
count--;
}
/*
* 这里是不可行的,为什么呢 我们都知道静态的方法或属性是不需要我们new出实例在调用的
* 静态的属性或方法可以直接用class调用的
* 而我们在这里锁定后的是.class 没有对象 this指的是对象不是class 所以此处不能写成synchronized(this)
*/
}
}
demo5
这里class是实现了Runnable接口重写run方法
package synchornizeds;
public class Demo5 implements Runnable {
private int count = 10;
public /*synchronized*/ void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
Demo5 t = new Demo5();
for(int i = 0;i<5;i++) {
new Thread(t ,"thread"+i).start();
}
}
}
thread0 count = 8
thread2 count = 7
thread1 count = 8
thread4 count = 5
thread3 count = 5
这里解释一些为什么会有重复的呢,就比如说5吧。当线程3减1之后应该为输出6,但是还没有等这个线程输出结果
下一个线程就执行了减操作,然后上一个线程才开始打印的,这个时候打印的值是减了2的值,应该是7-2 = 5
添加了synchronized关键字之后:
thread0 count = 9
thread3 count = 8
thread2 count = 7
thread1 count = 6
thread4 count = 5
这里加上锁之后,线程插入的问题就解决了。
demo6
package synchornizeds;
public class Demo6 {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + "m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m1 end...");
}
public void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m2 ...");
}
public static void main(String[] args) {
Demo6 t = new Demo6();
new Thread(()->t.m1(),"t1").start();
new Thread(()->t.m2(),"t2").start();
/*new Thread(t::m1,"t1").start();
* new Thread(t::m2,"t2").start();
*/
}
}
结果:
t1m1 start...
t2m2 ...
t1m1 end...
结论:在synchronized方法执行的过程中非同步方法也可以执行
就比如 一个人在上厕所,另一个人要在厕所外面扫地,两者之间是不需要竞争那把锁的。
线程之间脏读的问题(面试考点)
package synchornizeds;
import java.util.concurrent.TimeUnit;
/*
* 对业务写方法加锁
* 对业务读方法不加锁
* 容易产生藏读现象
*/
public class Account {
String name;
double balance;
public synchronized void set(String name,double balance) {
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.balance = balance;
}
public double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
Account a = new Account();
new Thread(()->a.set("xiaoming",100)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(a.getBalance("xiaoming"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(a.getBalance("xiaoming"));
}
}
结果:
0.0
100.0
这里造成数据脏读的问题的原因在上面的demo中也有说到,就是在同步方法执行的同时,非同步方法可以执行。
当加锁的写操作执行的同时,允许没有加锁的方法进行数据的读取。
解决方法就是在读方法上也加上锁
demo7 子类的同步方法调用父类的同步方法 (面试考点)
package synchornizeds;
import java.util.concurrent.TimeUnit;
/*
* 一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候依然会得到该对象的锁
* 也就是synchronized获得的锁是可重入的
* 这里是继承中可能发生的情形 子类的同步方法调用父类的同步方法
*
*/
public class Demo8 {
synchronized void m() {
System.out.println("m start ..");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
new DD().m();
}
}
class DD extends Demo8{
@Override
synchronized void m() {
System.out.println("child m start ");
super.m();
System.out.println("child m end ");
}
}
结果:
child m start
m start ..
child m end
子类的同步方法调用父类的同步方法是完全允许的!