目录
- 多线程
- 一、线程概述
- 四、线程常用方法
多线程
一、线程概述
1. 进程
正在执行的应用程序(java.exe),一个可执行的程序一次运行的过程
- 独立性:不同进程之间相互独立
- 动态性:是一直活动的
并发性:多个进程可以在单个处理器上同时运行
2. 线程
线程是程序中的一个执行流,每个线程都有自己的专有寄存器,但代码区是共享的,即不同的线程可以执行同样的函数
- 各线程在运行过程中可能会出现资源竞争
- CPU在不同的线程之间切换
每个线程的执行机会相对随机
3. 进程与线程
- 一个线程只能属于一个进程
- 一个进程可以有多个线程,至少有一个主线程
- 同一进程的所有线程共享系统分配给该进程的资源
- 线程是指进程内的一个执行单元,也是进程内的可调度实体
系统创建进程时需要重新分配资源,而创建线程相对容易,因此效率更高
4. 多线程
多线程是指程序中包含多个执行流,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务
5. 主线程
任何一个Java程序启动时,一个线程立刻运行,执行main方法(程序的入口),这个线程称为程序的主线程
- 主线程是产生其它子线程的线程
通常必须最后关闭,因为它要执行其它子线程的关闭工作
二、多线程优缺点
(1)优点
- Java支持编写多线程程序
- 可以并发执行多个任务
最大限度的减少CPU的闲置时间,提高CPU的利用率
(2)缺点
- 线程越多占用内存越多
- 线程之间对共享资源访问会互相影响
线程过多会导致控制复杂化
三、线程的创建
Thread类也实现了Runnable接口
1. 继承Thread类
Thread类中的run方法本身并不执行任何操作,需要继承Thread类后重写该方法
- MyThread
// TODO 线程实现实现方式:继承Thread类
System.out.println(111);
// 1.声明一个定义的线程对象 - 父类引用指向子类对象
Thread thread1 = new MyThread();
thread1.setName("子线程1");
// 2.使一个线程进入到就绪状态:start()方法
// 线程启动后会调用相应的run方法
thread1.start();
// 直接调用run方法相当于执行一个普通类下的普通方法
// 此时程序会等待方法调用完成后按顺序执行
thread1.run();
System.out.println(222);
// 多个子线程在执行时,执行的机会相对随机
Thread thread2 = new MyThread("子线程2");
thread2.start();
thread2.run();
System.out.println(333);
- ThreadTest
// 实现线程的方式 - 继承Thread类
public class MyThread extends Thread {
// 显示声明空的构造方法
public MyThread() {
}
// 子类中调用父类的构造方法
public MyThread(String name) {
// 在线程实例化时指定线程名称
super(name);
}
// 重写run方法
public void run() {
for (int i = 0; i < 10; i++) {
// Thread.currentThread()获得当前所在线程的对象
if (i == 5) {
if (Thread.currentThread().getName().equals("子线程1")) {
// 当某一个线程中出现异常时,不会影响另外一个线程
System.out.println(1/0);
}
}
System.out.println(Thread.currentThread() + ":" + i);
}
}
}
- 小练习
// TODO 多线程练习:相对独立的线程 - 继承Thread
// 某个商城进行促销活动,商城内存在多个商家
// -》类比思想:商场开门 - 主线程启动
System.out.println("商场开门");
// 每个商家售卖不同的商品,并有不同的库存
Thread thread1 = new ShopThread("夏季T恤", 35, "李宁品牌服装店");
Thread thread2 = new ShopThread("洁面神器", 50, "韩国护肤品牌店");
Thread thread3 = new ShopThread("kindle", 30, "亚马逊直销店");
// -》封装实体:能够刻画商家售卖商品以及表示库存
// 描述该商城当天开门直到所有商家商品售空的过程
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
// -》类比思想:商品售空,库存为0时线程正常停止
// 使用两种方式实现
System.out.println("商场关门");
// 多家店铺同时售卖商品
public class ShopThread extends Thread {
public ShopThread() {
}
public ShopThread(String productName, Integer count, String name) {
super(name);
this.count = count;
this.productName = productName;
}
// 售卖的商品
private String productName;
// 商品的库存
private Integer count;
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
// 使用相对随机的方式减少商品的库存
public void run() {
for (int i = count; i > 0;) {
if ((--i) != 0) {
// 通过线程的name属性刻画商家的名称
System.out.println(Thread.currentThread().getName() + "卖出一件" + this.productName + ",仅剩" + i + "件");
} else {
System.out.println(Thread.currentThread().getName() + "商品已售完,关店");
}
}
}
}
2. 实现Runnable接口
- MyRunnable
// 自定义类实现Runnable接口
public class MyRunnable implements Runnable {
// 重写run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 获得当前线程的名称
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
- RunnableTest
public class RunnableTest {
public static void main(String[] args) {
// 将一个实现了Runnalbe接口的实现类的实例传入
MyRunnable myRunnable1 = new MyRunnable();
MyRunnable myRunnable2 = new MyRunnable();
// 调用相应的构造方法,指定线程名
Thread thread1 = new Thread(myRunnable1, "线程1");
// 启动线程
thread1.start();
// 调用相应的构造方法,指定线程名
Thread thread2 = new Thread(myRunnable2, "线程2");
// 启动线程
thread2.start();
}
}
- 小练习
// TODO 多线程练习:相对独立的线程 - 实现Runnable
System.out.println("商场开门");
// 实例化Runnable接口的对象
Runnable runnable1 = new ShopRunnable("夏季T恤", 350);
Runnable runnable2 = new ShopRunnable("洁面神器", 500);
Runnable runnable3 = new ShopRunnable("kindle", 300);
// 通过线程控制目标对象
Thread thread1 = new Thread(runnable1, "李宁品牌服装店");
Thread thread2 = new Thread(runnable2, "韩国护肤品牌店");
Thread thread3 = new Thread(runnable3, "亚马逊直销店");
// 线程启动
thread1.start();
thread2.start();
thread3.start();
// 可以设置最长等待时长
// 超过等待时长,继续运行当先线程内容
// 不指定时长时,等待线程完全执行完毕再执行当前线程
thread1.join(10);
thread2.join(10);
thread3.join(10);
System.out.println("商场关门");
// 多家店铺同时售卖商品
public class ShopRunnable implements Runnable {
public ShopRunnable() {
}
public ShopRunnable(String productName, Integer count) {
this.count = count;
this.productName = productName;
}
// 售卖的商品
private String productName;
// 商品的库存
private Integer count;
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
// 使用相对随机的方式减少商品的库存
public void run() {
for (int i = count; i > 0;) {
if ((--i) != 0) {
// 通过线程的name属性刻画商家的名称
System.out.println(Thread.currentThread().getName() + "卖出一件" + this.productName + ",仅剩" + i + "件");
}else {
System.out.println(Thread.currentThread().getName() + "商品已售完,关店");
}
}
}
}
3. 内部类方式创建
- ThreadInner
public class ThreadInner {
public static void main(String[] args) {
// 使用匿名内部类实现线程执行具体内容
new Thread("新线程") {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + ":" + i);
}
}
// 启动线程
}.start();
}
}
- RunnableInner
public class RunnableInner {
public static void main(String[] args) {
// 实例化线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
// 启动线程
}, "新线程").start();
}
}
四、线程常用方法
1. 设置线程名称
- setName(String name)
Thread(String name)
2. 设置线程休眠
sleep(long millis):进入阻塞状态,时间单位为毫秒
3. 设置线程优先级
默认情况下线程的优先级与创建它的父线程具有相同的优先级,提升抢到时间片的概率,从而获得更多的执行机会
setPriority(int newPriority):参数范围为1-10,需要在执行start()方法前设置
4. 等待某一线程终止
join():先执行start()方法,再执行join方法,当前线程执行完毕后,会继续执行其它线程
5. 后台线程
也称为守护线程或用户线程,如JVM的垃圾回收线程,必须在start()前执行,如果所有的前台线程都死亡,则会自动死亡
setDaemon(boolean on)
五、生命周期
线程启动后进入到就绪状态,多个线程在同时运行时,不断的在争抢CPU的时间片
- 新生:实例化完成
- 就绪:准备运行,暂时没有分配到时间片
- 运行:分配到时间片,执行相关任务
- 阻塞:由于某些特殊情况导致阻塞,会在合适的机会重新进入就绪状态
死亡:线程结束(可以使用stop()方法强制结束)
六、多线程同步
把竞争访问的资源标识为private
使用synchronized(同步的)关键字同步方法或代码块- Ticket
public class Ticket implements Runnable {
// 使用private定义竞争资源
private int ticket = 100;
@Override
public void run() {
while (true) {
// 对当前线程进行同步加锁
synchronized (this) {
ticket--;
// 当车票售空时跳出循环
if (ticket < 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "购买,当前剩余票数:" + ticket);
}
}
}
}
- TicketTest
public class TicketTest {
// 模拟两名旅客的抢票
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread thread1 = new Thread(ticket,"旅客1");
Thread thread2 = new Thread(ticket,"旅客2");
thread1.start();
thread2.start();
}
}