目录
进程和线程:
进程:是资源分配的基本单位;每个进程都有自己独立的代码和数据空间(进程上下文);一个进程包含1-n个线程;进程之间的切换会有较大的开销,进程之间的数据交互和信息交互也非常的耗时。
进程是属于OS层级的概念,和语言没有关系,我们常说的java的多线程指的利用java去操作多线程
线程:线程是CPU调度的最小单位;同一类线程共享代码和数据空间,OS只会给线程分配CPU,不会给线程分配内存,只能使用所属进程的资源;每个线程有独立运行栈和程序计数器(PC);线程切换开销较小,线程之间的通信和数据交互都非常快。
我们在windows上跑的每一个程序,就是一个进程,一个后台的程序也是一个进程。
举个例子:在windows上打开QQ和Word,就分别是两个不同的进程,
Word :winWord.exe
QQ : QQ.exe
如果此时,在后台再开一个进程,如IDEA,那么此时电脑上就有三个进程(除去Windows自开的进程)
在QQ这个进程里面,我和ABC,三个人聊天,那么这三个人分别开一个进程,我和A发送文字,给B发送语音,给C发送文件,这是三个线程。
在说一个实际的场景,在使用台机器写word文档的时候,如果此时断电的话,当来电的时候重新打开word,可以恢复数据,为什么?因为在word中有一个容灾备份的线程backup Thread。如果继续写word的时候写错了,会发现波浪线提示,为什么?因为有一个语法检查的线程 checkStyle thread 。
多进程是指操作系统能同时运行多个任务(程序)。 多线程是指在同一程序中有多个程序流在执行。
关于单核和多核和多线程的关系:
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务 双核,跑四个线程,是真实的多线程
为什么对象能够作为锁?角度:线程的五个状态-->Object类能够控制线程的状态(wait方法, notify方法,和notify方法)
多次调用start方法,会产生什么异常?IllegalThreadStateException
由此我们可以看出操作系统的设计考虑到了:
①:允许多个任务同时运行
②:允许同一个任务分不同的部分运行
③:提供了协调的机制,一方面防止进程之间和线程之间产生冲突,一方面允许进程之间和线程之间进行资源的共享
Thread和Runnable实现多线程
多线程能够满足程序员编写非常有效的程序来提高CPU的利用率。
首先,先来体验一下多线程对于CPU资源的抢占:
package com.isea.java;
public class MyThread extends Thread {
@Override
public void run(){
for (int i = 0; i < 10 ; i ++){
System.out.println("Mythread:" + i);
}
}
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
for (int i = 0 ; i < 10 ; i ++){
System.out.println("Main:" + i);
}
}
}
运行的结果如下;
Main:0
Main:1
Main:2
Mythread:0
Main:3
Mythread:1
Mythread:2
Main:4
Mythread:3
Main:5
Mythread:4
Mythread:5
Mythread:6
Mythread:7
Mythread:8
Mythread:9
Main:6
Main:7
Main:8
Main:9
对于以上问题的解析:
主线程 main在main()被调用的时候被创建,在调用myThread对象的start()方法的时候,该线程也会变成可运行状态(就绪状态),等到获取CPU的时间片的时候,才会开始运行(由操作系统决定什么时候获得CPU的时间片)。以上打印顺序表示的是main线程先进行,打印了0,1,2。然后Mythread获得了CPU的执行权打印了0,以此类推。
以上的代码也可以使用Runnable接口来实现:
package com.isea.java;
public class MyRunnable implements Runnable {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
for (int i = 1; i <= 10; i++) {
System.out.println("Main:" + i);
}
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("MyRunnable:" + i);
}
}
}
Runnable接口:
1,run()方法是多线程程序的一个准则,所有的多线程代码都必须放到run()方法中
2,所有的多线程的代码都需要Thread类的start()方法来运行
3,使用runnable接口的线程类,要调用Thread(Runnable target)的构造方法,然后调用Thread类的start方法来启动线程。
4,java中所有的线程都是同时启动的,至于什么时候执行才取决于谁先获得CPU的时间片。
Thread也是实现了Runnable接口的。
public class Thread implements Runnable
/* What will be run. */
private Runnable target;
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(){
......
this.target = target;
.....
}
使用Runnable接口的好处:
1,突破java 中单线程的限制
2,适合多个相同的程序代码的线程去处理同一个资源
3,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4,线程池只能放入事先runnable或着是callable类的线程,不能直接放入继承Thread的类
线程的生命周期
图片:
①:新建(New),new一个线程的对象的时候。
②:就绪状态(Runnable),线程创建了对象之后,其他线程调用该对象的start方法,变的可运行,等待CPU的时间片,一个线程的start方法只能用一次。.start()进入就绪(可运行状态)多次调用start方法,会报IllegalThreadStateException。
③:运行的状态(Running)获得CPU的时间片,正在被CPU调度,执行程序,(此时会释放锁)
④:同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
其他原因,运行的线程执行sleep方法(sleep不会释放所持有的锁的)或者是join方法,或者发出了I/O请求,JVM会把该线程设置为阻塞状态,当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态
5,死亡状态(Dead),线程执行完了或者因异常退出了run()方法,该线程结束生命周期 绪状态才能重新回到运行状态
面:sleep和wait的区别?
线程的优先级:
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。 每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。 线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
1)、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
public static native void sleep(long millis) throws InterruptedException;
2)、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
public final native void wait(long timeout) throws InterruptedException;
3)、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
public static native void yield();
4)、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
public final synchronized void join(long millis)throws InterruptedException
5)、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
public final native void notify();
演示join:
package com.isea.java;
public class TestJoin {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run(){
for (int i = 1; i <= 10 ; i ++){
System.out.println("Thread: " + i);
}
}
};
thread.start();
for (int i = 1; i <= 10; i ++){
try {
thread.join();//在当前线程中(main),调用thread线程表示的是main线程将会等待thread线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main : " + i);
}
}
}
该程序的结果:
Thread: 1
Thread: 2
Thread: 3
Thread: 4
Thread: 5
Thread: 6
Thread: 7
Thread: 8
Thread: 9
Thread: 10
Main : 1
Main : 2
Main : 3
Main : 4
Main : 5
Main : 6
Main : 7
Main : 8
Main : 9
Main : 10
join的使用场景:
主线程中生成并行子线程,如果子线程进行了非常耗时的操作的话,主线程往往比子线程先执行完,但是如果主线程需要子线程处理之后的数据,那么这个时候需要使用到join方法,等待子线程执行完毕之后,才能执行主线程。