synchronized
demo1
package syndemo1;
/**
-
synchronized:加锁的意思,他是对某个对象加锁,而不是对某段代码。
-
下面例子中创建了一个对象o专门用于加锁。
*/
public class T1 {
private int count = 1;
private Object o = new Object();//o的作用就是充当锁public static void main(String[] args) {
T1 t = new T1();
t.m();
}public void m(){
/**
* 1、要执行下面的代码,必须先拿到o的锁。new Object()在堆里创建了一个对象,o是在栈里指向堆里面的对象。这里实际是
* 要找堆里面的对象申请锁的。下面统一说是找o申请锁。
* 2、这里可以理解为向o申请锁,也可以理解为给o加锁,怎么好理解怎么整,但是锁只有一把。
* 3、第一个线程把这把锁释放了,第二个线程才能申请到这把锁,才能调用下面的方法。一个线程拿到了这把锁别的线程就拿不到了,
* 这就叫互斥锁。
*/
synchronized(o){
count--;
System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
}
}
}
demo2
package syndemo2;
public class T2 {
private int count = 2;
public static void main(String[] args) {
T2 t = new T2();
t.m();
}
/**
* 任何线程要执行下面的代码,先要给this加锁,这里是给自身加锁。或者说要用这个方法,要先new一个T的对象指向自身。
* 也就是说this就是T2的对象。
*
* m()方法里面“一开始就对this加锁了”,对这种有个简单的写法,见T3。
*/
public void m(){
synchronized (this){
count--;
System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
}
}
}
demo3
package syndemo3;
public class T3 {
private int count = 3;
public static void main(String[] args) {
T3 t = new T3();
t.m();
}
/**
* 对T2里面的简单写法如下。这个例子里面锁定的是当前对象this。
*/
public synchronized void m(){
count--;
System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
}
}
demo4
package syndemo4;
public class T4 {
private static int count = 4;
public static void main(String[] args) {
m();//这里也可以写为T4.m()
mm();//这里也可以写为T4.mm()
}
/**
*这里m()方法和mm()方法中的两个加锁的效果是一样的。
* 我们知道万物皆对象,T4.class是将类抽象成了一个对象。
* 静态方法里面synchronized加锁的对象是当前类的class对象。
*/
public synchronized static void m(){
count--;
System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
}
public static void mm(){
/**
* 静态属性和静态方法的调用是类名.类方法或类名.类属性,这个时候调用方法的时候是没有new一个对象的,所以下面这行代码里
* 就不能写synchronized(this)。即这里不能给this加锁!这个时候锁定的是当前类的class对象。
*/
synchronized (T4.class){
count--;
System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
}
}
}
demo5
package syndemo5;
public class T5 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) {
T5 t = new T5();
for(int i = 0; i < 5; i++){
new Thread(t, "THREAD" + i).start();
}
}
/**
* 多次运行之后,可能会拿到如下结果:
* 线程名:THREAD1 count:8
* 线程名:THREAD0 count:8
* 线程名:THREAD2 count:7
* 线程名:THREAD3 count:6
* 线程名:THREAD4 count:5
* 分析:1、上面的代码中只new了一个T5的对象t,而不是在每个线程中都new了对象。所以这些线程是共同访问这个对象的。
* 2、这5个线程访问的是同一个count,count在堆里面,t在栈里面。
* 3、这里对运行结果做个分析。第一个线程count--之后还没有打印之前,第二个线程进来了,做了个count--操作,这时候
*第一个线程才开始打印结果,第二个线程也随之打印,所以前两个线程打印的结果都是count--再count--的结果,即8,后边的三个
* 线程都是count--立马打印,即结果正确。
* 解决办法。只要将上面的synchronized打开给加上锁,重复多次执行拿到的结果都是正确的。这里不展示测试结果了。
*/
}
demo6
package syndemo6;
/**
- 问题:同步和非同步方法是否可以同时调用?
- 答:可以。理解:m1执行需要锁,m2不管有没有锁都会执行,所以可以同时调用。(注意下面两个方法中的休眠时间)
- https://blog.csdn.net/zhou_fan_xi/article/details/83752697 lambda表达式引入依赖后报错的解决方法
- 运行结果:
- 线程名:t1 m1 start
- 线程名:t2 m2
- 线程名:t1 m1 end
*/
public class T6 {
public synchronized void m1(){
System.out.println("线程名:"+Thread.currentThread().getName()+" m1 start");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名:"+Thread.currentThread().getName()+" m1 end");
}
public void m2(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名:"+Thread.currentThread().getName()+" m2");
}
public static void main(String[] args) {
T6 t = new T6();
new Thread(()->t.m1(),"t1").start();//在一个线程中调用m1()方法
new Thread(()->t.m2(),"t2").start();//在另一个线程中调用m2()方法
}
}
demo7
package syndemo7;
import java.util.concurrent.TimeUnit;
/**
-
下面这个类模拟了一个充钱和查询钱的业务。对充钱的过程加了锁,对查询钱的过程没加锁,在执行过程中可能产生脏读。
*/
public class Account {
String name;
double money;//充钱
public synchronized void setMoney(String name, double money){
this.name = name;/** * 这里线程休息了2秒后才把钱设置进去。这里是为了模拟真实义务逻辑复杂的情况,将代码执行时间放大 * 尽管对写的过程加了锁,但是在写的过程还没有完成的时候进行了读,也就是在休息的这2秒内进行了读, * 读是没加锁的,就可能发生脏读。 */ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } this.money = money;
}
//查询钱/**
如果业务里面允许暂时性的脏读,那么读是不用加锁的,这样可以提高性能。如果不允许,就读写都加锁,把下面的加锁的代码打开即可。
/
public /synchronized/ double getMoney(String name){
return this.money;
}public static void main(String[] args) {
Account account = new Account();
// new Thread(new Runnable() {//开启一个线程,调用setMoney()方法
// @Override
// public void run() {
// account.setMoney("张三",101.0);
// }
// }).start();
//开启一个线程,调用setMoney()方法。下面这行代码等同于上面注释的六行代码
new Thread(()->account.setMoney("张三",100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);//线程休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(account.getMoney("张三"));//查询张三的钱
try {
TimeUnit.SECONDS.sleep(2);//线程休息2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(account.getMoney("张三"));//查询张三的钱
}
}
demo8
package syndemo8;
import java.util.concurrent.TimeUnit;
/**
- 一个同步方法里面可以调用另一个同步方法。一个线程已经拥有了某个对象的锁,再次申请的时候仍然会得到该对象的锁,
- 也就是说synchronized获得的锁是可重入的。
/
public class T8 {
public static void main(String[] args) {
new T8().m1();
}
/*- m1方法和m2在同一个线程(主线程)里面,所以这两个方法都能获得这把锁。即同一个线程同一把锁,所以一个同步方法里面可以调用另一个同步方法。
*/
synchronized void m1(){
System.out.println("m1 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
}
synchronized void m2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2被调用");
}
}
- m1方法和m2在同一个线程(主线程)里面,所以这两个方法都能获得这把锁。即同一个线程同一把锁,所以一个同步方法里面可以调用另一个同步方法。
demo9
package syndemo9;
/**
- 这个类里面模拟了死锁
*/
public class T9 {
private final Object o1 = new Object();
private final Object o2 = new Object();
private void m1(){
synchronized(o1){
System.out.println(Thread.currentThread().getName()+"获取对象o1锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
}
System.out.println("m1结束");
}
private void m2(){
synchronized(o2){
System.out.println(Thread.currentThread().getName()+"获取对象o2锁");
m1();
}
System.out.println("m2结束");
}
public static void main(String[] args) {
T9 t = new T9();
new Thread(t::m1, "线程1").start();
new Thread(t::m2, "线程2").start();
}
}
demo10
package syndemo1011;
import java.util.concurrent.TimeUnit;
/**
- 一个同步方法里面可以调用另一个同步方法。一个线程已经拥有了某个对象的锁,再次申请的时候仍然会得到该对象的锁,
- 也就是说synchronized获得的锁是可重入的。下面展示的是继承中可能发生的情况,子类的同步方法里面调用父类的同步方法,不会死锁。
- 运行结果:
- child m start
- m start
- m end
- child m end
/
public class T10 {
public synchronized void m(){
System.out.println("m start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
/*
* 根据前面的知识我们知道:这里的两个m()方法都是给this加锁,而this就是下面new出来的T11的对象。所以这俩个加锁的是同一个对象
*/
new T11().m();
}
}
class T11 extends T10{
@Override
public synchronized void m() {
System.out.println("child m start");
super.m();//子类调用父类的同步方法
System.out.println("child m end");
}
}
demo11
package syndemo12;
import java.util.concurrent.TimeUnit;
/**
-
程序在执行过程中,如果出现异常,默认情况下锁会被释放。在并发过程中有异常要特别注意,不然会发生不一致的情况。
-
运行结果请自行观察
/
public class T12 {
int count = 0;
synchronized void m(){
System.out.println("线程名:"+Thread.currentThread().getName()+" start");
while(true){
count++;
System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 6){
/*
* 这里抛异常,锁将会被释放,如果进行try……catch……,锁就不会被释放。
*/
int i = 1/0;
}
}
}public static void main(String[] args) {
T12 t12 = new T12();
Runnable runnable = new Runnable() {
@Override
public void run() {
t12.m();
}
};new Thread(runnable,"t1").start();//创建第一个线程 try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(runnable,"t2").start();//创建第二个线程
}
}