目录
多线程的基本概念
线程指进程中的一个执行场景,也就是执行流程。每一个进程都是一个应用程序,都有独立的内存空间;同一个进程中的线程共享进程中的内存和资源(共享的内存是堆内存和方法区内存,栈内存不共享,每一个线程都有自己的栈内存),一个进程中可以启动多个线程。
进程:
一个进程对应一个应用程序。例如:在Windows操作系统下启动一个Word就表示启动了一个进程。在Java的开发环境下启动JVM,就表示启动了一个进程。
多进程的作用:
单进程计算机一次只能做一件事;对于单核计算机来讲,CPU只能在某个时间点上做一件事。双核或多核计算机在某个时间点上可以同时处理多件事情。多进程的作用不是提高执行速度,而是提高CPU的使用率。进程与进程之间的内存是独立的。
多线程的作用:
多线程的作用不是为了提高执行速度,而是为了提高应用程序的使用率。
线程的创建和启动
Java中虚拟机的主线程入口是main方法,用户可以自己创建线程,创建方式有两种:继承Thread类、实现Runnable接口(推荐)。
继承Thread类:
Thread类中创建线程最重要的两个方法为:
public void run()
public void start()
采用Thread类创建线程,用户只需要继承Thread类,覆盖Thread类中的run方法,父类Thread中的run方法没有抛出异常,那么子类也不能抛出异常,最后采用start启动线程即可。
public class ThreadTest01{
public static void main(String[] args){
//创建线程
Thread t = new Processor();
//启动线程
t.start();
t.run();//普通方法调用
for(int i = 0; i < 50; i++){
System.out.println("main---->" + i);
}
}
}
//定义一个线程
class Processor extends Thread{
//重写run方法
public void run(){
for(int i = 0;i < 100;i++){
System.out.println("run————>" + i);
}
}
}
实现Runnable接口
实现Runnable接口,并实现run方法
public class ThreadTest01{
public static void main(String[] args){
//创建线程
Thread t = new Thread(new Processor());
//启动线程
t.start();
for(int i = 0; i < 50; i++){
System.out.println("main---->" + i);
}
}
}
//推荐这种方法,因为一个类实现接口之外,保留了类的继承
class Processor implements Runnable{
//重写run方法
public void run(){
for(int i = 0;i < 100;i++){
System.out.println("run————>" + i);
}
}
}
线程的生命周期
线程的生命周期存在五个状态:新建、就绪、运行、阻塞、消亡
线程的调度与控制
通常我们的计算器只有一个CPU,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。在单CPU的机器上线程并不是并行运行的,只有在多个CPU上,线程才可以并行运行。Java虚拟机要负责线程的调度,取得虚拟机的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,Java中使用抢占式调度模型。
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的时间片相对多一些。
线程优先级分为三种:MAX_PRIORITY(最高级10)、MIN_PRIORITY(最低级1)、NORM_PRIORITY(标准)默认5。
Thread.sleep(毫秒);:sleep是一个静态方法,作用是阻塞当前线程。当一个线程遇到sleep的时候,就会睡眠,进入到阻塞状态,放弃CPU,腾出CPU时间片,给其它线程用,当睡眠时间到了,线程就会进入可运行状态,得到CPU时间片继续运行。如果线程在睡眠状态被中断了,就会抛出interruptedException异常。
interrupt();:利用异常的处理机制打断线程的休眠,interruptedException异常。
若要不利用异常的处理机制,而是要正常的终止线程的休眠,使用:p.run = false;(Processor p = new Processor();)
Thread.yield();:静态方法,与sleep类似, 只是不能有用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会,让位时间不固定。
Thread.join();:成员方法,当前线程可以调用另一个线程的join方法,调用后当前线程被阻塞不再执行,直到被调用的线程执行完毕,当前线程才会被执行。该方法称为线程的合并。
线程的同步
线程同步是为了数据的安全,而暂时放弃了效率。在满足以下条件时要使用线程同步:
1、必须是多线程环境;2、多线程环境共享同一个数据;3、共享的数据涉及到修改操作;
使用线程同步,把需要同步的代码,放到同步语句块synchronized(this){}中或在对应的方法前加关键字synchronized。当一个线程t1执行到此处,遇到了synchronized关键字,就会去找this的对象锁,如果找到this对象锁,则t1进入同步语句块中执行,当同步语句块中的代码执行结束之后,该t1线程会归还this的对象锁。在t1线程执行同步语句块的过程当中,如果t2线程也执行此处遇到synchronized关键字,也会去找this的对象锁,但是该对象锁被t1线程持有,所以t2线程只能在这里等待this对象的归还。所以保证了语句块中的代码永远只有一个线程执行。
synchronized(this){
}
类锁:synchronized添加到静态方法上,线程执行此方法的时候会找类锁,与对象锁无关。类锁只有一把。
死锁:
public class DeadLock{
public static void main(String[] args){
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Thread(new T1(o1,o2));
Thread t2 = new Thread(new T2(o1,o2));
t1.start();
t2.start();
}
}
class T1 implements Runnable{
Object o1;
Object o2;
T1(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized(o1){
try{
Thread.sleep(1000);
}catch(InterruptedException e){}
synchronized(o2){
}
}
}
}
class T2 implements Runnable{
Object o1;
Object o2;
T2(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized(o2){
try{
Thread.sleep(1000);
}catch(InterruptedException e){}
synchronized(o1){
}
}
}
}
守护线程
线程可以分为守护线程和用户线程两类。所有的用户线程生命周期结束,守护线程的生命周期才会结束。只要有一个用户线程存在,守护线程就不会结束。例如:Java中的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束。
定时器的使用
每隔一个固定的时间,执行一段代码。
import java.text.*;
import java.util.*;
public class TimerTest{
public static void main(String[] args) throws Exception{
//创建定时器
Timer timer = new Timer();
//指定定时任务2021-03-30 21:22:00 000开始,每隔10秒钟打印输出当前时间
timer.schedule(new LogTimerTask(),
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").parse("2021-03-30 21:22:00 000"),
10*1000);
}
}
//指定任务
class LogTimerTask extends TimerTask{
public void run(){
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").format(new Date()));
}
}