JAVA多线程程序中常见的问题
- 线程中断
- 线程的六种状态
- 多线程安全问题
- 线程不安全
- 线程不安全的解决方案
- 隐式锁——synchronized
- 显式锁——Lock
- 可能出现的问题——线程死锁
- 产生的条件
- 代码中可能导致死锁产生的情况
- 避免的方法
线程中断
由启动线程的常用方法——start( ),会让人想当然的认为停止线程的方法为——stop( )。在JAVA早期的多线程程序中,确实存在stop( )的情况,但随着不断的使用,发现用这类方法来停止或中断线程会导致各种各样的情况出现,例如在线程中使用的文件在stop( )后仍然被占用,开启的数据流在stop后无法关闭等…因此JAVA在之后的版本中弃用了这种方法。
JAVA官方认为:线程是否要结束应由线程自身决定,因此提出了一种新的线程停止的方式。
- 在线程的调用程序块中使用 线程名.interrupt方法,可以给到线程一个中断标记。
- 在线程运行过程中,如果发现了中断标记,则线程会抛出异常InterruptException。
- 线程代码中可以try catch捕获这个异常,在异常处理语句块中进行相关资源释放。
- 最后线程通过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();
}
}
}
}
运行结果:
线程的六种状态
- new 刚创建的线程处于此状态
- Runnable 线程正在虚拟机内执行语句
- Blocked 阻塞状态
可能导致线程处于此状态的原因:
1.没抢到锁
2.没抢到时间片
3.执行耗时多的操作 - Waiting 无限期等待被唤醒
- Timed_Waiting 有限期等待被唤醒 超时会自动醒来
- 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的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。
产生的条件
- 互斥条件:一个资源,或者说一个锁只能被一个线程所占用,当一个线程首先获取到这个锁之后,在该线程释放这个锁之前,其它线程均是无法获取到这个锁的。
- 占有且等待:一个线程已经获取到一个锁,再获取另一个锁的过程中,即使获取不到也不会释放已经获得的锁。
- 不可剥夺条件:任何一个线程都无法强制获取别的线程已经占有的锁
- 循环等待条件:线程A拿着线程B的锁,线程B拿着线程A的锁。
代码中可能导致死锁产生的情况
- 套娃调用会产生锁的代码块或方法
- 在一个锁未解开的时候另一个锁又锁上
解决方法:不要出现这种套娃调用的情况
避免的方法
总体来说,死锁有2种避免的方法:
- 注意代码中线程加锁的顺序,按相同的顺序加锁。
- 抢锁超时,给线程加上获取锁的超时时间,如果超过这个时间还不能获得锁,就放弃获取锁。