Java进阶——线程与多线程
线程和多线程
概念
-
程序
程序是一段静态代码。 -
进程
进程是程序的一次动态执行过程(从代码加载、执行、执行完毕的完整过程)。进程是资源分配的最小单位。 -
线程
线程是CPU调度的最小执行单位。程序执行过程中可以产生多个线程。
进程和线程的区别
- 对进程:一个应用程序对应一个进程;进程是资源分配的最小单位;通过多线程占据系统资源;进程之间数据状态完全独立。
- 对线程:一个进程可以有多个线程;线程是执行程序的最小单元;线程是占用CPU的基本单位;线程之间共享一块内存空间。
线程的生命周期
-
新建状态
线程对象创建,还未调用start()
方法。 -
就绪状态
调用start()
方法,但调度程序还未将其选为可运行线程。 -
运行状态
线程调度程序从可运行池中选择一个线程作为当前线程。 -
阻塞状态
- 等待状态
- 阻塞状态
- 睡眠状态
线程是活的,但没有条件运行;当某事件发生后,可返回到就绪状态。
-
死亡状态
线程run()
方法完成。线程一旦死亡,不可复生(调用start()会抛异常
)。
Java的线程
主线程
每个Java程序都有一个默认的主线程。
当JVM加载代码,发现main方法后,会立即启动一个线程(主线程)
主线程特点
- 产生其他子线程的线程
- 不一定是最后完成执行的线程
创建线程
- 继承Thread类
- 重写
run()
方法 - new一个线程对象
- 调用对象的
start()
方法启动线程
- 重写
- 实现Runnable接口
- 实现
run()
方法 - 创建一个Runnable类的对象
- 创建Thread类对象,将Runnable对象作为参数
- 调用Thread对象的
start()
方法启动线程
- 实现
- 一个线程只能被启动一次,
run()
方法执行结束后,线程结束。 - 一个程序多个线程,线程只能保证开始时间,结束时间和执行顺序无法确定。
- 线程调度采用队列形式;JVM线程调度程序决定执行就绪状态的某个线程。
- 运行的线程有名字
- 可通过JVM默认线程名字
- 自定义线程名字
setName()
//给子线程命名
//默认为Thread-
Thread1 t1=new Thread1();
Thread t=new Thread(t1);
t.start();
t.setName("t1");
//获取主线程,并命名
Thread.currentThread().setName("mm");
System.out.println(Thread.currentThread().getName());
创建线程方法对比
- 继承Thread类
- 编写简单,访问当前线程
this
- java是单继承机制,不能继承其他父类;没有达到资源共享
- 编写简单,访问当前线程
- 实现Runnable接口
- 编程复杂,访问当前线程
Thread.currentThread()
- java是多接口实现,可继承其他类;可多个线程共享同一个目标对象。
- 编程复杂,访问当前线程
//Runnable
//多线程解决同一问题
Thread1 t1 = new Thread1();
new Thread(t1).start();
new Thread(t1).start();
new Thread(t1).start();
//Thread
//资源不共享
Thread t1 = new Thread1();
Thread t2 = new Thread2();
Thread t3 = new Thread3();
t1.start();
t2.start();
t3.start();
线程的方法
方法名 | 功能 |
---|---|
start() | 启动线程,让线程从新建状态进入就绪状态队列 |
run() | 普通方法,线程对象被调度后执行的操作 |
sleep() | 暂停线程的执行,休眠线程 |
yield() | 暂停正在执行的线程,让同等优先级的线程运行 |
join() | 暂停当前线程的执行,等调用该方法的线程执行完后,线程返回就绪状态 |
interrupt() | 唤醒休眠的线程 |
stop() | 终止线程 |
isAlive() | 测试线程的状态;新建/死亡状态=false |
currentThread() | 返回当前正在执行线程对象的引用 |
//输出 (新建状态、死亡状态为false)
//false false
//join方法将主线程暂停,先执行Thread1线程
Thread1 t1=new Thread1();
Thread t=new Thread(t1);
System.out.print(t.isAlive());
t.start();
t.join();
System.out.print(t.isAlive());
设置线程优先级
- 通过Thread的
setPriority()
方法设置线程优先级Thread.MIN_PRIORITY
——1Thread.NORM_PRIORITY
——5Thread.MAX_PRIORUTY
——10
- 通过thread的
getPriority()
方法得到线程优先级 - 线程默认优先级为创建其的运行状态线程的优先级
线程让步
当线程池中的线程具有相同优先级:
- 选择一个线程运行,知道线程阻塞或运行结束
- 时间分片,为线程池中每个线程提供均等运行机会
多线程运行时,JVM按优先级调度,级别相同的由操作系统按时间片分配。
阻止线程执行
线程睡眠 sleep()
当线程睡眠时,暂停执行;苏醒前不会回到就绪状态;
当睡眠时间到期,线程回到就绪状态。
- 线程睡眠可帮助其他线程获得运行机会的最好方法
- 线程苏醒后,返回到就绪状态
sleep()
指定时间为最短睡眠时间sleep()
为静态方法,只能控制当前运行的线程
线程等待 yield()
线程让步,暂停当前正在执行的线程对象,并执行同等优先级的其他线程
yield()使线程从运行状态——>就绪状态;让步的线程也有可能被线程调度程序选中。
线程阻塞 join()
线程A中调用线程B的join()
方法,让线程A置于线程B的尾部。
在线程B执行完毕之前,线程A一直处于阻塞状态,只有当B线程执行完毕时,A线程才能继续执行
当join(100)带有参数时,如果A线程中掉用B线程的join(100),则表示A线程会等待B线程执行100毫秒,100毫秒过后,A、B线程并行执行;同时join(0)==join()
join方法必须在线程start方法调用之后调用才有意义
在主线程中执行程序:创建A、B两个子线程,首先调用线程A的
start()
方法执行线程A;
调用线程A的join()
方法,使主线程进入阻塞状态,只有当线程A执行完毕后,才能执行主线程
线程A执行完毕后,主线程才可执行,调用线程B的start()
方法执行线程B。
Thread a = new ThreadA();
Thread b = new ThreadB();
//线程A开始执行
a.start();
//线程A调用join()
a.join();
//线程B开始执行
b.start();
多线程
对象互斥锁
Java每个对象都对应一个互斥锁的标记。
每个对象只有一个锁(lock)与之相关联.
synchronized
关键字与对象互斥锁联合使用,保证对象在任意时刻只能由一个线程访问。
避免多个线程进行访问导致数据不同步的问题。
- 修饰代码块,该代码块在任意时刻只能由一个线程访问
- 作用范围:代码块{}的内容
- 作用对象:调用该代码块的对象
//实现Runnable接口
public class Thread1 implements Runnable {
private static int count;
@Override
public void run() {
// TODO Auto-generated method stub
// 1. 修饰代码块
// 同步语句块
synchronized (this) {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ":" + count);
}
}
}
}
// 同一个对象
//实现资源共享、资源同步
Thread1 thread = new Thread1();
new Thread(thread).start();
new Thread(thread).start();
// 不同对象
//实现资源同步、多线程进行处理
Thread1 thread = new Thread1();
Thread1 thread1 = new Thread1();
new Thread(thread).start();
new Thread(thread1).start();
- 修饰方法,表示该方法在任意时刻只能由一个线程访问
- 作用范围:方法内容
- 作用对象:调用方法的对象
- 关键字
synchronized
不可继承 - 定义接口不可使用关键字
synchronized
修饰 - 构造方法不可使用关键字
synchronized
修饰,但可以用同步代码块
public synchronized void print(){
//todo
}
- 修饰静态方法,
- 作用范围:静态方法内容
- 作用对象:这个类的所有对象
public synchronized static void print(){
//todo
}
- 修饰类,表示该类的所有对象公用一把锁
- 作用范围:{}包括的所有内容
- 作用对象:这个类的所有对象
class ClassTest{
public void method(){
synchronized(ClassTest.class){
//todo
}
}
}
多线程同步
为了更好的解决多个交互线程之间的运行进度。
引入wait()
方法与notify()
方法
wait()方法:使当前线程进行等待状态
notify()方法:通知那些等待该对象锁的其他线程,使其重新获取该对象的对象锁。
wait()
与notify()
方法必须配合synchronized
关键字使用wait()
会释放锁,notify()
不会释放锁wait()
方法执行后,执行interrupt()
方法会报异常
死锁
死锁:当两个或两个以上的线程在执行过程中时,因争夺资源造成互相等待,若无外力作用,线程都无法推进下去的现象。
必要条件:
- 互斥条件
- 线程对分配到的资源进行排他性使用;其他线程不可使用。
- 请求、保持条件
- 线程保持至少一个资源,但又提出新的资源请求。
- 不可剥夺条件
- 线程获得的资源在使用完之前,不可被剥夺;只能在使用完后自主释放。
- 环路等待条件
- 发生死锁时,必然存在线程请求资源、资源被另一线程占用的环。
线程池
引入
为什么有线程池
通过在主线程中new一个Thread线程
- 新建线程对象性能差
- 线程缺乏统一管理,造成多线程之间的死锁、同步、资源协调问题
- 缺乏定期执行等功能
线程池优点
- 重用线程、减少线程的创建、死亡开销。
- 可控制最大并发线程数,提高系统资源利用率
- 提供定期执行等功能
线程池
-
newCachedThreadPool
可缓存线程池。
当请求线程数大于线程池长度
- 回收空闲线程
- 没有空闲线程可回收时,创建新线程
//可缓存的线程
ExecutorService pool = Executors.newCachedThreadPool();
MyThread thread = new MyThread();
//线程池执行子线程
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
//此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
pool.shutdown();
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
-
newFixdThreadPool
定长线程池
可控制线程最大并发数,超出的线程会在队列等待
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
//定长线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
MyThread thread = new MyThread();
//线程池执行子线程
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
//关闭线程池
pool.shutdown();
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
-
newScheduledThreadPool
延迟连接池
支持定时、周期性任务执行
//创建延时线程池-定长
ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(3);
//创建子线程
MyThread thread = new MyThread();
//线程延迟执行
/*
* scheduleAtFixedRate(Runnable,long,long,TimeUnit);
* 1.Runnable:子线程
* 2.long:该线程池延迟毫秒
* 3.long:两次执行最短间隔
* 4.TimeUnit:计时单位
* */
pool.scheduleAtFixedRate(thread, 5000, 1000, TimeUnit.MILLISECONDS);
-
newSingleThreadExecutor
单线程化线程池
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
ExecutorService pool = Executors.newSingleThreadExecutor();
MyThread thread = new MyThread();
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.shutdown();
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
在此感谢以下博主
java 线程方法join的简单总结
Java中Runnable和Thread的区别
Java中Synchronized的用法
四种Java线程池用法解析