Java并发编程(1)
<p> </p><hr>
<h1 style="text-align: center">基础</h1>
<h1>1、并行跟并发的区别</h1>
<ul>
<li> 并行:同一时刻,多个线程都在执行,这就要求有多个CPU分别执行多个线程。</li>
<li>并发:在同一时刻,只有一个线程执行,但在一个时间段内,两个线程都执行了。其实现依赖于CPU切换线程,因为切换时间很短,所以基本对于用户是无感知的。</li>
</ul>
<h1>2、什么是进程和线程</h1>
<ul>
<li><strong>进程</strong>:程序运行起来后在内存中执行,并附带有运行所需的资源,是系统进行资源分配的基本单位。</li>
<li><strong>线程</strong>:CPU是被分配到线程的,所以线程是CPU分配的基本单位。在Java中,当我们启动一个main函数就相当于启动了一个<strong>JVM进程</strong>,而main函数的线程就是主线程。一个进程中有多个线程,多个线程共用进程的堆和方法区,但每个线程都有自己的程序计数器和栈。</li>
</ul>
<h1>3、线程有几种创建方式</h1>
<ul>
<li><strong>继承Thread类</strong>:重写run方法,调用start()方法启动线程。
<ul>
<li>缺点:单继承,继承了Thread就不能继承别的类了</li>
</ul>
</li>
</ul>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">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();
}</pre>
</div>
<ul>
<li>实现Runnable接口:重写run()方法,这是一个任务,要包装成Thread才能start。
<ul>
<li>优点:只需要实现Runnable,业务逻辑和线程机制解耦。可以被多个线程复用。</li>
<li>缺点:也是用完就销毁。</li>
</ul>
</li>
</ul>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">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();
}</pre>
</div>
<ul>
<li>实现Callable接口:重写call()方法,通过FutureTask获取任务执行的返回值。</li>
</ul>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">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 ();
}
}
}
</pre>
</div>
<p> </p>
<h1>4、为什么调用start()方法时会执行run()方法?那为什么不直接调用run()</h1>
<ul>
<li> JVM执行start方法,会先创建一个线程,由创建出来的线程去执行run方法,才能起到多线程的效果。如果直接调用run方法,那么run还是运行在主线程中,相当于顺序执行。</li>
</ul>
<h1>5、线程有哪些常用的调度方法</h1>
<p><img src="https://img2024.cnblogs.com/blog/2297173/202509/2297173-20250914105613351-979023527.png"></p>
<p> </p>
<h1>6、线程有几种状态</h1>
<table style="height: 226px; width: 563px" border="0">
<tbody>
<tr>
<td>状态</td>
<td>说明</td>
</tr>
<tr>
<td>NEW</td>
<td>初始状态:线程被创建,还没调用start方法</td>
</tr>
<tr>
<td>RUNNABLE</td>
<td>运行状态:就绪 + 运行</td>
</tr>
<tr>
<td>BLOCKED</td>
<td>阻塞状态:阻塞于锁</td>
</tr>
<tr>
<td>WAITING</td>
<td>等待状态:等待其他线程做出一定动作(通知或中断)</td>
</tr>
<tr>
<td>TIME_WATING</td>
<td>超时等待:指定时间自行返回</td>
</tr>
<tr>
<td>TERMINATED</td>
<td>终止状态:已经执行完毕</td>
</tr>
</tbody>
</table>
<p><img src="https://img2024.cnblogs.com/blog/2297173/202509/2297173-20250914105906377-2057227143.png"></p>
<ul>
<li>初始状态:用new Thread()创建线程对象,还没调用start()时,此时只是被实例化。</li>
<li>就绪:线程已经具备运行条形,只差操作系统调度。</li>
<li>运行:当CPU把时间片分配给线程时,进入Running,执行run()方法里的代码。可能会发生:
<ul>
<li>执行完毕-->终止</li>
<li>调用yield()-->放弃CPU,回到就绪</li>
<li>被系统抢占调度-->回到就绪</li>
<li>进入阻塞或等待-->转到响应状态</li>
</ul>
</li>
<li>阻塞:当线程尝试获取锁,但锁被其他线程占用,会进入阻塞状态。一旦获取锁,就进入running状态</li>
<li>等待:线程调用wait()、join()等时,会进入等待状态。必须由notify()或notifyAll()才能唤醒,回到runnable状态</li>
<li>超时等待:线程调用带超时参数的方法进入超时等待状态。到了超时时间或被notiry()唤醒后回到runnable</li>
<li>终止:执行完run()或抛出未捕获异常结束时,进入终止状态。</li>
</ul>
<h1>7、什么是线程上下文切换</h1>
<p> CPU资源分配采用时间片轮转,也就是给每个线程分配一个时间片,线程在时间片内占用CPU执行任务。当线程使用完时间片后,就会处于就绪状态并让出CPU让其他线程用,这就是线程上下文切换。</p>
<h1>8、守护线程了解吗</h1>
<p> Java中的线程分为两类:daemon线程(守护线程)和user线程(用户线程)</p>
<p> 在JVM启动时会调用main函数,其所在线程就是一个用户线程。JVM内部还启动了很多守护线程,如垃圾回收线程。</p>
<p> 守护线程和用户线程的区别是,当最后一个非守护线程结束时,JVM会正常退出(此时不管当前是否存在守护线程)。</p>
<h1>9、线程间有哪些通信方式</h1>
<ul>
<li> volatile和synchronized关键字:
<ul>
<li>volatile:保证变量在多线程间的可见性,不保存原子性。</li>
<li>synchronized:保证同一时刻只有一个线程执行被保护的代码块,提供原子性和可见性。常用于操作<strong>共享变量</strong>、<strong>临界区</strong>的互斥访问。</li>
</ul>
</li>
<li>等待/通知机制:线程调用wait()进入等待队列,释放锁;另一个线程在条件满足后调用notify或notifyAll唤醒。</li>
<li>管道输入/输出流:用于线程间直接传递数据。一个线程写入 <code data-start="956" data-end="975">PipedOutputStream</code>,另一个线程从 <code data-start="983" data-end="1001">PipedInputStream</code> 读取。适合流式传输。</li>
<li>使用Thread.join():一个线程等待另一个线程执行完毕后再继续。常用于 <strong data-start="1526" data-end="1540">主线程等待子线程结果</strong> 的场景。</li>
<li>使用ThreadLocal:为每个线程单独提供一个变量副本。线程间不共享数据,避免竞争和加锁。</li>
</ul>
<p> </p><br><br>
来源:https://www.cnblogs.com/xiaoqian01/p/19083406
頁:
[1]