Java并发编程(1)
基础
1、并行跟并发的区别
[*] 并行:同一时刻,多个线程都在执行,这就要求有多个CPU分别执行多个线程。
[*]并发:在同一时刻,只有一个线程执行,但在一个时间段内,两个线程都执行了。其实现依赖于CPU切换线程,因为切换时间很短,所以基本对于用户是无感知的。
2、什么是进程和线程
[*]进程:程序运行起来后在内存中执行,并附带有运行所需的资源,是系统进行资源分配的基本单位。
[*]线程:CPU是被分配到线程的,所以线程是CPU分配的基本单位。在Java中,当我们启动一个main函数就相当于启动了一个JVM进程,而main函数的线程就是主线程。一个进程中有多个线程,多个线程共用进程的堆和方法区,但每个线程都有自己的程序计数器和栈。
3、线程有几种创建方式
[*]继承Thread类:重写run方法,调用start()方法启动线程。
[*]缺点:单继承,继承了Thread就不能继承别的类了
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("this is child thread");
}
}
public static void main(String[] args){
MyThread thread = new MyThread();
thread.start();
}
[*]实现Runnable接口:重写run()方法,这是一个任务,要包装成Thread才能start。
[*]优点:只需要实现Runnable,业务逻辑和线程机制解耦。可以被多个线程复用。
[*]缺点:也是用完就销毁。
public class RunnableTask extends Runnable {
public void run() {
System.out.println("Runnable!");
}
}
public static void main(String[] args){
RunnableTask task = new RunnableTask();
new Thread(task).start();
}
[*]实现Callable接口:重写call()方法,通过FutureTask获取任务执行的返回值。
public class CallerTask implements Callable < String > {
public String call () throws Exception {
return "Hello,i am running!" ;
}
public static void main ( String [] args) {
//创建异步任务
FutureTask < String > task = new FutureTask < String > ( new CallerTask ());
//启动线程
new Thread ( task ) .start();
try {
//等 待 执 ⾏ 完 成 ,并获取返回结果
String result = task . get();
System.out.println ( result) ;
} catch ( InterruptedException e ) {
e.printStackTrace ();
} catch ( ExecutionException e ) {
e.printStackTrace ();
}
}
}
4、为什么调用start()方法时会执行run()方法?那为什么不直接调用run()
[*] JVM执行start方法,会先创建一个线程,由创建出来的线程去执行run方法,才能起到多线程的效果。如果直接调用run方法,那么run还是运行在主线程中,相当于顺序执行。
5、线程有哪些常用的调度方法
6、线程有几种状态
状态说明NEW初始状态:线程被创建,还没调用start方法RUNNABLE运行状态:就绪 + 运行BLOCKED阻塞状态:阻塞于锁WAITING等待状态:等待其他线程做出一定动作(通知或中断)TIME_WATING超时等待:指定时间自行返回TERMINATED终止状态:已经执行完毕
[*]初始状态:用new Thread()创建线程对象,还没调用start()时,此时只是被实例化。
[*]就绪:线程已经具备运行条形,只差操作系统调度。
[*]运行:当CPU把时间片分配给线程时,进入Running,执行run()方法里的代码。可能会发生:
[*]执行完毕-->终止
[*]调用yield()-->放弃CPU,回到就绪
[*]被系统抢占调度-->回到就绪
[*]进入阻塞或等待-->转到响应状态
[*]阻塞:当线程尝试获取锁,但锁被其他线程占用,会进入阻塞状态。一旦获取锁,就进入running状态
[*]等待:线程调用wait()、join()等时,会进入等待状态。必须由notify()或notifyAll()才能唤醒,回到runnable状态
[*]超时等待:线程调用带超时参数的方法进入超时等待状态。到了超时时间或被notiry()唤醒后回到runnable
[*]终止:执行完run()或抛出未捕获异常结束时,进入终止状态。
7、什么是线程上下文切换
CPU资源分配采用时间片轮转,也就是给每个线程分配一个时间片,线程在时间片内占用CPU执行任务。当线程使用完时间片后,就会处于就绪状态并让出CPU让其他线程用,这就是线程上下文切换。
8、守护线程了解吗
Java中的线程分为两类:daemon线程(守护线程)和user线程(用户线程)
在JVM启动时会调用main函数,其所在线程就是一个用户线程。JVM内部还启动了很多守护线程,如垃圾回收线程。
守护线程和用户线程的区别是,当最后一个非守护线程结束时,JVM会正常退出(此时不管当前是否存在守护线程)。
9、线程间有哪些通信方式
[*] volatile和synchronized关键字:
[*]volatile:保证变量在多线程间的可见性,不保存原子性。
[*]synchronized:保证同一时刻只有一个线程执行被保护的代码块,提供原子性和可见性。常用于操作共享变量、临界区的互斥访问。
[*]等待/通知机制:线程调用wait()进入等待队列,释放锁;另一个线程在条件满足后调用notify或notifyAll唤醒。
[*]管道输入/输出流:用于线程间直接传递数据。一个线程写入 PipedOutputStream,另一个线程从 PipedInputStream 读取。适合流式传输。
[*]使用Thread.join():一个线程等待另一个线程执行完毕后再继续。常用于 主线程等待子线程结果 的场景。
[*]使用ThreadLocal:为每个线程单独提供一个变量副本。线程间不共享数据,避免竞争和加锁。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! 东西不错很实用谢谢分享
页:
[1]