Java SE 101 synchronized关键字深入详解

本文深入探讨JavaSE中synchronized关键字的使用,讲解其在多线程环境下的作用及实现线程安全的方法,包括同步机制的引入原因、解决多线程问题的案例分析,以及对象加锁和静态方法同步的细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~

Java SE 101 synchronized关键字深入详解

文章目录

1.停止一个线程的方式

(1)不能使用Thread类的stop方法来终止线程的执行。

(2)一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。

2.不能依靠线程的优先级来决定线程的执行顺序

3.多线程同步

在这里插入图片描述

3.1为什么要引入同步机制?

(1)在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。

(2)解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。

4.多个人(线程)在同一银行账户上取钱的问题

4.1 不同的账户从各自的账户上取钱,即多个线程操作不同对象的成员变量。

4.1.1定义银行账户信息类
package com.javareview.thread.synchronizedkeyword;

/**
 * 定义银行账户信息类
 */
public class Bank {
	
	//多个线程需要操作的成员变量
	private int money = 1000;

	/**
	 * 返回实际取到钱的数目 
	 * (1)number表示取的钱
	 * (2)实际取多少钱,取多少钱我就是多少钱,为什么这里还要返回一个int呢?这个值不是和传进来的值一样吗?
	 * (3)这是不一定的,如果有1000块钱,我给柜台小姐说,给我取2000,那这种情况我们就认为是个错误。这时候就不能返回2000了,
	 * 返回一个-1,来表示这个错误,因为不知道账户里面有多少钱。
	 */
	public int getMoney(int number) {
		if (number < 0) {
			// s1.取钱金额小于零的情况处理
			return -1;
		} else if (number > money) {
			// s2.取钱金额大于银行账户余额的情况处理
			return -2;
		} else if (money < 0) {
			// s3.银行账户余额小于0的情况
			return -3;
		} else {
			try {
				// s1.完成取钱之前的准备工作
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			money -= number;

			System.out.println("left money: " + money);
			return number;
		}
	}
}
4.1.2定义取钱的线程类
package com.javareview.thread.synchronizedkeyword;

/**
 * 线程类
 */
public class MoneyThread extends Thread {
	/**
	 * 银行账户对象引用,
	 */
	private Bank bank;

	/**
	 * 通过构造方法传递银行账户信息
	 */
	public MoneyThread(Bank bank) {
		this.bank = bank;
	}

	@Override
	public void run() {
		/**
		 * 从银行账户里面取钱,每次取800
		 */
		System.out.println(bank.getMoney(800));
	}
}
4.1.3开启多个线程执行取钱操作
package com.javareview.thread.synchronizedkeyword;

/**
 * 开启多个线程执行取钱操作
 */
public class TetchMoney1 {
	public static void main(String[] args) {
		
		//1.情境:不同的账户从各自的账户上取钱,即多个线程操作不同对象的成员变量。
		
		//s1.构建银行账户信息
		Bank bank = new Bank();
		//s2.线程1,模拟一个人从柜台取钱
		Thread t1 = new MoneyThread(bank);
		
		//s3.再构建一个银行账户信息
		bank = new Bank();
		//s4.线程2,模拟一个人从取款机上取钱
		Thread t2 = new MoneyThread(bank);

		//s5.开启两个线程,执行取钱的操作
		t1.start();
		t2.start();

		//s6.结果是两个线程start之后,分别取出了800元钱
	}
}
4.1.4执行结果

(1)两个线程start之后,分别取出了800元钱

left money: -600
800
left money: -600
800

4.2多个线程取出多个账户共有的钱

4.2.1 定义银行账户信息类
package com.javareview.thread.synchronizedkeyword;

/**
 * 定义银行账户信息类
 */
public class Bank {
	
	/**
	 * 多个线程对多个对象共有成员变量进行操作
	 */
	private static int money = 1000;

	public int getMoney(int number) {
		if (number < 0) {
			return -1;
		} else if (number > money) {
			return -2;
		} else if (money < 0) {
			return -3;
		} else {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			money -= number;

			System.out.println("left money: " + money);
			return number;
		}
	}
}

4.2.2定义取钱线程类
package com.javareview.thread.synchronizedkeyword;
/**
 * 定义取钱线程类
 */
public class MoneyThread extends Thread {
	/**
	 * 银行账户对象引用,
	 */
	private Bank bank;

