进程
一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程(至少有一个
),比如window系统中运行的WeChat.exe就是一个进程。
进程是程序的一次执行过程
,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。
线程
线程是CPU调度和分派的基本单位
,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
比如我们开了一个进程Word,写字,自动保存就是线程负责的
JAVA天生就是多线程程序,默认至少有两个线程main
gc
JAVA能不能开启线程?
不能,上源码
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
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();
Java开启线程是调用的本地方法,而本地方法的底层是C++,JAVA无法直接操作硬件。
进程和线程的区别
根本区别
:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
开销方面
:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系
:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
内存分配
:系统为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。
影响关系
:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
执行过程
:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
并发与并行
并发:多线程操作同一个资源
- CPU一核,模拟出来多条线程,天下武功,唯快不破,快速交替执行
并行:多个线程一起执行
- CPU多核,多个线程可以同时执行
并发编程的本质:充分利用CPU的资源
线程的状态
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待,死死地等
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
线程创建的方法
- 继承 Thread
- 实现 Runable
- 实现 Callable
1、继承java.lang.Thread, 重写run()方法
package com.yizhan.demo;
public class Demo1 {
public static void main(String[] args) {
new ExThread().start();
}
}
class ExThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
2、实现java.lang.Runnable接口,重写run()方法,然后使用Thread类来包装
package com.yizhan.demo;
public class Demo2 {
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName());
}).start();
}
}
两种方式都是围绕着Thread和Runnable,继承Thread类把run()写到类中,实现Runnable接口是把run()方法写到接口中然后再用Thread类来包装, 两种方式最终都是调用Thread类的start()方法来启动线程的。
两种方式在本质上没有明显的区别,在外观上有很大的区别,第一种方式是继承Thread类,因Java是单继承,如果一个类继承了Thread类,那么就没办法继承其它的类了,在继承上有一点受制,有一点不灵活,第二种方式就是为了解决第一种方式的单继承不灵活的问题,所以平常使用就使用第二种方式
3、 实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread
Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕
package com.yizhan.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<V>()).start();
// new Thread(new FutureTask<V>( Callable )).start();
// new Thread().start(); // 怎么启动Callable
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start(); //结果被缓存,效率高,所以只打印一个call()
Integer o = (Integer) futureTask.get(); //这个get 方法可能会产生阻塞!把他放到最后
// 或者使用异步通信来处理!
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call(){
System.out.println("call"); // 会打印几个call
//耗时的操作
return 1024;
}
}
三种方式比较:
-
Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
-
Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
-
Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
-
当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
-
Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现
Thread常用方法
1. start() 与 run()
start()
package com.yizhan.demo;
public class Demo4 {
public static void main(String[] args) {
new Thread(()->{
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
},"A").start();
new Thread(()->{
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
},"B").start();
}
}
start(): 启动一个线程,线程之间是没有顺序的,是按CPU分配的时间片来回切换的。
run()
package com.yizhan.demo;
public class Demo4 {
public static void main(String[] args) {
new Thread(()->{
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
},"A").run();
new Thread(()->{
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
},"B").run();
}
}
执行结果都是main主线程
run(): 调用线程的run方法,就是普通的方法调用,虽然将代码封装到两个线程体中,可以看到线程中打印的线程名字都是main主线程,run()方法用于封装线程的代码,具体要启动一个线程来运行线程体中的代码(run()方法)还是通过start()方法来实现,调用run()方法就是一种顺序编程不是并发编程。
2. sleep() 与 interrupt()
sleep(long millis): 睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到就会立刻执行(因为睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行)。
- sleep(): 睡眠指定时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到就要醒需要使用interrupt()来随时唤醒
- interrupt(): 唤醒正在睡眠的程序,调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常,当sleep()方法抛出异常就中断了sleep的方法,从而让程序继续运行下去
package com.yizhan.demo;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡个5秒");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t醒了");
}
},"A");
thread.start();
//这里的睡眠保证让上面线程先执行
TimeUnit.SECONDS.sleep(5);
new Thread(()->{
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t您起来吧");
// 无需获取锁就可以调用interrupt
thread.interrupt();
}).start();
}
}
3. wait() 与 notify()
wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中可以通过this或者super来调用this.wait(), super.wait()
- wait(): 导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
- wait(long timeout): 时间到了自动执行,类似于sleep(long millis)
- notify(): 该方法只能在同步方法或同步块内部调用,随机选择一个(注意:只会通知一个)在该对象上调用wait方法的线程,解除其阻塞状态
- notifyAll(): 唤醒所有的wait对象
4. sleep() 与 wait()
1、来自不同的类
wait -> Object
sleep -> Thread
2、关于锁的释放
wait 会释放锁
sleep抱着锁睡觉,不会释放
3、使用范围
wait 必须在同步代码块中
sleep想在哪里睡就在哪里睡
4、是否需要捕获异常
wait 不需要捕获异常
sleep需要捕获异常
5. join()
让当前线程加入父线程,加入后父线程会一直wait,直到子线程执行完毕后父线程才能执行。当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。
将某个线程加入到当前线程中来,一般某个线程和当前线程依赖关系比较强,必须先等待某个线程执行完毕才能执行当前线程。一般在run()方法内使用
package com.yizhan.demo;
public class Demo6 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 1; i <=6 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
},"A");
thread.start();
thread.join();
for (int i = 0; i < 6; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
main线程会一直等待thread执行完才会执行
6. yield()
交出CPU的执行时间,不会释放锁,让线程进入就绪状态,等待重新获取CPU执行时间,yield就像一个好人似的,当CPU轮到它了,它却说我先不急,先给其他线程执行吧, 此方法很少被使用到,