Java怎么保证多线程运行安全?
线程安全是程序设计中的术语,指某个方法在多线程环境中被调用时,能正确的处理多个线程中的共享变量,是程序正确执行。
Java中线程安全体现在以下三个方面:
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作
可见性:一个线程对主内存的修改可以及时地被其他线程看到
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序
因此,只要满足上述三个条件,我们就可以说改代码是线程安全的。那么,Java中提供了如下解决方案:
使用sychronized关键字
使用线程安全类,如:java.util.concurrent包下的类
使用并发包下Lock相关锁
总结:想要代码满足线程安全,只需要代码满足原子性,可见性,有序性即可。
线程和进程的区别?Java实现的多线程的方式有哪几种?
线程和进程的区别:
进程是程序的一次动态执行过程,每个进程都有自己的独立的内存空间。一个应用程序可以同时启动多个进程(比如浏览器可以开多个窗口,每个窗口就是一个进程)
多进程操作系统能够运行多个进程,每个进程都能够循环利用所需要的CPU时间片,使得所有进程看上去像在同时运行一样。
线程是进程的一个执行流程,一个进程可以由多个线程组成,也就是一个进程可以同时运行多个不同的线程,每个线程完成不同的任务。
线程的并发运行:就是一个进程内若干个线程同时运行。(比如:word的拼写检查功能和首字母自动大写功能是word进程中的线程)
线程和进程的关系是一个局部和整体的关系,每个进程都有操作系统分配独立的内存地址空间,而同一进程的多有线程都在同意地址空间工作。
多线程的实现方式:Java多线程实现方式主要有四种:继承Thread类,实现Runnable接口,实现Callable接口通过FutureTask包装器来创建Thread线程,使用ExecutorService,Callable,Future实现有返回结果的多线程。
继承Thread类,重写run方法
实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
通过Callable和FutureTask创建线程
通过线程池创建线程
线程有哪些基本状态,并描述每种状态?
![](https://i-blog.csdnimg.cn/blog_migrate/5bf6932e98c45d46661a2d762521d941.png)
同步和一部的区别?
同步:发出一个调用之后,在没有得到结果之前,该调用就不可以返回,一直等待。
异步:调用在发出之后,不用等待返回结果,该调用直接返回。
并发与并行的区别?
并发:两个及两个以上的作业在同一时间段内执行。
并行:两个及两个以上的作业在同一时刻执行。
最关键的点是:是否是同时执行。
线程的run()和start()有什么区别?
start:它的作用是启动一个新线程。通过start()方法启动线程,处于就绪(可运行)状态,一旦得到cpu时间片,就开始执行相应线程的run()方法,这个方法run()成为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。start()不能被重复调用。用start方法来启动线程,真正实现了多线程运行,即无需等待某个线程的run方法体代码执行完毕就直接继续执行下面的代码。这里无需等待run方法执行完毕,即可继续执行下面的代码,即进行了线程切换。
run():就和普通的成员方法一样,可以被重复调用。如果直接调用run方法,并不会启动新线程!程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可以继续执行下面的代码,这样就没有达到多线程的目的。
总结:调用start()方法方可启动线程,而run()方法只是thread的一个普通方法调用,还是主线程里执行。
说一下runnable和callable有什么区别?
最大的区别,runnable没有返回值,而现实callable接口的任务线程能返回执行结果
callable接口实现类中的run方法允许异常向上抛出,可以在内部处理,try catch,但是runnable接口实现类中run方法的异常必须在内部处理,不能抛出。
什么是线程死锁
线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
如下图所示,线程A持有资源2,线程B持有资源1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
![](https://i-blog.csdnimg.cn/blog_migrate/7ce530f89c444796f86b12c6ec6d6368.png)
产生死锁必须具备以下四个条件:
互斥条件:该资源任意一个时刻只有一个线程占用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
如何避免线程死锁?
为了避免死锁,只要破坏产生死锁的四个条件中的其中一个就可以了。
破坏互斥条件:这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)
破坏请求与保持条件:一次性申请所有的资源
破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏等待条件
sleep()方法和wait()方法区别和共同点
两者最主要的区别在于:sleep方法没有释放锁,而wait方法释放了锁
两者都可以暂停线程的执行
wait通常被用于线程间交互/通信,sleep通常被用于暂停执行
wait()方法被停用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法。sleep()方法执行完成后,线程会自动苏醒,或者可以使用wait(long timeout)超时后线程会自动苏醒。
现在有线程T1、T2和T3,如何确保T2线程在T1之后执行,并且T3线程在T2之后执行?
在多线程中有多种⽅法让线程按特定顺序执⾏,你可以⽤线程类的join()⽅法在⼀个线程中启动另⼀个线程,另外⼀个线程完成该线程继续执⾏。为了确保三个线程的顺序你应该先启动最后⼀个(T3调⽤T2,T2调⽤T1),这样T1就会先完成⽽T3最后完成。
实际上先启动三个线程中哪⼀个都⾏,
因为在每个线程的run⽅法中⽤join⽅法限定了三个线程的执⾏顺序。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
public void run() {
System.out.println("1");
}
};
Thread t2 = new Thread() {
public void run() {
System.out.println("2");
}
};
Thread t3 = new Thread() {
public void run() {
System.out.println("3");
}
};
t1.start();
t1.join();//阻塞住直到t1完成
t2.start();
t2.join();
t3.start();
System.out.println("end");
}
volatile关键字
保持内存可见性:所有线程都能看到共享内存的最新状态。
防止指令重排