天天看点

Stoker的Java学习之同步锁、死锁与如何让线程停止

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);
        }
        
    }
    
}