Java学习之同步锁、死锁与interrupt()方法
一.同步锁(同步代码块)
在Java中使用多线程,你就不能绕过同步锁这个概念。这在多线程中是十分重要的。
在Java多线程的使用中,你必然会遇到一个问题:多个线程共享一个或者一组资源,该怎么办?
举一个很常见的一个例子,三个窗口同时售票,如何保证 票不会被重复买出去?
1.用 synchronized(锁){上锁代码 } 来实现
public class SimplExamp {
public static void main(String[] args) {
// 利用接口的实现类 创建三个线程
Tickets tickets = new Tickets();
// 创建3个线程
Thread t1 = new Thread(tickets);
Thread t2 = new Thread(tickets);
Thread t3 = new Thread(tickets);
// 开启线程
t1.start();
t2.start();
t3.start();
}
}
// 利用接口资源 来保证 访问的共享资源
class Tickets implements Runnable{
// 票总数
private int tickets = 50;
// 声明锁对象(保证锁也是线程共享的)
private final Object obj = new Object();
// 卖票
@Override
public void run() {
// 利用循环 保证票都能卖出去
while (true) {
synchronized (this) {
// 休眠(放大问题)
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 判断票
if (tickets > 0) {
// 可以卖
System.out.println(Thread.currentThread().getName() + "--" + tickets);
// 卖票
tickets--;
} else {
// 卖光了
break;
}
}
// 让线程让出CPU的执行资源(可能让出 增加几率)
Thread.yield();
}
}
}
利用同步锁就能很简单的解决这个问题。
同步代码块(同步锁)
- 写法:
synchronized(锁){
上锁代码
} - 执行的过程:
当线程进入同步代码块 会把锁拿走 执行代码块中的代码。
当代码执行完毕后 会把锁还回去。
如果线程遇到同步代码块,发现没有锁,将进入等待(有锁才能进)。 - 锁注意: 保证所有线程使用的是同一把锁
锁可以使用任意一个对象(同一对象就行)
2.用同步方法来实现
封装方法
同步方法
写法:使用synchronized关键字修饰方法
原理跟同步代码块一样。
成员方法 使用的对象锁是this
同步方法可以是静态方法(成员变量也要用静态的)
锁不是this(静态方法不能用this)
静态方法使用的锁 类锁 本类.class
// 利用接口资源 来保证 访问的共享资源
class Tickets1 implements Runnable{
// 票总数
private static int tickets = 50;
// 声明锁对象(保证锁也是线程共享的)
// 卖票
@Override
public void run() {
// 利用循环 保证票都能卖出去
while (true) {
if (sellTickets()) {
break;
}
// 让线程让出CPU的执行资源(可能让出 增加几率)
Thread.yield();
}
}
public static synchronized boolean sellTickets() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "--" + tickets);
tickets--;
return false;
}else {
// 卖完了
return true;
}
}
3.用 Lock 来实现
lock();加锁方法
unlock();释放锁方法
要保证出现异常时也能把锁关闭
写法:
lock()
try{
-
加锁代码
}finally{
-
unlock()释放锁
}
public void run() {
// 利用循环 保证票都能卖出去
while (true) {
// 使用lock锁
lock.lock();
try {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "--" + tickets);
tickets--;
}else {
break;
}
} finally {
// 释放锁
lock.unlock();
}
二.死锁
- 前提:
1.至少两个线程
2.需要有锁的嵌套(同步代码块的嵌套)
3.两把锁 - 线程1和线程2同时访问有嵌套的同步代码块程序
并且有两个锁 A和B
线程1拿到了A 向进入下一个代码块需要B锁
线程2拿到了B 向进入下一个代码块需要A锁
这时谁也进不去 线程进入相互等待的状态 导致程序卡住。
public class SimpleExample {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
t1.start();
t2.start();
}
}
// 声明锁
class LockA{
// 私有化构造方法
private LockA() {
// TODO Auto-generated constructor stub
}
// 创建锁对象(声明一个常量)
public static final LockA A =new LockA();
}
class LockB{
// 私有化构造方法
private LockB() {
// TODO Auto-generated constructor stub
}
// 创建锁对象(声明一个常量)
public static final LockB B =new LockB();
}
// 线程
class MyThread implements Runnable{
// 利用标识来控制 先A->B 或 先B->A
boolean isTure = false;
@Override
public void run() {
// 利用死循环 增加死锁几率
while (true) {
// 不断地让两个线程先进A锁再进B锁
// 下一次从B锁进A锁
if (!isTure) {
// 先进A锁再进B锁 (同步代码块嵌套)
synchronized (LockA.A) {
System.out.println("我是if中的A锁");
synchronized (LockB.B) {
System.out.println("我是if中的B锁");
}
}
}else {
// 下一次从B锁进A锁
synchronized (LockB.B) {
System.out.println("我是else中的B锁");
synchronized (LockA.A) {
System.out.println("我是else中的A锁");
}
}
}
isTure = !isTure;
}
}
}
三.如何让线程停止
1.interrupt()方法
interrupt() 并不能中断线程。
interrupt() 作用
中断状态将被设置
1.可以改变中断状态 就是个布尔值 初值false --> true
2.当你这个线程中 使用 sleep wait join 方法时
会抛出InterruptedException异常
中断状态将被清除 这时interrupted()的值还是false
- 如何让线程停止?
- 正确方式:使用标记停止线程
public class Demo06 {
public static void main(String[] args) {
InterruptRunnable interruptRunnable = new InterruptRunnable();
Thread t = new Thread(interruptRunnable);
t.start();
// 休眠几秒 给子线程运行时间
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 中断线程
//t.interrupt();
// 利用标记停止线程
interruptRunnable.isTrue = true;
System.out.println("线程中断");
// 让主线程运行一会
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
class InterruptRunnable implements Runnable{
// 声明标记 控制线程的停止
public boolean isTrue = false;
@Override
public void run() {
while (!isTrue) {
// 线程休眠
// long time = System.currentTimeMillis();
// while (System.currentTimeMillis() - time < 1000) {
//
// }
try {
// InterruptedException 中断异常
// 中断状态被清除指的是
// 从休眠状态-->运行状态
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "run");
}
}
}
四. 子线程中修改状态 主线程中是否能立即接受
当你从主线程中 修改状态时
主线程不能立即接收到这个状态的改变
使用关键词 volatile 来标识你改变的状态的变量
效果:可以让主线程立即接收到 改变的值
public class Demo08 {
public static void main(String[] args) {
ChangeRunnable runnable = new ChangeRunnable();
Thread thread = new Thread(runnable);
thread.start();
// 利用线程中的标记 卡住主线程
while (!runnable.isTrue) {
}
System.out.println("主线程结束");
}
}
class ChangeRunnable implements Runnable{
// 当你从主线程中 修改状态时
// 主线程不能立即接收到这个状态的改变
// 使用关键词 volatile 来标识你改变的状态的变量
// 效果:可以让主线程立即接收到 改变的值
public volatile boolean isTrue = false;
// 记录循环次数
private int num = 0;
@Override
public void run() {
while (!isTrue) {
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (num == 5) {
// 修改状态
isTrue = true;
}
System.out.println(Thread.currentThread().getName() + "--" + num);
}
}
}