导航
进程和线程
进程
一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
线程
是进程中的单个顺序控制流,是一条执行路径。
线程的生命周期
线程调度
两种调度模型
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型。
Thread 类中设置和获取线程优先级的方法
- public final int getPriority():返回此线程的优先级
- public final void setPriority(int newPriority):更改此线程的优先级
线程默认优先级是 5;线程优先级是一个整数,其取值范围是:1 - 10。
线程优先级高仅仅表示线程获取的 CPU 时间片的几率高,并不是一定先执行。
创建线程的方式
继承 Thread 类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + ", " + i);
}
}
}
public class ThreadMain {
public static void main(String[] args) {
Thread t1 = new MyThread();
Thread t2 = new MyThread();
t1.start();
t2.start();
}
}
注意事项:run() 方法和 start() 方法的区别
- run():封装线程执行的代码;直接调用则相当于普通方法的调用
- start():启动线程;然后由 JVM 调用此线程的 run() 方法
实现 Runnable 接口
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ", " + i);
}
}
}
public class RunnableMain {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
}
}
相比继承 Thread 类,实现 Runnable 接口的优点
- 避免了 Java 单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较子的体现了面向对象的设计思想
通过 Callable 接口和 FutureTask 类
// 创建 Callable 接口的实现类
public class MyCallable implements Callable<Integer> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 使用 FutureTask 类来包装 Callable 对象
// 该 FutureTask 对象封装了该 Callable 对象 call() 方法的返回值
FutureTask<Integer> ft = new FutureTask<>(new MyCallable());
new Thread(ft).start();
// 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值
System.out.println("子线程结束后返回的值:" + ft.get());
}
// 实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}
线程同步
问题场景示例
多个窗口同时卖票:
public class SellTicket implements Runnable {
private int tickets = 100;
@Override
public void run() {
for (; ; ) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出 " + tickets + " 号票");
tickets--;
}
}
}
}
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口一");
Thread t2 = new Thread(st, "窗口二");
Thread t3 = new Thread(st, "窗口三");
t1.start();
t2.start();
t3.start();
}
/* 部分输出:
窗口二卖出 2 号票
窗口三卖出 2 号票
窗口一卖出 2 号票
窗口二卖出 -1 号票 */
出现的问题:
- 相同票被卖多次
- 超卖
synchronized 关键字
Java 以提供关键字 synchronized 的形式,为防止资源冲突提供了内置支持。当任务希望执行被 synchronized 关键字保护的代码片段的时候,Java 编译器会生成代码以查看锁是否可用。如果可用,该任务获取锁,执行代码,然后释放锁。
三大特性
有序性、可见性、原子性
同步代码块
给代码块加锁,任何对象都可以看成是一把锁。格式:
synchronized (对象) {
// 对共享数据操作的代码
}
对卖票线程的改进:
public class SellTicket implements Runnable {
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
for (; ; ) {
// 此处如果这样写 代表每一个线程都有独立的线程锁,而我们需要的是三个线程共用一个线程锁
// synchronized (new Object()) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出 " + tickets + " 号票");
tickets--;
}
}
}
}
}
好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,每个线程都会去判断同步上的锁,这样很耗费资源,会降低程序的运行效率
同步方法
即把 synchronized 关键字加到方法上
- 同步普通方法格式
修饰符 synchronized 返回值 类型方法名(方法参数){ }
同步普通方法的锁对象是 this - 同步静态方法格式
修饰符 static synchronized 返回值 类型方法名(方法参数){ }
同步静态方法的锁对象是 类名.class
Lock 锁
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。
相关方法:
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
对卖票线程的改进:
public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
for (; ; ) {
lock.lock();
try {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出 " + tickets + " 号票");
tickets--;
}
} finally {
lock.unlock();
}
}
}
}
线程间通信
生产者和消费者场景示例
模拟生产者生产商品放入仓库,消费者从仓库取出商品消费,要求当仓库中有商品时不生产,无商品时不消费。
仓库类
public class Warehouse {
/** 商品 */
private int goods;
/** 仓库中是否有商品标识 */
private boolean flag = false;
/** 没加 synchronized 将抛出 java.lang.IllegalMonitorStateException 异常 */
public synchronized void put(int goods) {
// 有商品时 生产者等待生产
if (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.goods = goods;
System.out.println("生产者将商品 " + goods + " 号放入仓库");
// 标记仓库中有商品
flag = true;
// 唤醒其它等待线程
notifyAll();
}
/** 没加 synchronized 将抛出 java.lang.IllegalMonitorStateException 异常 */
public synchronized void get() {
// 无商品时 消费者等待消费
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者将商品 " + goods + " 号取出仓库");
// 标记仓库中无商品
flag = false;
// 唤醒其它等待线程
notifyAll();
}
}
生产者类
public class Producer implements Runnable {
private Warehouse warehouse;
public Producer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for (int i = 1; i < 10; i++) {
this.warehouse.put(i);
}
}
}
消费者类
public class Consumer implements Runnable {
private Warehouse warehouse;
public Consumer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for (; ; ) {
this.warehouse.get();
}
}
}
测试类
public class Demo {
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
Producer producer = new Producer(warehouse);
Consumer consumer = new Consumer(warehouse);
Thread p1 = new Thread(producer);
Thread c1 = new Thread(consumer);
p1.start();
c1.start();
}
}
/** 输出
生产者将商品 1 号放入仓库
消费者将商品 1 号取出仓库
生产者将商品 2 号放入仓库
消费者将商品 2 号取出仓库
...
*/
相关代码地址
版权声明:本文为Amazing20原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。