java并发基础篇

191 阅读5分钟

死锁

什么是死锁?

如何分析

通过资源分配图分析,如果成环,就可能出现死锁

直接dump线程查看到底哪个线程出现问题,通过jstack 命令或者Java VisualVM这个可视化工具将 JVM 所有的线程栈信息导出来

解决办法

只有以下这四个条件都发生时才会出现死锁:

  • 互斥:共享资源 X 和 Y 只能被一个线程占用;(锁)
  • 占有且等待:线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
  • 不可抢占:其他线程不能强行抢占线程 T1 占有的资源;
  • 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

只要破坏其中一个,就可以成功避免死锁的发生。

破坏方案:

  • 破坏“占用且等待”,我们可以一次性申请所有的资源,这样就不存在等待了。
  • 破坏“不可抢占”,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。
  • 破坏对于“循环等待”,可以靠按序申请资源来预防。

用面向对象思想写并发编程

封装共享变量

其中不变的变量用final关键字,可变的变量加锁实现原子操作

指定并发访问策略

避免共享

局部变量是线程安全的(利用线程的本地存储)

同步工具

synchronized,lock,读写锁,volatile等同步工具

线程

线程状态转换

TIMED_WAITING 和 WAITING 状态的区别,仅仅是触发条件多了超时参数。

并发包中的锁,都是基于LockSupport.park() 实现的。调用 LockSupport.park() 方法,当前线程会阻塞。调用 LockSupport.unpark(Thread thread) 可唤醒目标线程。

线程的使用

Thread常用方法

方法 描述
start() 启动一个线程
yield() 线程让步,让出CPU权,从运行状态进入到就绪状态,不会释放锁。
sleep() 线程休眠,从运行状态进入进入阻塞状态,但不会释放锁,休眠结束,会由阻塞状态变成就绪状态
join() 让子线程执行完,再执行父线程,由于底层是调用wait方法,所以会释放锁
wait() 让当前线程处于“等待(阻塞)状态”(当前线程指正在cpu上运行的线程),直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
notify() 唤醒在此对象监视器上等待的单个线程。
notifyAll() 唤醒在此对象监视器上等待的所有线程。
interrupt() 中断线程。可以中断阻塞线程,不能中断运行线程
boolean isInterrupted() 检测对象的“中断标记”,并清除标记
boolean interrupted() //检测对象的“中断标记”

创建线程


// 自定义线程对象
class MyThread extends Thread {
  public void run() {
    // 线程需要执行的代码
    ......
  }
}
// 创建线程对象
MyThread myThread = new MyThread();

// 实现Runnable接口
class Runner implements Runnable {
  @Override
  public void run() {
    // 线程需要执行的代码
    ......
  }
}
// 创建线程对象
Thread thread = new Thread(new Runner());

启动线程

调用Thread的start()方法

中断线程

Java的中断是一种协作机制。也就是说调用线程对象的interrupt方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己。每个线程都有一个boolean的中断状态(这个状态不在Thread的属性上),interrupt方法仅仅只是将该状态置为true。

方法介绍

public void interrupt()//可以中断阻塞线程,不能中断运行线程(需要额外增加标记)
public boolean isInterrupted()//检测对象的“中断标记”,并清除标记
public static boolean interrupted() //检测对象的“中断标记”

中断阻塞状态线程

使用thread.interrupt()

class Example3 extends Thread {
    public static void main(String args[]) throws Exception {
        Example3 thread = new Example3();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        thread.interrupt();// 等中断信号量设置后再调用
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }
 
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread running...");
            try {
                Thread.sleep(1000);//线程阻塞,如果线程收到中断操作信号将抛出异常
            } catch (InterruptedException e) {//从阻塞状态进入就绪状态
                //抛出异常后,中断标示会被清除
                Thread.currentThread().interrupt();//如果想中断线程,则需要重新设置中断位。
            }
        }
        System.out.println("Thread exiting under request...");
    }
}

注意:不可中断的操作,包括进入synchronized段以及Lock.lock(),inputSteam.read()等,调用interrupt()对于这几个问题无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去。

中断非阻塞状态线程

1、使用thread.interrupt()

class Example2 extends Thread {
    public static void main(String args[]) throws Exception {
        Example2 thread = new Example2();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        // 发出中断请求
        thread.interrupt();
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }
 
    public void run() {
        // 每隔一秒检测是否设置了中断标示
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            // 使用while循环模拟 sleep
            while ((System.currentTimeMillis() - time < 1000) ) {
            }
        }
        System.out.println("Thread exiting under request...");
    }
}

2、使用中断信号量中断非阻塞状态的线程

class Example2 extends Thread {
    volatile boolean stop = false;// 线程中断信号量
 
    public static void main(String args[]) throws Exception {
        Example2 thread = new Example2();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        // 设置中断信号量
        thread.stop = true;
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }
 
    public void run() {
        // 每隔一秒检测一下中断信号量
        while (!stop) {
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            /*
             * 使用while循环模拟 sleep 方法,这里不要使用sleep,否则在阻塞时会 抛
             * InterruptedException异常而退出循环,这样while检测stop条件就不会执行,
             * 失去了意义。
             */
            while ((System.currentTimeMillis() - time < 1000)) {}
        }
        System.out.println("Thread exiting under request...");
    }
}

线程中断参考:线程中断详解