线程和进程

本文围绕Java多线程展开,包含多线程面试50题链接。介绍了多线程实现方式,对比了Thread类和Runnable接口,还提及Callable和Future接口。阐述了wait、notify等方法位置原因,分析并发编程的原子性、有序性、可见性问题,以及Java内存模型和happens - before原则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多线程面试50题
http://www.importnew.com/12773.html
注意:多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动——对于已经死亡的线程,无法再使用start方法令其进入就绪。

多线程的实现方式:
继承thread类或者实现Runable接口。
由于java单继承和可实现多接口,推荐用Runable接口。
但是两者都有缺陷——都无法获取计算结果。虽然可以通过共享变量或者使用线程通信的方式来获取执行结果,但是比较麻烦。
1 .thread 类中有start() 和 run() 方法:
start() :使线程进入就绪状态,等待cpu时间片,然后执行run() 方法,即线程体 包含了要执行的线程的内容,run() 结束,线程终止。
run() :可以被重复调用。单独调用run()的话,会在当前线程中执行run(),不会启动新的线程。
2 .java中Callable和Runable

callable接口代表可以获取执行结果,而furture接口表示异步任务,可以获取未完成任务给出的未来结果。所以 callable用于产生结果,Future用于获取结果。

而Runable接口只有一个返回值为void的 run() 方法。无法获取执行结果。
Callable接口是一个泛型接口,也是只有一个方法 call() ,但是其返回值为 传递进来的 V类型,
使用:
一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:

Funture对象:装载计算结果。其源码:
public interface Future {
boolean cancel(boolean mayInterruptIfRunning); //用来取消任务,参数表示是否可以取消正在执行的任务。
boolean isCancelled(); //任务正常执行结束之前是否被取消
boolean isDone(); //任务是否已经完成
V get() throws InterruptedException, ExecutionException; //方法用来获取执行结果,这个方法会产生阻塞,会一直等到 //任务执行完毕才返回
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException; //用来获取执行结果,如果在指定时间内,还没 //获取到结果,就直接返回null
}
五个方法。功能:判断任务是否完成;取消或者中断任务;获取任务执行结果。

FutureTask 实现了RunableFurture 接口,其源码:
public interface RunnableFuture extends Runnable, Future {
void run();
}

public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
sync = new Sync(callable);
}
public FutureTask(Runnable runnable, V result) {
sync = new Sync(Executors.callable(runnable, result));
}
如上提供了两个构造函数,一个以Callable为参数,另外一个以Runnable为参数。这些类之间的关联对于任务建模的办法非常灵活,允许你基于FutureTask的Runnable特性(因为它实现了Runnable接口),把任务写成Callable,然后封装进一个由执行者调度并在必要时可以取消的FutureTask。

FutureTask可以由执行者调度,这一点很关键。它对外提供的方法基本上就是Future和Runnable接口的组合:get()、cancel、isDone()、isCancelled()和run(),而run()方法通常都是由执行者调用,我们基本上不需要直接调用它。

wait, notify 和 notifyAll这些方法为何不在thread类里面?
java提供的锁是对象级的,而不是线程级的,每个对象都有锁,通过线程获得。

并发编程常见三个问题:原子性,有序性,可见性
Java内存模型(Java Memory Model,JMM)定义了程序中变量的访问规则就是执行次序

java本身定义一些规则:
1 .原子性:对于基础数据类型的读写存取是原子性操作,i=10,是原子性操作,而i=i+1 不属于原子性操作,因为要先对i进行读取操作。所以,
Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
2 .可见性:对于可见性,Java提供了volatile关键字来保证可见性。
 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
 另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
 3 .有序性:在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
  在Java里面,可以通过volatile关键字来保证一定的“有序性”(具体原理在下一节讲述)。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
  另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。

happens-before原则(先行发生原则):
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
  这8条原则摘自《深入理解Java虚拟机》
通常来说,使用volatile必须具备以下2个条件:

  1)对变量的写操作不依赖于当前值

  2)该变量没有包含在具有其他变量的不变式中

  实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

  事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值