	public MoneyThread(Bank bank) {
		this.bank = bank;
	}

	@Override
	public void run() {
		/**
		 * 模拟从银行账户里面取钱,每次取800
		 */
		System.out.println(bank.getMoney(800));
	}
}
4.2.3开启多个线程,构建多个对象,执行取钱操作
package com.javareview.thread.synchronizedkeyword;

/**
 * 开启多个线程执行取钱操作
 */
public class TetchMoney1 {
	public static void main(String[] args) {
		Bank bank = new Bank();
		Thread t1 = new MoneyThread(bank);
		
		bank = new Bank();
		Thread t2 = new MoneyThread(bank);

		t1.start();
		t2.start();
	}
}

(1)注意:两个线程对两个对象的共有成员静态变量进行操作

(2)static 的成员变量是被所有对象所共享的。既然共享了,无论生成多少个对象,它也只会生成一份儿。操纵它的时候,也只会操纵唯一的一份儿。

(3)静态成员变量是属于类级别的,不属于对象,所以无论生成多少个对象,对于具体的对象来说,这个对象也只会生成一分拷贝。所以多个线程,再操作共享的静态成员变量时,互相也不会受到干扰。

(4)这个案例的实质还是多个线程对多个对象进行操作,但是是对多个对象共享的静态成员变量的操作。

4.2.4执行结果

(1)两个线程start之后,分别取出了800元钱

left money: -600
800
left money: -600
800

5.如何处理线程同步的问题?

5.1多个用户从同一个账户取钱(即多个线程操作同一个对象的成员变量)

5.1.1 定义银行账户信息类
package com.javareview.thread.synchronizedkeyword;

/**
 * 定义银行账户信息类
 */
public class Bank {
	
	/**
	 * 多个线程对同一对象的成员变量进行操作
	 */
	private int money = 1000;

	/**
	 * 使用synchronized关键字解决多线程同步问题,即多个线程同时操作某一对象的成员变量
	 * @param number
	 * @return
	 */
	public synchronized int getMoney(int number) {
		if (number < 0) {
			return -1;
		} else if (number > money) {
			return -2;
		} else if (money < 0) {
			return -3;
		} else {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			money -= number;

			System.out.println("left money: " + money);
			return number;
		}
	}
}
5.1.2 定义取钱线程类
package com.javareview.thread.synchronizedkeyword;

/**
 * 定义取钱线程类
 */
public class MoneyThread extends Thread {
	
	private Bank bank;

	public MoneyThread(Bank bank) {
		this.bank = bank;
	}

