超牛牪犇Java之同步代码块和锁的用法&死锁&如何停止线程

一.同步代码块和锁

CPU是随机执行线程的 也就是说线程可以在方法的任何位置被停止执行(用极限位置去假设)

在多个线程访问共享数据时:

要保证同一时间 只有一个线程操作共享数据

(当一个线程操作数据时 其他线程不能操作 只能等着操作的线程操作完成之后才能继续进入操作数据)

Thread子类不能实现资源共享

用同步代码块(同步锁)来解决:

锁:任意对象(只有一把锁 多个线程使用的是通一把锁)

写法:

synchronized(锁){

        操作共享数据的代码

}

卖票的例子:

多个平台同时卖票 为了防止多个平台同时卖一张票

class TicketRunnable implements Runnable {
	// 声明票
	private int ticket = 50;
	// 声明锁对象 保证只有一把锁
	private Object obj = new Object();
	// 卖票 票--
	@Override
	public void run() {
		// 循环卖
		while (true) {
			// 同步锁
			synchronized (obj) {
				// 判断卖完没
				if (ticket > 0) {
					// 为了让测试结果更明显 加个休眠
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					// 卖票
					System.out.println(Thread.currentThread().getName() 
							+ "****" + ticket);
					// 卖一张 少一张
					ticket--;
				} else {
					// 卖光了 结束循环
					break;
				}
			}
			// 让出CPU的执行资源(随机让出 随机性)
			Thread.yield();
		}
	}
}

在主线程中创建三个子线程 相当于三个平台卖票:

public static void main(String[] args) {
	// 创建三个线程
	TicketRunnable runnable = new TicketRunnable();
	Thread t1 = new Thread(runnable);
	Thread t2 = new Thread(runnable);
	Thread t3 = new Thread(runnable);
	// 开启线程
	t1.start();
	t2.start();
	t3.start();
}

那么同步代码块是怎么做到一个线程执行代码时 其他线程在外面等待的?

同步代码块的规则:

当线程进入同步代码块的时候 先看一下有没有锁

如果有锁 就近入同步代码块中执行代码 进去的同时 会获取这把锁

当执行完毕 出同步代码块时 会将这把锁释放还回去

如果没锁 线程在同步代码块前等待(等着有锁才能进)

二.同步方法

作用和同步代码块一样

也是用synchronized关键字 只是该关键字声明在方法上

同一时间只能有一个线程进入到同步方法中 执行代码

同步方法中 也是写操作共享数据的代码

把上面的例子用同步方法来写:

用接口实现线程的方式

在接口的实现类中再写一个同步方法 如下:

class TicketRunnable1 implements Runnable {
	// 声明票
	private  static int ticket = 50;
	// 卖票 票--
	@Override
	public void run() {
		// 循环卖
		while (true) {
				// 判断卖完没
				if (sellTicket()) {
					break;
				}
			}
			// 让出CPU的执行资源(随机让出 随机性)
			Thread.yield();
		}
	//声明同步方法
	public synchronized boolean sellTicket() {
		if (ticket >= 0) {
			// 为了让测试结果更明显 加个休眠
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			// 卖票
			System.out.println(Thread.currentThread().getName() 
					+ "****" + ticket);
			// 卖一张 少一张
			ticket--;
			return false;
		} else {
			// 卖光了 结束循环
			System.out.println("票已售空");
			return true;
		}
	}
}

几个小问题:

1.同步方法怎么处理的 有没有锁 锁是什么?

有锁 处理方式和同步代码块是一样的 锁是同步的对象(成员)方法 this()本类的对象 

2.静态方法能不能加锁?

3.静态方法的锁是this吗?

不是 (静态方法不能使用this) 静态方法使用的锁是类锁  类名.class表示这个类

三.锁类 ReentranLock

JDK1.5的Lock接口 使用实现类ReentrantLock

方法:

lock();获取锁

unlock();释放锁 保证锁一定会被释放

使用前提和同步代码块一样 要保证用的是同一把锁

使用格式:

try{

    操作共享数据的代码

}finally{

    释放锁

}

还是上面的例子 用锁类的方法来写:

