进程与线程
进程:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位。
线程:线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
进程与线程的区别
- 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
- 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;
Java线程的创建与启动
三种方式:
- 通过继承Thread类(java.lang.Thread)来创建并启动多线程的方式
- 通过实现Runnable接口(java.lang.Runnable)来创建并启动线程的方式
- 通过实现Callable接口来创建并启动线程的方式
继承Thread类创建线程步骤
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务即run()方法称为线程执行体
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
代码演示
public class ThreadCreate {
public static void main(String[] args) {
MyThread t1 = new MyThread("张三");
Thread t2 = new MyThread("李四");
// System.out.println(t1.getName());
// System.out.println(t2.getName());
t1.start();
t2.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":helloThread");
}
public MyThread(String name) {
super(name);
}
public MyThread() {
}
}
实现Runnable接口中run()方法来创建并启动线程
public class MyRunnableTest {
public static void main(String[] args) {
// Thread t1 = new Thread(new MyRunnable(), "张三");
// Thread t2 = new Thread(new MyRunnable(), "李四");
// System.out.println(Thread.currentThread().getName());
// t1.start();
// t2.start();
//java8 Lambda表达式
new Thread(() -> System.out.println(Thread.currentThread().getName()+":HelloThread"),"王五").start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "HelloThread");
}
}
实现Callable接口中call()方法
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread2 thread = new MyThread2();
FutureTask<Integer> futureTask = new FutureTask(thread);
new Thread(futureTask,"A").start();
System.out.println(futureTask.get());//这个get方法可能会产生阻塞!把他放在最后
}
}
class MyThread2 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
//有缓存
//结果可能需要等待,会阻塞
三种方式区别
- 继承Thread类的,代码编写简单,并且可以使用currentThread()方法来访问当前线程,但是继承该类之后不能继承其它的类。
- 实现Runable接口,有利于扩展性,实现了该接口之后,还可以实现其它接口或者继承其它的类。
- 实现Callable接口,编写相对于其它两种比较复杂,但是可以有返回值并且能抛出异常。
线程调度
线程的调度和优先级问题
A:线程的调度
a:分时调度
b:抢占式调度 (Java采用的是该调度方式)
B:获取和设置线程优先级
public final int getPriority():返回线程对象的优先级
public final void setPriority(int newPriority):更改线程的优先级。
public class PriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
tp1.setName("东方不败");
tp2.setName("岳不群");
tp3.setName("林平之");
// 获取默认优先级
// System.out.println(tp1.getPriority());
// System.out.println(tp2.getPriority());
// System.out.println(tp3.getPriority());
// 设置线程优先级
// tp1.setPriority(100000);
//设置正确的线程优先级
tp1.setPriority(10);
tp2.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
}
class ThreadPriority extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
-
线程默认优先级是5。
-
线程优先级的范围是:1-10。
-
线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
- IllegalArgumentException:非法参数异常。
- 抛出的异常表明向方法传递了一个不合法或不正确的参数。
线程的控制(常见方法)
A:休眠线程
public class SleepTest {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for(int x =0; x <= 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" +",日期" + new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
for(int x =0; x <= 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" +",日期" + new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.setName("张三");
t2.setName("李四");
t1.start();
t2.start();
}
}
****B:加入线程****
public final void join():等待该线程终止。
public class ThreadDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}, "李源");
Thread t2 = new Thread(() -> {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}, "李世民");
Thread t3 = new Thread(() -> {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}, "李元霸");
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
}
}
**C:礼让线程**
/*
1. public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
2. 让多个线程的执行更和谐,但是不能靠它保证一人一次。
*/
public class ThreadYield {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
Thread.yield();
}
},"男");
Thread t2 = new Thread(() -> {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
Thread.yield();
}
},"女");
t1.start();
t2.start();
}
}
**D:后台线程**
public class ThreadDaemonDemo {
public static void main(String[] args) {
Thread td1 = new Thread(() -> {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}, "坦克1");
Thread td2 = new Thread(() -> {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}, "坦克2");
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("鸟");
for(int x = 0; x < 10; x++) {
System.out.println(Thread.currentThread().getName() + ":" +x);
}
}
}
E:终止线程
/*
3. public final void stop():让线程停止,过时了,但是还可以使用。
4. public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
*/
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 你超过三秒不醒过来,就终止该线程
try {
Thread.sleep(3000);
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程的生命周期
A:新建
B:就绪
C:运行
D:阻塞
E:死亡
线程池创建线程
合理利用线程池能够带来三个好处。
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
使用Executors工厂类产生线程池
三大方法
方法名 | 功能 |
---|---|
newFixedThreadPool(int nThreads) | 创建固定大小的线程池 |
newSingleThreadExecutor() | 创建只有一个线程的线程池 |
newCachedThreadPool() | 创建一个不限线程数上限的线程池,任何提交的任务都将立即执行 |
Java中创建线程池很简单,只需要调用Executors中相应的便捷方法即可,比如Executors.newFixedThreadPool(int nThreads),但是便捷不仅隐藏了复杂性,也为我们埋下了潜在的隐患(OOM,线程耗尽)。
ThreadPoolExecutor构造方法
提交任务的方式:
提交方式 | 是否关心返回结果 |
---|---|
Future submit(Callable task) | 是 |
void execute(Runnable command) | 否 |
7大参数
Executors中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor的构造方法(定时任务使用的是ScheduledThreadPoolExecutor),该类构造方法参数列表如下:
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大核心线程池大小
long keepAliveTime, // 超时了没有人调用就会释放
TimeUnit unit, // 超时单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handle // 拒绝策略)
四种拒绝策略
- AbortPolicy 抛出RejectedExecutionException
- DiscardPolicy 什么也不做,直接忽略
- DiscardOldestPolicy 丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置
- CallerRunsPolicy 直接由提交任务者执行这个任务
流程图