进程:是程序的基本执行实体
线程:是操作系统能够进行运算调度的最小的单位,它被包含在进程之中,是进程中的实际运作单位。
并发:在同一时刻,有多个指令在单个CPU上交替
执行
并行:在同一时刻,有多个指令在多个CPU上同时
执行
线程优先级最小是1,最大是10,默认是5;优先级越大,抢占到CPU的概率越大。
当其他非守护线程执行完毕后,守护线程会陆续结束(当非守护线程结束了,守护线程也就没有存在的必要了,不会等到执行完守护线程才结束)。
练习:
需求:
某电影院目前正在上映国产大片,共有100张票,而它有三个窗口卖票,请设计一个程序模拟该电影院卖票。
代码实现:
public class MyThread extends Thread {
// 加static表示这个类的所有对象,都共享ticket数据
static int ticket = 0;//0~99
@Override
public void run() {
while (true) {
if (ticket < 100) {
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!");
} else {
break;
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
// 起名字
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
// 开启线程
t1.start();
t2.start();
t3.start();
}
}
上述代码存在安全问题:
引发原因:
线程执行时,具有随机性。
解决思路:
把操作共享数据的代码锁起来。
解决方法:
注意:synchronized的锁对象可以时任意的,但一定要是唯一的
修改上述线程类代码:
public class MyThread extends Thread {
// 加static表示这个类的所有对象,都共享ticket数据
static int ticket = 0;//0~99
// 锁对象,一定要是唯一的
static Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object) {
if (ticket < 100) {
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!");
} else {
break;
}
}
}
}
}
锁对象也可以用MyThread.class
对象,因为字节码文件也是唯一的。
public class MyRunnable implements Runnable {
int ticket = 0;
@Override
public void run() {
while (true) {
if (method()) {
break;
}
}
}
private synchronized boolean method() {
if (ticket < 100) {
ticket++;
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!");
return false;
} else {
return true;
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程对象
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
// 起名字
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
// 开启线程
t1.start();
t2.start();
t3.start();
}
}
代码实现:
public class MyThread extends Thread {
// 加static表示这个类的所有对象,都共享ticket数据
static int ticket = 0;//0~99
// 注意:采用第一种实现多线程的方式,锁对象要加static关键字
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticket < 100) {
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!");
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
// 起名字
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
// 开启线程
t1.start();
t2.start();
t3.start();
}
}
public class MyThread extends Thread {
static Object objectA = new Object();
static Object objectB = new Object();
@Override
public void run() {
while (true) {
if ("线程A".equals(getName())) {
synchronized (objectA) {
System.out.println("线程A拿到了A锁,准备拿B锁");
synchronized (objectB) {
System.out.println("线程A拿到了B锁,顺利执行完一轮");
}
}
} else if ("线程B".equals(getName())) {
synchronized (objectB) {
System.out.println("线程B拿到了B锁,准备拿A锁");
synchronized (objectA) {
System.out.println("线程B拿到了A锁,顺利执行完一轮");
}
}
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 起名字
t1.setName("线程A");
t2.setName("线程B");
// 开启线程
t1.start();
t2.start();
}
}
用来控制生产者和消费者的执行:
/**
* @date 2023/7/22
* @deprecated 用来控制生产者和消费者的执行
*/
public class Desk {
/**
* 桌子上是否有食物 0:没有食物 1:有食物
*/
public static int foodFlag = 0;
/**
* 最多能吃10个
*/
public static int count = 10;
/**
* 锁对象 要唯一
*/
public static Object lock = new Object();
}
消费者代码:
/**
* @date 2023/7/22
* @deprecated 消费者
*/
public class ConsumerThread extends Thread {
@Override
public void run() {
/*
* 1. 循环
* 2. 同步代码块
* 3. 判断共享数据是否到了末尾(到了末尾)
* 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
**/
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
// 判断桌子上是否有食物
if (Desk.foodFlag == 0) {
// 如果没有,就等待
try {
Desk.lock.wait(); // 让当前线程跟锁进行绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 把吃的总数-1
Desk.count--;
// 如果有,就开吃
System.out.println("消费者正在吃面条,还能再吃" + Desk.count + "碗!!!");
// 修改桌子状态
Desk.foodFlag = 0;
// 吃完之后,唤醒生产者继续生产
Desk.lock.notifyAll();
}
}
}
}
}
}
生产者代码:
/**
* @date 2023/7/22
* @deprecated 生产者
*/
public class ProducerThread extends Thread{
@Override
public void run() {
/*
* 1. 循环
* 2. 同步代码块
* 3. 判断共享数据是否到了末尾(到了末尾)
* 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
**/
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
// 判断桌子上是否有食物
if (Desk.foodFlag == 1) {
// 如果有,就等待
try {
Desk.lock.wait(); // 让当前线程跟锁进行绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 如果没有,就开始生产
System.out.println("生产者生产了一碗面条!!!");
// 修改桌子状态
Desk.foodFlag = 1;
// 生产完之后,唤醒消费者继续吃
Desk.lock.notifyAll();
}
}
}
}
}
}
启动类:
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程对象
ConsumerThread consumer = new ConsumerThread();
ProducerThread producer = new ProducerThread();
// 给线程设置名字
producer.setName("生产者");
consumer.setName("消费者");
// 开启线程
producer.start();
consumer.start();
}
}
细节:生产者和消费者必须使用同一个阻塞队列; 阻塞队列的put方法和take方法中就有加锁了,所以我们不需要在使用synchronized关键字或者使用Lock锁了。
消费者代码实现:
public class ConsumerThread extends Thread {
ArrayBlockingQueue<String> queue;
public ConsumerThread(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
// 不断从阻塞队列中获取面条
try {
String take = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者代码实现
public class ProducerThread extends Thread {
ArrayBlockingQueue<String> queue;
public ProducerThread(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
queue.put("面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
启动类代码实现:
public class ThreadDemo {
public static void main(String[] args) {
// 创建容量为1的阻塞队列
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
// 创建线程对象
ConsumerThread consumerThread = new ConsumerThread(queue);
ProducerThread producerThread = new ProducerThread(queue);
// 开启线程
consumerThread.start();
producerThread.start();
}
}
注意: 上述的生产者和消费者代码中都没有打印语句是因为阻塞队列的put和take方法中实现了加锁,如果我们写的打印语句的话,打印语句是在锁的外面de,就会有重复打印的现象。但是打印语句没有操作共享数据的话是不会有问题的。