Java--多线程
进程和线程的区别
- 进程时资源分配的最小单位,线程时CPU调度的最小单位
- 线程不能看作独立应用,而进程可看作独立应用
- 进程还有独立的地址空间,多进程的程序比多心啊吃程序健壮
- 进程的切换比线程的切换开销大
Java进程和线程的关系
- Java对操作系统提供的功能进行封装,包括进程和线程
- 运行一个程序会产生一个进程,进程包含至少一个线程
- 每个进程对应一个JVM实例,多线程共享JVM里的堆
- Java采用单线程编程模式,程序会自动创建主线程
- 主线程可以创建子线程,原则上要后于子线程完成执行
线程run()和start()的区别
start()
调用start()方法会创建一个新的子线程并启动,处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行相应线程的run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。start()不能被重复调用
run()
方法只是Thread的一个普通方法,可以被重复调用。如果直接调用run方法,并不会启动新线程!程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的
代码实例证明上面的结论
private void runCurrentThread(){
System.out.println("currentThread=="+Thread.currentThread().getName());
System.out.println("over");
}
@Test
public void diffRunAndStart(){
Thread thread = new Thread(){
@Override
public void run() {
super.run();
runCurrentThread();
}
};
System.out.println("current main Thread=="+Thread.currentThread().getName());
thread.start();
}
输出结果:
current main Thread==main
currentThread==Thread-0
over
Process finished with exit code 0
将thread.start()换为thread.run(),输出结果:
current main Thread==main
currentThread==main
over
Process finished with exit code 0
以上代码证明我们的结论,下面通过源码来查看它的实现:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
最终调用了native方法start0(),查看jdk源码:
![](https://img.haomeiwen.com/i5733856/e14d4b8b167e5dbd.png)
start0调用了JVM_StartThread,在去看看jvm文件:
![](https://img.haomeiwen.com/i5733856/3819351c3467828d.png)
代码很多,但最终看到创建了一个新的线程,也证实了我们的结论。start()新创建线程并启动
Thead与Runnable的关系
- Thread是实现了Runable接口的类,使得run支持多线程
- 因类的单一继承原则,推荐多使用Runnable接口
网上有一个结论,用Runnable就可以实现资源共享,而 Thread 不可以。用来论证的代码大概是这样
- Thread:
@Test
public void thread(){
Thread thread1 = new MyThread("thread1");
Thread thread2 = new MyThread("thread2");
thread1.start();
thread2.start();
}
class MyThread extends Thread{
public String name;
public int tickit = 10;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
super.run();
for(int i=0;i<200;i++){
if (0<tickit){
System.out.println(name+" 票号=="+tickit--);
}
}
}
}
输出:
thread2 票号==10
thread2 票号==9
thread2 票号==8
thread2 票号==7
thread2 票号==6
thread2 票号==5
thread2 票号==4
thread1 票号==10
thread2 票号==3
thread2 票号==2
thread2 票号==1
thread1 票号==9
thread1 票号==8
thread1 票号==7
thread1 票号==6
thread1 票号==5
thread1 票号==4
thread1 票号==3
thread1 票号==2
thread1 票号==1
Process finished with exit code 0
- Runnable:
@Test
public void runnable(){
Runnable myRunable = new MyRunnable();
Thread thread1 = new Thread(myRunable,"thread1");
Thread thread2 = new Thread(myRunable,"thread2");
thread1.start();
thread2.start();
}
class MyRunnable implements Runnable{
public int tickit = 10;
@Override
public void run() {
for(int i=0;i<200;i++){
if (tickit>0){
System.out.println(Thread.currentThread().getName()+" 票号=="+tickit--);
}
}
}
}
输出:
thread1 票号==10
thread2 票号==9
thread2 票号==7
thread2 票号==6
thread2 票号==5
thread2 票号==4
thread2 票号==3
thread2 票号==2
thread2 票号==1
thread1 票号==8
Process finished with exit code 0
输出貌似验证了这个结论,其实仔细看看代码就知道,这只是两种写法的区别,根本就不是 implements Runnable 与 extends Thread 的区别。
修改代码,在此证明错误:
@Test
public void thread(){
Thread thread1 = new MyThread("thread1");
Thread thread2 = new MyThread("thread2");
thread1.start();
thread2.start();
}
static class MyThread extends Thread{
public String name;
public static int tickit = 10;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
super.run();
for(int i=0;i<200;i++){
if (0<tickit){
System.out.println(name+" 票号=="+tickit--);
}
}
}
}
输出:
thread2 票号==10
thread2 票号==8
thread2 票号==7
thread1 票号==9
thread1 票号==5
thread1 票号==4
thread1 票号==3
thread2 票号==6
thread2 票号==1
thread1 票号==2
Process finished with exit code 0
由此Runnable相比Thread的优势:
-
适合
多个相同的程序代码的线程去处理同一个资源。 - 可以避免java中的单继承的限制。
- 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
如何给run()方法传参
- 构造参数传参
- 成员变量传参
- 回调函数传参
如何实现处理线程的返回值
1.主线程等待法
class WaiRunnable implements Runnable{
public String value ;
@Override
public void run() {
try {
Thread.sleep(3000);
value = "it's Lillard time";
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Test
public void mainWait() throws InterruptedException {
WaiRunnable waitRunnable = new WaiRunnable();
Thread t = new Thread(waitRunnable);
t.start();
//等待value赋值
while (null==waitRunnable.value){
Thread.sleep(1000);
}
System.out.println("value=="+waitRunnable.value);
}
value==it's Lillard time
Process finished with exit code 0
缺点:
- 需要自己去实现等待的逻辑
- 需要等待多久是不确定的
2.使用Thread类的join()阻塞当前线程以等待子线程处理完毕
只需将上方的while循环改为t.join();
@Test
public void mainWait() throws InterruptedException {
WaiRunnable waitRunnable = new WaiRunnable();
Thread t = new Thread(waitRunnable);
t.start();
t.join();
System.out.println("value=="+waitRunnable.value);
}
value==it's Lillard time
Process finished with exit code 0
优点:
- 实现简单
- 控制精准
缺点:
- 粒度不够细,我们上方
票号
的例子,如果当thread1卖出第5张时需要进行其他操作。通过join阻塞是无法实现的
3.通过Callable接口实现:通过FutureTask或线程池获取
FutureTask:
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("ready");
Thread.sleep(3000);
return "it's Lillard time";
}
}
@Test
public void callable() throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new MyCallable());
new Thread(futureTask).start();
if (!futureTask.isDone()) {
System.out.println("task not finish,please wait");
}
System.out.println("task return:"+futureTask.get());
}
task not finish,please wait
ready
task return:it's Lillard time
Process finished with exit code 0
线程池:
@Test
public void executor(){
ExecutorService executorService = Executors.newCachedThreadPool();
Future future = executorService.submit(new MyCallable());
if (!future.isDone()) {
System.out.println("task not finish,please wait");
}
try {
System.out.println("task return:"+future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
task not finish,please wait
ready
task return:it's Lillard time
Process finished with exit code 0
Thread的join方法
join() method suspends the execution of the calling thread until the object called finishes its execution.
t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待子线程完成再结束main()主线程
System.out.println("MainThread run start.");
//启动一个子线程
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadA run start.");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("threadA run finished.");
}
});
threadA.start();
System.out.println("MainThread join before");
try {
threadA.join(); //调用join()
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MainThread run finished.");
MainThread run start.
threadA run start.
MainThread join before
threadA run finished.
MainThread run finished.
sleep和wait的区别(两者都能使线程进行等待状态)
基本的差别
- sleep是Thread类的方法,wait是Object类中的方法
- sleep()方法可以在任何地方使用
- wait()方法只能在synchronized方法或synchronized块中使用
最重要的本质区别
- Thread.sleep只会让出CPU,不回导致锁行为的改变
- Object.wait不仅让出CPU,还会释放已经占有的通过资源锁
代码实例:
@Test
public void diffSleepAndWait() {
final Object lock = new Object();
new Thread(new Runnable() {
@Override
public void run() {
1 System.out.println("Thread A is waiting to get lock");
synchronized (lock) {
try {
2 System.out.println("Thread A get lock");
Thread.sleep(20);
3 System.out.println("Thread A do wati");
lock.wait(1000);
4 System.out.println("Thread A is done");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
5 System.out.println("Thread B is waiting to get lock");
synchronized (lock) {
try {
6 System.out.println("Thread B get lock");
7 System.out.println("Thread B is sleeping 10 ms");
Thread.sleep(10);
8 System.out.println("Thread B is done");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
Thread A is waiting to get lock
Thread A get lock
Thread B is waiting to get lock
Thread A do wati
Thread B get lock
Thread B is sleeping 10 ms
Thread B is done
Thread A is done
Process finished with exit code 0
开启线程A并上锁(输出1.2),休眠20毫秒这样只休眠了10毫秒的主线程就会启动线程B(输出5,但由于现场A此时占有对象锁,所以不回往下执行),线程A的20秒休眠结束(输出3)调用wait休眠1秒,按照上方关于wait的理论此时释放线程A释放对象锁
,线程B继续执行--输出67(Thread.sleep(10))只休眠10毫秒远比线程A的一秒短--输出8,最好线程A休眠时间到--输出4
将Thread A 标记3的wait改为sleep,然后运行同样也可以证明sleep只会让出cpu不回释放锁的结论
线程的状态
- 新建(New):创建后未启动的线程状态
- 运行(Runnable):包含Running和Ready
- 无限期等待(Waiting):不回被分配CPU执行时间,需要显示被唤醒
1.没有设置Timeout参数的Object.wait()
2.没有设置Timeout参数的Thread.join()
3.LockSupport.park() - 限期等待(Time Waiting):在一定时间后会由系统自动唤醒
1.设置了Timeout参数的Object.wait()
2.设置了Timeout参数的Thread.join()
3.Thread.sleep(long time) 方法
4.LockSupport.parkNanos(time)
5.LockSupport.parkUntil(time) - 阻塞(Blocked):等待获取排它锁
- 结束(Terminated):已终止线程的状态,线程已经结速执行
notify和notifyall的区别(都能唤醒等待状态的线程)
- notifyall会让所有处于
等待池
中的线程全部进入锁池去竞争获取锁的机会 - notify只会随机选取一个处于
等待池
中的线程进入锁池去竞争获取锁的机会
两个概念
-
锁池EntryList:
线程A已经拥有了某个对象(不是类)的锁,而线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象的synchronized(或者块)之前必须先获取该对象锁的拥有权,而恰巧该对象的锁目前正被线程A占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池 -
等待池WaitSet
假设线程A调用了某个对象的waiit()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不回去竞争该对象的锁
yield
A hint to the scheduler that the current thread is willing to yield
its current use of a processor. The scheduler is free to ignore this
hint
- 当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出
cpu(不是锁)
使用的暗示,但是线程调度器可能会忽略这个暗示
Runnable runnable = new Runnable() {
@Override
public void run() {
for(int i= 0;i<6;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
if (i==2){
Thread.yield();
}
}
}
};
Thread threadA = new Thread(runnable,"A");
Thread threadB = new Thread(runnable,"B");
threadA.start();
threadB.start();
A 0
A 1
A 2
B 0
B 1
B 2
A 3
A 4
A 5
B 3
B 4
B 5
Process finished with exit code 0
输出结果证实了结论,但由于线程调度器可能会忽略这个提示,所以上面这个输出不是100%复现
如何中断线程
已经被抛弃的方法
- 通过调用stop()方法停止线程
- 通过suspend和resume方法
目前使用的方法
调用interrupt(),通知线程应该中断了:
- 如果线程处于被阻塞状态(sleep、wait、join),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常
- 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,被设置中断标志的线程将继续正常运行,不受影响
coding:
Runnable interruptTask = new Runnable() {
@Override
public void run() {
int i = 0;
try {
//在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(100); // 休眠100ms
i++;
System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") loop " + i);
}
} catch (InterruptedException e) {
//在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)
System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") catch InterruptedException.");
}
}
};
Thread t1 = new Thread(interruptTask, "t1");
System.out.println(t1.getName() +" ("+t1.getState()+") is new.");
t1.start(); // 启动“线程t1”
System.out.println(t1.getName() +" ("+t1.getState()+") is started.");
// 主线程休眠300ms,然后主线程给t1发“中断”指令。
Thread.sleep(300);
t1.interrupt();
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
// 主线程休眠300ms,然后查看t1的状态。
Thread.sleep(300);
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) catch InterruptedException.
t1 (TERMINATED) is interrupted now.
Process finished with exit code 0
由于intterrupt不会真的停止线程,所以需要检查中断标志来进行中断处理