	@Override
	public void run() {
		//s1.线程对同一对象的成员变量进行操作
		System.out.println(bank.getMoney(800));
	}
}
5.1.3 启动多个线程在同一时刻对同一对象的成员变量做操作
package com.javareview.thread.synchronizedkeyword;

/**
 * 启动多个线程在同一时刻对同一对象的成员变量做操作
 */
public class TetchMoney2 {
	public static void main(String[] args) {
		//s1.构建多个线程需要操作的同一对象
		Bank bank = new Bank();
		//s2.构建多个线程
		Thread t1 = new MoneyThread(bank);
		Thread t2 = new MoneyThread(bank);
		//s3.启动多个线程在同一时刻对同一对象的成员变量做操作
		t1.start();
		t2.start();
	}
}
5.1.4在方法上添加synchronized关键字解决多线程问题

多个线程需要同时操作同一对象的成员变量时,在该对象所在类的成员方法上添加synchronized关键字,来解决线程安全的问题。

6.synchronized关键字

(1)当synchronized修饰一个方法的时候,该方法叫做同步方法。

(2)Java中的每个对象都有一个锁(lock),或者叫做监视器(monitor)。

(3)当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。

6.1定义具有同步方法的类

package com.javareview.thread.synchronizedkeyword.synchronizedexample;
/**
 * 定义线程操作类
 */
public class Person {
	/**
	 * 定义同步方法
	 * (1)每隔500毫秒输出一个数字
	 */
	public synchronized void excute(){
		for(int i = 0 ; i < 50 ; i++){
			try {
				Thread.sleep((long)(Math.random()*500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("example: " + i);
		}
	}
}

6.2定义线程类,用于调用对象的同步方法。

package com.javareview.thread.synchronizedkeyword.synchronizedexample;

/**
 * 定义线程类,用于调用对象的同步方法。
 */
public class PersonOpThread extends Thread {
	private Person p;

	public PersonOpThread(Person p) {
		super();
		this.p = p;
	}

	@Override
	public void run() {
		this.p.excute();
	}
}

6.3创建多个线程同时调用同一个对象的成员方法

package com.javareview.thread.synchronizedkeyword.synchronizedexample;

public class PersonOpThreadTest {
	public static void main(String[] args) {
		//s1.构建线程需要操作的对象
		Person p = new Person();
		
		//s2.创建多个线程同时调用同一个对象的成员方法,为了保证线程安全,则在操作对象类的成员方法中加入了synchronized关键字,保证线程有序调用该同步方法
		Thread t1 = new PersonOpThread(p);
		Thread t2 = new PersonOpThread(p);
		
		//s3.启动多个线程调用对象的同步方法
		t1.start();
		t2.start();
	}
}

7.关于对象加锁

(1)多个线程类,生成多个线程对象,调用同一个对象中的两个不同同步方法,结果应该是有序的还是无序的呢?(是有序的)

(2)我们所说的加锁,是指给对象加锁。

(3)对象只有一个,它里面有n个synchronized方法.

(4)多线程环境下,当某一个线程去访问了某一个对象的synchronized方法的时候,这时候其他的任何线程都没法访问这个对象里面的所有的synchronized方法。(一定要记住,是给对象加锁,只要给对象的一个方法加锁,其他任何加上了锁的方法,都不能被其他线程所调用)

(5)多线程环境下,某一个线程要访问对象的任何一个synchronized方法,都需要给对象上锁,对象已经被锁住了,就无法再上锁了,除非等第一个线程执行完同步方法。要么同步方法抛异常了,导致方法调用结束,这个时候其他的线程才能去访问该对象的其他的synchronized方法。

8.学生和老师抢占侧所资源案例

8.1定义具有两个同步方法的类

package com.javareview.thread.synchronizedkeyword.multithread;

/**
 * 定义具有两个同步方法的类
 */
public class Toilet {
	public synchronized void excute(){
		for(int i = 0 ; i < 50 ; i++){
			try {
				Thread.sleep((long)(Math.random()*500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("hello: " + i);
		}
	}
	
	public synchronized void excute2(){
		for(int i = 0 ; i < 50 ; i++){
			try {
				Thread.sleep((long)(Math.random()*500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("world: " + i);
		}
	}
}

8.2定义多个线程类

package com.javareview.thread.synchronizedkeyword.multithread;

/**
 * 定义学生线程类
 */
public class StudentThread extends Thread {
	private Toilet toilet;
	
	public StudentThread(Toilet toilet) {
		super();
		this.toilet = toilet;
	}

	@Override
	public void run() {
		this.toilet.excute();
	}
}
package com.javareview.thread.synchronizedkeyword.multithread;

/**
 * 定义老师线程类
 */
public class TeacherThread extends Thread {
	
	private Toilet toilet;
	
	public TeacherThread(Toilet toilet) {
		super();
		this.toilet = toilet;
	}

	@Override
	public void run() {
		this.toilet.excute2();
	}
}

8.3定义多个线程调用同一对象的不同同步方法,观察同步方法的执行顺序。

package com.javareview.thread.synchronizedkeyword.multithread;

/**
 * 构建学生与老师两个线程对侧所这一公共资源进行抢占
 */
public class MultiThreadTest {
	public static void main(String[] args) {
		Toilet toilet = new Toilet();
		
		StudentThread stuThread = new StudentThread(toilet);
		TeacherThread teaThread = new TeacherThread(toilet);
		
		stuThread.start();
		teaThread.start();
	}
}

8.4执行结果

hello: 0
hello: 1
hello: 2
...
hello: 47
hello: 48
hello: 49
world: 0
world: 1
...
world: 47
world: 48
world: 49

8.5小结

(1)如果一个对象有多个synchronized方法,多线程环境下,同一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕之前,其他线程是无法访问该对象的任何synchronized方法的。

9.多个线程访问多个对象的synchronized方法

(1)多个线程访问多个对象的synchronized方法。(多个对象,不同线程,访问对象的synchronized方法。)

(2)分析

(a)第一个线程对象调用excute同步方法,第一个对象就被上锁了。

(b)第二个线程对象调用第二个对象的同步方法,第一个对象上锁了,与第二个对象没什么关系,第二个对象肯定还没上锁,因此第二个对象还是照样可以调用对象的synchronized方法的,所以当两个线程同时运行时,这种情况下输出结果就不能保证有序。

(3)因此最关键的是给哪个对象上了锁。

(a)一个对象上了锁,这个对象的其他任何synchronized方法都没法访问了。

(b)如果要是两个对象或多个对象,每个对象上一把锁,对A对象进行上锁,不会影响B对象同步方法的调用。

9.1定义多线程需要访问的类

package com.javareview.thread.synchronizedkeyword.lockmultiobj;

/**
 * 定义多线程需要访问的类
 */
public class Person {
	
	public synchronized void excute() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("hello: " + i);
		}
	}

	public synchronized void excute2() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("world: " + i);
		}
	}

}

9.2 定义线程类

package com.javareview.thread.synchronizedkeyword.lockmultiobj;

/**
 * 定义线程类,调用Person对象的同步方法excute
 */
public class StudentOpThread extends Thread {
	private Person person;

	public StudentOpThread(Person person) {
		super();
		this.person = person;
	}

	@Override
	public void run() {
		this.person.excute();
	}
}
package com.javareview.thread.synchronizedkeyword.lockmultiobj;

/**
 * 定义线程类,调用Person对象的同步方法excute2
 * @author xiongjie
 *
 */
public class TeacherOpThread extends Thread {
	
	private Person person;
	
	public TeacherOpThread(Person person) {
		super();
		this.person = person;
	}

	@Override
	public void run() {
		this.person.excute2();
	}
}

9.3 多个线程访问多个对象的synchronized方法。

package com.javareview.thread.synchronizedkeyword.lockmultiobj;

public class MultiThreadTest {
	public static void main(String[] args) {
		//s1.创建对象1
		Person person = new Person();
		//s2.创建线程对象访问对象1中的同步方法
		StudentOpThread stuThread = new StudentOpThread(person);
		
		//s3.创建对象2
		person = new Person();
		//s4.创建线程对象调用对象2中的同步方法
		TeacherOpThread teaThread = new TeacherOpThread(person);
		
		stuThread.start();
		teaThread.start();
	}
}

9.4执行结果

是一种乱序现象

hello: 0
hello: 1
world: 0
world: 1
hello: 2
world: 2
...

10.将对象的方法上锁并设置为静态的

10.1定义多线程需要访问的类,具有两个静态的同步方法

(1)说明,相较上例只给多线程需要访问的类的同步方法加上static关键字

package com.javareview.thread.synchronizedkeyword.lockstatic;

/**
 * 定义多线程需要访问的类
 */
public class Person {
	
	public static synchronized void excute() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("hello: " + i);
		}
	}

	public static synchronized void excute2() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("world: " + i);
		}
	}

}

10.2执行结果

输出有序的数据,两个static修饰的同步方法都属于类级别的方法,不是对象级别的,所以能够保证数字有序输出。

hello: 0
...
hello: 48
hello: 49
world: 0
world: 1
...
world: 48
world: 49

11.将对象的方法上锁,并设置其中的一个方法为静态,另一个方法非静态.

(1)说明,相较上例只给多线程需要访问类的同步方法去掉一个static关键字

11.1定义多线程需要访问的类,去掉其中一个同步方法的static关键字

package com.javareview.thread.synchronizedkeyword.firstsample;

/**
 * 定义多线程需要访问的类
 */
public class Person {
	
	public static synchronized void excute() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("hello: " + i);
		}
	}

	public synchronized void excute2() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("world: " + i);
		}
	}

}

11.2执行结果

输出乱序数字 static修饰的同步方法属于类级别的,而非static的同步方法属于对象级别的,执行顺序有先有后,所以输出数据乱序。

world: 0
hello: 0
world: 1
hello: 1
world: 2
hello: 2
hello: 3
world: 3
world: 4
hello: 4
le;

/**
 * 定义多线程需要访问的类
 */
public class Person {
	
	public static synchronized void excute() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("hello: " + i);
		}
	}

	public synchronized void excute2() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("world: " + i);
		}
	}

}

11.2执行结果

输出乱序数字 static修饰的同步方法属于类级别的,而非static的同步方法属于对象级别的,执行顺序有先有后,所以输出数据乱序。

world: 0
hello: 0
world: 1
hello: 1
world: 2
hello: 2
hello: 3
world: 3
world: 4
hello: 4
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清风百草

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值