class TicketRunnable2 implements Runnable {
	// 声明票
	private int ticket = 50;
	//声明锁 保证同一把
	private ReentrantLock lock = new ReentrantLock();
	// 卖票 票--
	@Override
	public void run() {
		// 循环卖
		while (true) {
			lock.lock();
			try {
				// 判断是否卖完
				if (ticket >= 0) {
					// 为了让测试结果更明显 加个休眠
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					// 卖票
					System.out.println(Thread.currentThread().getName() 
							+ "****" + ticket);
					// 卖一张 少一张
					ticket--;
				} else {
					// 卖光了 结束循环
					break;
				}
			} catch (Exception e) {
				// TODO: handle exception
			}finally {
				lock.unlock();
			}	
			// 让出CPU的执行资源(随机让出 随机性)
			Thread.yield();
		}
	}
}
Thread.yield();方法可以让出CPU的执行资源(随机让出 随机性)

四.死锁

前提:

1. 必须要有同步锁的嵌套

        synchronized(){

                    synchronized(){

                    }

        }

2. 锁对象要唯一(使用的是同一把锁)

两把锁都要保证唯一

测试:

创建两个锁类用来创建锁对象

//创建A锁
class LockA{
	//为了唯一 不让外人创建 自己创建
	private LockA(){
		
	}
	//定义一个常量
	//能在外界获取到 但是不能修改
	public static final LockA LOCK_A = new LockA();
}
//创建B锁
class LockB{
	//为了唯一 不让外人创建 自己创建
	private LockB(){
		
	}
	//定义一个常量
	//能在外界获取到 但是不能修改
	public static final LockB LOCK_B = new LockB();
}

创建一个接口实现类来创建线程

class DieLockRunnable implements Runnable{
	//声明一个标记
	private boolean isFlag = true;
	//第一次 先进A锁再进B锁
	//下一次 先进B锁再进A锁
	@Override
	public void run() {
		//死循环 为了增加死锁的几率
		while (true) {
			if (isFlag) {
				//A -> B
				synchronized (LockA.LOCK_A) {
					System.out.println("我是if的LOCK_A锁");
					synchronized (LockB.LOCK_B) {
						System.out.println("我是if的LOCK_B锁");
					}
				}
			}else {
				//B -> A
				synchronized (LockB.LOCK_B) {
					System.out.println("我是else的LOCK_B锁");
					synchronized (LockA.LOCK_A) {
						System.out.println("我是else的LOCK_A锁");
					}
				}
			}
			//修改标记
			isFlag = !isFlag;
		}	
	}
}

五.线程的停止问题

stop()方法停止线程已经过时 不推荐使用

interrupt()方法 中断线程

interrupt方法的作用:

1.调用interrupt方法时 线程中有wait()/sleep()等方法 这时会抛出一个异常

        InterruptException异常 并且清除中断状态

2.调用interrupt方法时 线程中没有上述方法 这时会设置(改变)中断状态的值(true --> false)

用标记法中断线程:

用接口类的方法创建线程

class InterruptRunnable implements Runnable{
	//声明一个标识 用来关闭或者停止线程
	public boolean isFlag = false;
	@Override
	public void run() {
		//直接使用中断状态来中断线程
		//Thread.currentThread().isInterrupted()
		while (!isFlag) {
			//休眠
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName());
		}
	}	
}
public static void main(String[] args) {
	InterruptRunnable runnable = new InterruptRunnable();
	Thread t1 = new Thread(runnable);
	t1.start();
	//给子线程几秒的执行时间 然后再中断线程
	try {
		Thread.sleep(3000);
	} catch (InterruptedException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	//标记中断线程
	runnable.isFlag = true;
	System.out.println("中断线程");
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	System.out.println("主线程结束");
}
wait()方法是Object类中的方法 是让线程等待 并且如果没有被唤醒 就会一直等待下去

直接使用wait()方法会出现异常 IllegalMonitorStateException

注意:wait()方法 必须使用锁对象去调用

当子线程中修改了状态 不能及时同步到主线程中

这时可以使用volatile关键词 让线程中的这个状态信息可以及时得到修改

class VolRunnable implements Runnable{
	//当子线程中修改了状态 不能及时同步到主线程中
	//这时可以使用volatile关键词 让线程中的这个状态信息 可以及时得到修改
	//标记
	public volatile boolean isFlag = false;
	//声明一个变量 记录 什么时候更改状态
	private int num = 0;
	@Override
	public void run() {
		while (!isFlag) {
			num++;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (num > 5) {
				//更改中断状态
				isFlag = true;	
			}
			System.out.println(Thread.currentThread().getName() + "..run");
		}
	}
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值