JAVA多线程程序中常见的问题

  • 线程中断
  • 线程的六种状态
  • 多线程安全问题
  • 线程不安全
  • 线程不安全的解决方案
  • 隐式锁——synchronized
  • 显式锁——Lock
  • 可能出现的问题——线程死锁
  • 产生的条件
  • 代码中可能导致死锁产生的情况
  • 避免的方法


线程中断

由启动线程的常用方法——start( ),会让人想当然的认为停止线程的方法为——stop( )。在JAVA早期的多线程程序中,确实存在stop( )的情况,但随着不断的使用,发现用这类方法来停止或中断线程会导致各种各样的情况出现,例如在线程中使用的文件在stop( )后仍然被占用,开启的数据流在stop后无法关闭等…因此JAVA在之后的版本中弃用了这种方法。
JAVA官方认为:线程是否要结束应由线程自身决定,因此提出了一种新的线程停止的方式。

  1. 在线程的调用程序块中使用 线程名.interrupt方法,可以给到线程一个中断标记。
  2. 在线程运行过程中,如果发现了中断标记,则线程会抛出异常InterruptException。
  3. 线程代码中可以try catch捕获这个异常,在异常处理语句块中进行相关资源释放。
  4. 最后线程通过return;语句 就自我了结;当然如果不return 线程就可以不自我了结,捕获异常后若无其事的继续执行。
public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i = 0;i<100;i++){
            try {
                System.out.println(getName()+"线程正在运行:"+i);
                sleep(10);
            } catch (InterruptedException e) {
                System.out.println("收到了打断标记");
                System.out.println("我自我了结了");
                return;
            }
        }
    }
}
public class demo1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程正在执行:" + i);
            Thread.currentThread().sleep(10);
            if(i == 50){
                t.interrupt();
            }
        }
    }
}

运行结果:

java 线程更新数据库失败 java线程突然不执行了_JAVA

线程的六种状态

  1. new 刚创建的线程处于此状态
  2. Runnable 线程正在虚拟机内执行语句
  3. Blocked 阻塞状态
    可能导致线程处于此状态的原因:
    1.没抢到锁
    2.没抢到时间片
    3.执行耗时多的操作
  4. Waiting 无限期等待被唤醒
  5. Timed_Waiting 有限期等待被唤醒 超时会自动醒来
  6. Terminated 线程终止 线程死亡状态

多线程安全问题

多线程程序中,存在多个线程同时操作同一个变量的情况,有可能一个线程刚判断完某个变量现在的状态符合判断条件,下一个时刻,另一个线程就将变量的值修改,而刚做完判断的线程还在照常的执行原程序,就导致程序出现BUG,这就是线程不安全的一种典型情况。

线程不安全

线程不安全,简而言之就是多个线程争先插足导致程序中变量异常。

线程不安全的解决方案

JAVA中为了应对线程不安全问题设置了几种解决方案,可以将线程不安全的状况转变为线程安全,所谓线程安全,就是多条线程对于要执行的代码进行排队执行或争抢执行,但一段时间内只有一条线程在进行代码的执行,线程安全的情况下,相对来说程序的执行效率比线程不安全时低,但可以避免多个线程争先插足导致程序中变量异常的情况。

隐式锁——synchronized

synchronized用于同步代码块:

使用synchronized(){
}
将需要同步的代码块包括起
在()中放入同一个object对象作为锁
每个进程都会争抢锁 抢到后将代码块锁住
等到抢到锁的代码块运行结束后会解开锁
其他进程再争抢 没抢到的就继续等待下一次抢

synchronized用于同步方法:

将任务类中调用的方法使用synchronized修饰
当多个线程执行同一个任务对象中的方法时就可实现进程同步
如果是多个任务对象分配给多个线程则不会同步
同步方法的锁
	非静态方法  则锁对象是调用这个方法的this
	静态方法  锁的对象是 类名.class
如果同步代码块和同步方法使用的锁都为this 则 两个代码块只能执行一个
如果是2个同步方法也是一样,一个被执行,使用锁为this 则另一个也为this 就不能被执行

显式锁——Lock

显式锁相对来说更能体现JAVA OOP的特性。
显式锁Lock的使用方法是这样的:

Lock myLock = new ReentrantLock();
	在要加锁的位置调用myLock.lock() 即可加锁
	解锁的位置调用myLock.unlock() 即可解锁
	锁中的代码在遇到多线程的情况下,仅抢到锁的线程可以执行,执行完解锁后其余线程才可能抢到锁执行锁中的代码。

公平锁与非公平锁

一堆线程排队,先来先得 即为公平锁
	使用方法:在上面的ReentrantLock() 构造方法中传入true即可
	JAVA默认为非公平锁

可能出现的问题——线程死锁

线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。

产生的条件

  1. 互斥条件:一个资源,或者说一个锁只能被一个线程所占用,当一个线程首先获取到这个锁之后,在该线程释放这个锁之前,其它线程均是无法获取到这个锁的。
  2. 占有且等待:一个线程已经获取到一个锁,再获取另一个锁的过程中,即使获取不到也不会释放已经获得的锁。
  3. 不可剥夺条件:任何一个线程都无法强制获取别的线程已经占有的锁
  4. 循环等待条件:线程A拿着线程B的锁,线程B拿着线程A的锁。

代码中可能导致死锁产生的情况

  1. 套娃调用会产生锁的代码块或方法
  2. 在一个锁未解开的时候另一个锁又锁上
    解决方法:不要出现这种套娃调用的情况

避免的方法

总体来说,死锁有2种避免的方法:

  1. 注意代码中线程加锁的顺序,按相同的顺序加锁。
  2. 抢锁超时,给线程加上获取锁的超时时间,如果超过这个时间还不能获得锁,就放弃获取锁。