多线程
概述
并行与并发
代码语言:javascript复制并行
指两个或多个事件在同一时刻发生(同时发生)
并发
指两个或多个事件在同一个时间段内发生。(交替执行)
线程与进程
进程:
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间, 一个应用程序可以同时运行多 个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位; 系统运行一个程序即是一个进程从创 建、运行到消亡的过程。
线程
进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。 一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
进程与线程的区别
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。 . 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多
线程
线程类 Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例 每个线程的作用是 完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码 Java使用线程执行体来代表这段程序流。
创建并启动多线程的步骤
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
示例 自定义线程类
执行线程
代码语言:javascript复制 public static void main(String[] args) {
MyThread myThread = new MyThread("MyThread");
myThread.start();
for (int i = 0;i<1000;i ){
System.out.println("main" i);
}
}
执行结果
运行时序图
执行过程
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建 随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。
内存结构
Thread类
构造方法
- public Thread() 分配一个新的线程对象。
- public Thread(String name) 分配一个指定名字的新的线程对象
- public Thread(Runnable target) 分配一个带有指定目标新的线程对象
- public Thread(Runnable target,String name) 分配一个带有指定目标新的线程对象并指定名字
常用方法
- public String getName() :获取当前线程名称。
- public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
- public void run() :此线程要执行的任务在此处定义代码。
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
获取线程名称
- 可以使用Thread类中的方法getName, String getName() 返回该线程的名称。
- 可以先获取当前正在执行的线程,在通过getName方法获取线程名称 static Thread currentThread() 返回对当前正在执行的线程对象的引用。
//1.可以使用Thread类中的方法getName
String name = getName();
System.out.println(name);//创建时, 指定了名称,获取的就是指定的名称
//如果没有指定名称,获取的就是Thread-0
//2.可以先获取当前正在执行的线程
Thread currentThread = Thread.currentThread();
System.out.println(currentThread);//Thread[Thread-0,5,main]
String name2 = currentThread.getName();
System.out.println(name2);//Thread-0
设置线程名称
- 可以使用Thread类中的方法setName void setName(String name) 改变线程名称,使之与参数 name 相同。
MyThread myThread = new MyThread();
myThread.setName("myThreadName");
myThread.start();
- 添加一个带参构造方法,参数传递线程的名称;调用父类的带参构造方法,把名字传递给父类,让父亲给儿子起名字 Thread(String name) 分配新的 Thread 对象。
public class MyThread extends Thread{
//定义指定线程名称的构造方法
public MyThread(String name) {
super(name);
}
public static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)睡醒了,继续执行
代码语言:javascript复制 /*程序在执行第二秒时, 会暂停2秒,2秒后,继续执行后面程序*/
for (int i = 1; i <=60; i ) {
System.out.println(i);
/*让程序睡眠1秒钟 1秒=1000毫秒*/
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Runnable接口
作用:多线程程序的第二种实现方式
构造方法
- Thread(Runnable target) 分配新的 Thread 对象
- Thread(Runnable target, String name) 分配新的 Thread 对象
实现步骤
代码语言:javascript复制1.创建一个类实现Runnable接口
2.重写Runnable接口中的run方法,设置线程任务
3.创建Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程,执行run方法
示例
代码语言:javascript复制public class RunnableImpl implements Runnable{
//2.重写Runnable接口中的run方法,设置线程任务
@Override
public void run() {
//新线程执行的代码
for (int i = 0; i <20; i ) {
System.out.println(Thread.currentThread().getName() "===>" i);
}
}
}
public static void main(String[] args) {
//3.创建Runnable接口的实现类对象
RunnableImpl r = new RunnableImpl();
//4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(r);//打印20次i
//5.调用Thread类中的start方法,开启新的线程,执行run方法
t.start();
//主线程开启新线程之后继续执行的代码
for (int i = 0; i <20; i ) {
System.out.println(Thread.currentThread().getName() "===>" i);
}
}
使用Runnable接口的好处
- 避免单继承的局限性 一个类继承了Thread类就不能继承其他的类 一个类实现了Runnable接口,还可以继续继承别的类,实现其他的接口
- 增强了程序的扩展性,降低程序的耦合度 使用Runnable接口把设置线程任务和开启线程相分离 实现类当中,重写run方法,设置线程任务 创建Thread类对象,调用 start方法,开启新线程
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享
匿名内部类,实现多现程
匿名内部类
作用 把子类继承父类,重写父类的方法,创建子类对象,合成一步完成 把实现类实现接口,重写接口库的方法,创建实现类对象,合成一步完成 最终得要子类对象或实现类对象 格式
代码语言:javascript复制 new 父类/接口(){
重写父类/接口中的方法
};
Thread
代码语言:javascript复制 public static void main(String[] args) {
new Thread(){ //new 没有名称的类 继承Thread
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i ) {
System.out.println(Thread.currentThread().getName() "==>" i);
}
}
}.start();
}
Runnable
代码语言:javascript复制new Thread(new Runnable() { //new没有名称的类实现了Runnable接口
//重写run方法,设置线程任务
@Override
public void run() { //实现接口当中run方法
for (int i = 0; i <20 ; i ) {
System.out.println(Thread.currentThread().getName() "-->" i);
}
}
}).start();
线程安全
什么是线程安全
多线程访问了共享的数据,就会产生线程的安全
举例
多个窗口,同时卖一种票. 如果不进行控制, 可以会出现卖重复的现象
代码实现
卖票线程
代码语言:javascript复制public class TicketRunnableImpl implements Runnable {
//定义共享的票源
private int ticket = 100;
//线程任务:卖票
@Override
public void run() {
while (ticket > 0) {
/*为了提高线程安全问题出现的几率
让线程睡眠10毫秒,放弃cpu的执行权*/
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() "正在卖第" ticket "张票");
ticket--;
}
}
}
开启多线程同时执行
代码语言:javascript复制//创建Runnable接口的实现类对象
RunnableImpl r = new RunnableImpl();
//创建3个线程
Thread t0 = new Thread(r);
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
//开启新的线程
t0.start();
t1.start();
t2.start();
同步代码块synchronized解决线程安全
格式
代码语言:javascript复制synchronized(锁对象){
出现安全问题的代码(访问了共享数据的代码)
}
注意
1.锁对象可以是任意对象 new Person new Student … 2.必须保证多个线程使用的是同一个锁对象 3.锁对象的作用:把{}中代码锁住,只让一个线程进去执行
示例
代码语言:javascript复制public class TicketRunnableImpl implements Runnable {
//定义共享的票源
private int ticket = 100;
private Object obj = new Object(); //锁对象
//线程任务:卖票
@Override
public void run() {
synchronized (obj){
while (ticket > 0) {
/*为了提高线程安全问题出现的几率
让线程睡眠10毫秒,放弃cpu的执行权*/
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() "正在卖第" ticket "张票");
ticket--;
}
}
}
}
总结
同步中的线程,没有执行完毕,不会释放锁对象,同步外的线程没有锁对象进不去同步代码块当中 当没有锁对象时,进入阻塞状态,一直等待. 出了同步后,会把锁对象归还 同步保证了只能有一个线程在同步 中执行共享数据 保存了安全,但是程序频繁的判断锁,释放锁,程序的效率会降低
同步方法解决线程安全
格式
代码语言:javascript复制 修饰符 synchronized 返回值类型 方法名(参数列表){
出现安全问题的代码(访问了共享数据的代码)
}
使用步骤
1.创建一个方法,方法的修饰符添加上synchronized 2.把访问了共享数据的代码放入到方法中 3.调用同步方法
同步方法
示例
代码语言:javascript复制 @Override
public void run() {
ticketMethods();
}
public synchronized void ticketMethods(){
while (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() "正在卖第" ticket "张票");
ticket--;
}
}
锁对象是谁 锁对象为this
代码语言:javascript复制 public void ticketMethods(){
synchronized(this){
while (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() "正在卖第" ticket "张票");
ticket--;
}
}
}
静态同步方法
示例
代码语言:javascript复制public class TicketRunnableImpl implements Runnable {
//定义共享的票源
private static int ticket = 100;
private Object obj = new Object(); //锁对象
//线程任务:卖票
@Override
public void run() {
ticketMethods();
}
public static synchronized void ticketMethods(){
while (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() "正在卖第" ticket "张票");
ticket--;
}
}
}
锁对象是谁 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)
使用Lock锁解决线程安
概述
java.util.concurrent.locks.Lock接口 是JDK1.5之后的新特性 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法
代码语言:javascript复制void lock() 获取锁。
void unlock() 释放锁。
使用步骤
1.在成员位置创建一个Lock接口的实现类对象ReentrantLock 2.在可能会出现安全问题的代码前,调用lock方法获取锁对象 3.在可能会出现安全问题的代码后,调用unlock方法释放锁对象
示例
代码语言:javascript复制public class TicketRunnableImpl implements Runnable {
//定义共享的票源
private int ticket = 100;
//1.在成员位置创建一个Lock接口的实现类对象ReentrantLock
Lock l = new ReentrantLock();
//线程任务:卖票
@Override
public void run() {
while (true) {
l.lock();
if (ticket > 0){
try {
Thread.sleep(10);
//卖票操作,ticket--
System.out.println(Thread.currentThread().getName() "正在卖第" ticket "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//3.在可能会出现安全问题的代码后,调用unlock方法释放锁对象
l.unlock(); //无论程序是否异常,都会把锁对象释放,节约内存提高程序的效率
}
}
}
}
}
线程状态
六种线程状态
- NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法
- Runnable(可 运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操 作系统处理器
- Blocked(锁阻 塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程持有锁时,该线程将变成Runnable状态。
- Waiting(无限 等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。 进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
- Timed Waiting(计时 等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。 这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、 Object.wait
- Teminated(被 终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
等待与唤醒
public void wait() : 让当前线程进入到等待状态 此方法必须锁对象调用.
public void notify() : 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.会继续执行wait()方法之后的代码
示例 需求
顾客与老板线程: 创建一个顾客线程(消息者):告诉老板要吃什么 调用wait方法,放弃cpu的执行,进入wating状态(无限等待) 创建一个老板线程(生产者):花5秒做好 做好后 调用notify方法 唤醒顾客 开吃
注意
顾客与老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行 同步使用的锁必须要保证唯一, 只有锁对象才能调用wait和notify方法
代码实现
顾客线程
老板线程
代码语言:javascript复制Object obj = new Object();
new Thread(){
@Override
public void run() {
synchronized (obj){
System.out.println("告诉老板要吃饺子");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("做好===开始吃饺子");
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (obj){
try {
Thread.sleep(3000);
System.out.println("老板饺子已经做好");
obj.notify();//唤醒当前锁对象上的等待线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
进入计时等待状态的两种方式
- 使用sleep(long m)方法,在毫秒值结束后,线程睡醒,进入Runnable/Blocked状态
- 使用wait(long m)方法wait方法如果在毫秒值结束之后,还没有被唤醒,就会自动醒来,进入Runnable/Blocked状态
两种唤醒的方法
- public void notify() 随机唤醒1个
- public void notifyall() 唤醒锁对象上所有等待的线程.
线程池
存在问题
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, 这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间
线程池
有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务 在Java中可以通过线程池来达到这样的效果 线程池其实就是一个容纳多个线程的容器,其中的线程可以反复使用, 省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。
线程池的简要工作模型
解释
线程池的工作模型主要两部分组成,一部分是运行Runnable的Thread对象,另一部分就是阻塞队列。 由线程池创建的Thread对象其内部的run方法会通过阻塞队列的take方法获取一个Runnable对象,然后执行这个Runnable对象的run方法 在Thread的run方法中调用Runnable对象的run方法 当Runnable对象的run方法执行完毕以后,Thread中的run方法又循环的从阻塞队列中获取下一个Runnable对象继续执行 这样就实现了Thread对象的重复利用,也就减少了创建线程和销毁线程所消耗的资源。
合理利用线程池能够带来三个好处
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Executors类
作用
在JDK1.5的时候java提供了线程池 java.util.concurrent.Executors类:线程池的工厂类,用来生产线程池
方法
代码语言:javascript复制static ExecutorService newFixedThreadPool(int nThreads)
//创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程 int nThreads:创建线程池中线程的个数
代码语言:javascript复制submit(Runnable task)
//提交一个 Runnable 任务用于执行
代码语言:javascript复制oid shutdown()
//用于销毁线程池,一般不建议使用
//注意:线程池销毁之后,就在内存中消失了,就不能在执行线程任务了
使用步骤
- 使用线程池工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
- 调用线程池ExecutorService中的方法submit,传递线程任务,执行线程任务
public static void main(String[] args) {
//1.使用线程池工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService ex = Executors.newFixedThreadPool(2);
//2.调用线程池ExecutorService中的方法submit,传递线程任务,执行线程任务
// 相当于new Thread(new Runnable(){}).start();
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() "线程任务1执行了!");
}
});
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() "线程任务2执行了!");
}
});
ex.shutdown();//销毁线程比
ex.submit(new Runnable() { //会报错
@Override
public void run() {
System.out.println(Thread.currentThread().getName() "线程任务3执行了!");
}
});
}