一、多线程的基本概念
1. 概述
线程指进程中的一个执行场景,也就是执行流程,那么进程和线程有什么区别呢?每个进程是一个应用程序,都有独立的内存空间。同一个进程中的线程共享其进程中的内存和资源。(共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的。)
2. 进程与线程的区别
一个进程就是一个应用程序。在操作系统中每启动一个应用程序就会相应的启动一个进程。
线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。
3. 系统引入多线程的作用
提高 CPU 的使用率。 注:进程和进程之间的内存独立。
4. 进程引入多线程的作用
提高进程的使用率。 注:线程和线程之间栈内存独立,堆内存和方法区内存共享。一个线程一个栈。
5. 描述Java程序的执行原理
java 命令执行会启动 JVM,JVM 的启动表示启动一个应用程序,表示启动了一个进程。该进程会自动启动一个“主线程”,然后主线程负责调用某个类的 main 方法。所以 main 方法的执行是在主线程中执行的。然后通过 main 方法代码的执行可以启动其他的“分支线程”。所以,main 方法结束程序不一定结束,因为其他的分支线程有可能还在执行。
二、线程的创建与启动
1. 线程的创建
Java 虚拟机的主线程入口是 main 方法,用户可以自己创建线程,创建方式有s三种: 1.继承 Thread 类实现 2.Runnable 接口(推荐使用 Runnable 接口)
方法一 :写一个类,直接继承java.lang.Thread,重写run方法。
public class MyThread extends Thread{
public void run(){
}
}
MyThread t = new MyThread();
t.start();
方法二 :编写一个类,实现java.lang.Runnable接口,实现run方法。
public class MyRunnable implements Runnable {
public void run(){
}
}
Thread t = new Thread(new MyRunnable());
t.start();
**方法三:实现Callable接口 **
这种方式实现的线程可以获取线程的返回值。上述那两种方式是无法获取线程返回值的,因为run方法返回void。
2. 线程的启动
t.start();
三、线程的生命周期
线程的生命周期存在五个状态:新建、就绪、运行、阻塞、死亡。
新建:采用 new语句创建完成 就绪:执行 start 后 运行:占用 CPU 时间 阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合 终止:退出 run()方法
四、线程的调度与控制
1. 概述
通常我们的计算机只有一个 CPU,CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。在单 CPU 的机器上线程不是并行运行的,只有在多个 CPU 上线程才可以并行运行。Java 虚拟机要负责线程的调度,取得 CPU的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,Java 使用抢占式调度模型。
2. 分类
分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片 抢占式调度模型:优先级高的线程获取 CPU 的时间片相对多一些,如果线程的优先级相同,那么会随机选择一个
3. 线程优先级
线程优先级主要分三 种 : MAX_PRIORITY(最高级) ; MIN_PRIORITY(最低级) ; NOM_PRIORITY(标准) 默认
关于线程优先级的方法
void setPriority(int newPriority);设置线程的优先级
int getPriority();获取线程优先级
static void yield();
void join();
void sleep();
void interuptt();
五、线程同步与数据安全
共享同一个对象启动两个线程,两个线程会对同一个对象进行操作,数据会出现异常,即数据不安全。
1. 什么时候数据在多线程并发的环境下会存在安全问题呢?
条件1:多线程并发。 条件2:有共享数据。 条件3:共享数据有修改的行为。
2. 如何解决线程安全问题?
用排队执行解决线程安全问题, 即线程同步。
3. 何为线程同步机制?
线程同步,指某一个时刻,指允许一个线程来访问共享资源,线程同步其实是对对象加锁,如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上的问题,我们只要保证线程一操作 s 时,线程 2 不允许操作即可,只有线程一使用完成 s 后,再让线程二来使用 s 变量。
4. 异步编程模型与同步编程模型
异步编程模型 : 线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)
同步编程模型 : 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。给共享对象加线程锁。
5. Java中三大变量有哪些会线程安全问题?
三大变量 :
实例变量:在堆中。 静态变量:在方法区。 局部变量:在栈中
线程安全:
局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个。静态变量在方法区中,方法区只有1个。堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。 成员变量:可能会有线程安全问题。
注:
因为局部变量不存在线程安全问题。选择StringBuilder。 StringBuffer效率比较低。 ArrayList是非线程安全的。 Vector是线程安全的。 HashMap HashSet是非线程安全的。 Hashtable是线程安全的。
6. 如何给线程共享对象加锁?
第一种:同步代码块
灵活
synchronized(线程共享对象){
同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized
表示找类锁。
类锁永远只有1把。
就算创建了100个对象,那类锁也只有一把
7. 在开发中一定要用线程同步机制吗?
不是,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。
代替方案
第一种方案:尽量使用局部变量代替实例变量和静态变量。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
六、守护线程
1. 概述
从线程分类上可以分为:用户线程(以上讲的都是用户线程),另一个是守护线程。守护线程是这样的,所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程存在,那么守护线程就不会结束,例如 java 中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束。
2. 如何设置守护线程
t.setDaemon(true);
七、定时器
1. 概述
间隔特定的时间,执行特定的程序。可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
2. 定时器用法
public static void timer1() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
System.out.println("-------设定要指定任务--------");
}
}, 2000);
}
八、生产者与消费者模式
1. 概述
生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
2. wait() 与 notify() 方法实现生产者与消费者模式
注 : wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。wait方法和notify方法不是通过线程对象调用。
Object o = new Object();
o.wait();
Object o = new Object();
o.notify();
|