一、互斥锁
Java语言中,引入对象互斥锁的概念,来保证共享数据操作的完整性
线程同步机制:数据在同一时刻,最多只能有一个线程访问,不允许多个线程同时访问,以保证数据的完整性。
使用Synchronized关键字使线程同步
1.基本介绍
- 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能有一个线程访问
- 同步的局限性:导致程序的执行效率降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法(静态的)的锁为当前类本身
2.Synchronized关键字
格式一: synchronized在方法声明中,表示整个方法-为同步方法
public synchronized void 方法名(String name){
//需要被同步的代码;
}
格式二:synchronized还可以使用在同步代码块中
synchronized(锁对象){ //得到对象的锁,才能操作同步代码
//需要被同步的代码;
}
使用两种互斥锁解决售票同步问题
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
new Thread(test1).start(); //线程一
new Thread(test1).start(); //线程二
new Thread(test1).start(); //线程三
}
}
//实现Runnable接口的方式
class test1 implements Runnable{
private int sum = 100;
private boolean loop = true;
//使用synchronized实现同步线程,在同一时刻,只能有一个线程执行sell方法
//格式一:同步方法
public synchronized void sell(){ //方法锁
if (sum<=0){
System.out.println("售票结束");
loop = false;
return;
}
try {
Thread.sleep(50); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票 剩余票数为"+ --sum);
}
// //格式二:同步代码块
// public void sell(){
// synchronized (this){ //代码块锁
// if (sum<=0){
// System.out.println("售票结束");
// loop = false;
// return;
// }
//
// try {
// Thread.sleep(50); //睡眠1秒
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票 剩余票数为"+ --sum);
// }
// }
@Override
public void run() {
while (loop){
sell();
}
}
}
3.注意事项
- 同步方法如果没有使用static修饰:默认 锁对象为this,也可以是其他对象(但要求是同一个对象)
- 如果同步方法使用static修饰,默认 锁对象:当前类.class(静态方法里不能使用this)
- 实现的落地步骤:
① 需要先分析上锁的代码
② 选择同步代码块或同步方法
③ 要求多个线程的锁对象为同一个即可
举例说明
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
new Thread(test1).start(); //线程一
new Thread(test1).start(); //线程二
new Thread(test1).start(); //线程三
}
}
//实现Runnable接口的方式
class test1 implements Runnable{
private int sum = 100;
private boolean loop = true;
//注意事项一
Object object = new Object();
//格式二:同步代码块
public void sell(){
synchronized (object){ //代码块锁,这里的this也可以是其他对象,注意:同一个对象即可
if (sum<=0){
System.out.println("售票结束");
loop = false;
return;
}
try {
Thread.sleep(50); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票 剩余票数为"+ --sum);
}
}
//注意事项二
//同步方法中:public synchronized void sell(){} ,静态锁是当前test1.class本身的
//同步代码块中:如果在静态方法中,实现一个同步代码
public static void p(){
//同步代码
synchronized (test1.class){
System.out.println("123");
}
}
@Override
public void run() {
while (loop){
sell();
}
}
}
二、线程的死锁
线程死锁:多个线程都占用了对方的锁资源,但互不相让,导致了死锁,在编辑是一定要避免死锁的发生
public class test {
public static void main(String[] args) throws InterruptedException {
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A 线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B 线程");
A.start();
B.start();
/*
此时就会出现死锁现象
线程A得到o1对象锁,线程B得到o2对象锁
而线程A又要得到o2对象锁,线程B又要得到o1对象锁
由于o1和o2锁均被占用且不能释放,所以就出现了死锁现象
*/
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object(); //保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean f;
//构造器
public DeadLockDemo(boolean f) {
this.f = f;
}
@Override
public void run() {
if (f){
synchronized (o1){
System.out.println(Thread.currentThread().getName() + " 进入 1");
synchronized (o2) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 2");
}
}
}else {
synchronized (o2){
System.out.println(Thread.currentThread().getName() + " 进入 3");
synchronized (o1) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 4");
}
}
}
}
}
三、释放锁
释放锁的情况
- 当前线程的同步方法或同步代码块执行结束
- 当前线程在同步方法或同步代码块中遇到break、return
- 当前线程在同步方法或同步代码块中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步方法或同步代码块中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不释放锁的情况
- 线程执行同步方法或同步代码块时,程序调用了Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
- 线程执行同步方法或同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(注:尽量避免使用suspend()和resume()来控制线程,因为方法已经过时不推荐使用)