基础知识
线程
线程是操作系统能够进行运算调度的最小单位.它被包含到进程中,是进程中的实际运作单位.
多线程用来提高效率.让程序同时做多件事.
并发
在同一时刻,有多个指令在单个CPU上交替执行.
并行
在同一时刻,有多个指令在多个CPU上同时执行
多线程实现方式
1.实现runnable接口
2.继承Thread类,重写run方法.
3.实现Callable接口
package com.lanqiao.study;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author: lans
* @date: 2025/4/1
* @name: 刘宇
*/
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 多线程的第三种实现方式
* 1.创建一个类MyCallable实现Callable接口
* 2.重写call(有返回值,表示多线程运行的结果)
* 3.创建MyCallable对象(表示多线程要执行的任务)
* 4.创建FutureTask的对象(作用管理多线程运行的结果)
* 5.创建Thread类的对象,并启动(表示线程)
*/
//3.创建MyCallable对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
//4.创建FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
t1.start();
//获取多线程运行的结果
Integer result = ft.get();
System.out.println(result);
}
}
常见的成员方法
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/1
* @name: 刘宇
*/
public class ThreadDemo1 {
public static void main(String[] args) {
//1.String getName()返回该线程名字
//2.void setName(String name)设置线程的名字
//要设置名字 可以用set方法设置 也可以构造方法设置
//3.static Thread currentThread()获取当前线程的对象
//细节:当JVM启动后 会自动启动多条线程
//其中有一条main线程 它的作用是去调用Main方法 并执行里面的代码
//在以前 我们写的所有代码 起始都是运行在Main线程当中的
//4.static void sleep(long time)让线程休眠指定的时间,单位为ms
// 哪条线程执行到这个方法 就会停留对应时间,时间到后自动醒来 自动执行其他代码
MyThread t1 = new MyThread();
MyThread t2 = new MyThread("飞机");
t1.start();
t2.start();
}
}
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/1
* @name: 刘宇
*/
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0;i<100;i++){
try{
Thread.sleep(1000);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(getName()+"@"+i);
}
}
public MyThread(String name) {
super(name);
}
public MyThread() {
}
}
线程的优先级
JAVA属于抢占式调度 充满随机性
优先级分为10档 最小1 最大10 默认5
优先级不是绝对的 只是一个概率
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/1
* @name: 刘宇
*/
public class ThreadDemo2 {
public static void main(String[] args) {
/*
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
*/
MyRuunable mr = new MyRuunable();
Thread t1 = new Thread(mr,"飞机");
Thread t2 = new Thread(mr,"坦克");
t1.setPriority(1);
t2.setPriority(9);
// System.out.println(t1.getPriority());
// System.out.println(t2.getPriority());
t1.start();
t2.start();
}
}
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/1
* @name: 刘宇
*/
public class MyRuunable implements Runnable {
@Override
public void run() {
for(int i=1;i<=100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
守护线程
final void setDaemon(boolean on)
当其他的非守护线程执行完毕后,守护线程会陆续结束
可以用于这样的场景 聊天:线程1 传输文件:线程2
聊天结束了传输文件就不必要了(特殊情境)
礼让线程
public static void yield()
通过Thread.yield()表示出让当前CPU的执行权,尽可能让线程执行均匀
少
插入线程/插队线程
public static void join()
t.join(); 把t线程插入到当前线程之前 会先执行t线程的代码
线程的生命周期
线程安全的问题
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/1
* @name: 刘宇
*/
public class ThreadDemo3 {
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();
}
}
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/1
* @name: 刘宇
*/
public class MyThread extends Thread {
int ticket = 0;
@Override
public void run() {
// for(int i=0;i<100;i++){
// try{
// Thread.sleep(1000);
// }catch(Exception e){
// e.printStackTrace();
// }
// System.out.println(getName()+"@"+i);
// }
while (true) {
if(ticket<100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票");
}else{
break;
}
}
}
public MyThread(String name) {
super(name);
}
public MyThread() {
}
}
如本题中会出现问题:
1.相同的票卖了多次
2.出现了超出范围的票
线程执行时,有随机性
同步代码块
把操作共享数据的代码锁起来
synchronized(锁){
操作共享数据的代码
}
特点1:锁默认打开,有一个线程进行去了,锁自动关上
特点2:里面的代码全部执行完毕,线程出来,锁自动打开
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/1
* @name: 刘宇
*/
public class MyThread extends Thread {
static int ticket = 0;
//锁对象一定是唯一的
static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized(obj){
if(ticket<100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票");
}else{
break;
}
}
}
}
public MyThread(String name) {
super(name);
}
public MyThread() {
}
}
注意锁放的位置
锁对象一定要是唯一的
synchronized(MyThread.class)这种写法保险
同步方法
StringBuffer是线程安全的 因为是同步方法
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/2
* @name: 刘宇
*/
public class MyThread1 extends Thread {
static int ticket=0;
//同步代码块的方式
// @Override
// public void run() {
// while(true){
// synchronized(MyThread1.class){
// if(ticket==100){
// break;
// }else{
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// ticket++;
// System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!!");
// }
// }
// }
// }
//同步方法
@Override
public void run() {
while (true) {
if(method())break;
}
}
private synchronized boolean method(){
if(ticket==100){
return true;
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!!");
}
return false;
}
}
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/2
* @name: 刘宇
*/
public class ThreadDemo4 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t1.start();
t2.start();
t3.start();
}
}
lock锁
即可手动开关锁
public class MyThread1 extends Thread {
static int ticket=0;
//同步代码块的方式
//加static使多个对象共享同一把锁
static Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
// synchronized(MyThread1.class){
lock.lock();
try {
if (ticket == 100) {
break;
} else {
Thread.sleep(100);
ticket++;
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!!!");
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally {//保证释放锁
lock.unlock();
}
}
}
}
}
死锁
避免锁嵌套
生产者和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式.notify wait
类似于ABABABABABABABABABABA...
void wait() 当前线程等待,直到被其他线程唤醒
void notify() 随机唤醒单个线程
void notifyAll()唤醒所有线程
public class ThreadDemo5 {
public static void main(String[] args) {
cook c = new cook();
foodie f = new foodie();
c.setName("厨师");
f.setName("吃货");
c.start();
f.start();
}
}
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/2
* @name: 刘宇
*/
public class cook extends Thread{
@Override
public void run() {
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();
}
}
}
}
}
}
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/2
* @name: 刘宇
*/
public class foodie extends Thread{
/*
1.循环
2.同步代码块
3.判断共享数据是否到了末尾 到了
4.没到末尾
*/
@Override
public void run() {
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.lock.notifyAll();
//修改桌子的状态
desk.foodFlag=0;
}
}
}
}
}
}
package com.lanqiao.study;
/**
* @author: lans
* @date: 2025/4/2
* @name: 刘宇
*/
public class desk {
//0是没有面条 1有
public static int foodFlag=0;
//总个数
public static int count=10;
//锁对象
public static Object lock=new Object();
}
等待唤醒机制(阻塞队列方式实现)
public class ThredDemo {
public static void main(String[] args) {
//需要指定上限
//1.创建阻塞队列对象 将对象传递给cook foodie实现共用同一个对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//2.创建线程的对象,并把阻塞队列传递过去
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
//3.开启线程
c.start();
f.start();
}
}
public class Cook extends Thread {
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断的把面条放到阻塞队列中
try {
queue.put("面条");
//锁在阻塞队列里面 打印语句在外导致输出结果看的不方便但执行是合理的
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Foodie extends Thread {
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
String food = null;
try {
food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
多线程的6种状态
练习1.抢红包
100元三个包 五个人抢
package com.lanqiao.homework;
/**
* @author: lans
* @date: 2025/4/3
* @name: 刘宇
* 抢红包 100块分成了3个包 现在有5个人去抢 红包是共享数据
*/
public class thread1 {
public static void main(String[] args) {
//创建线程对象
MyThread1 t1 = new MyThread1();
MyThread1 t2 = new MyThread1();
MyThread1 t3 = new MyThread1();
MyThread1 t4 = new MyThread1();
MyThread1 t5 = new MyThread1();
t1.setName("ly");
t2.setName("鸡哥");
t3.setName("31");
t4.setName("sm");
t5.setName("jm");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
package com.lanqiao.homework;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: lans
* @date: 2025/4/3
* @name: 刘宇
*/
public class MyThread1 extends Thread {
static BigDecimal money = BigDecimal.valueOf(100);
//100块分3个包
static int count = 3;
//红包最小金额
static final BigDecimal MIN = BigDecimal.valueOf(0.01);
private static final Lock lock = new ReentrantLock(true); // 启用公平锁
@Override
public void run() {
//同步代码块
lock.lock();
try {
if (count == 0) {
System.out.println(getName() + "没有抢到红包");
} else {
//判断是否到末尾
BigDecimal price;
if (count == 1) {
//此时是最后一个红包 无需随机 即剩下的所有金额
price = money;
} else {
//表示是第一二次
Random r = new Random();
//第一个红包最大99.98
double bounds = money.subtract(BigDecimal.valueOf(count - 1).multiply(MIN)).doubleValue();
//范围0.01-bounds
price = BigDecimal.valueOf(r.nextDouble(bounds));
}
price = price.setScale(2, RoundingMode.HALF_UP);
money = money.subtract(price);
count--;
System.out.println(getName() + "抢到了" + price + "元");
}
} finally {
lock.unlock();
}
}
}
练习2.抽奖箱抽奖
package com.lanqiao.homework;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
/**
* @author: lans
* @date: 2025/4/3
* @name: 刘宇
*/
public class thread2 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
Collections.addAll(list,10,5,20,50,100,200,500);
MyThread2 t1 = new MyThread2(list);
MyThread2 t2 = new MyThread2(list);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
}
}
package com.lanqiao.homework;
import java.util.ArrayList;
import java.util.Collections;
/**
* @author: lans
* @date: 2025/4/3
* @name: 刘宇
*/
public class MyThread2 extends Thread {
//用集合来存放奖品,在构造方法中赋值集合
ArrayList<Integer> list;
public MyThread2(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
//1.循环
//2.同步代码块
//3.判断
while(true){
synchronized (MyThread2.class){
if(list.size()==0){
break;
}else{
//shuffle方法打乱集合然后抽取
Collections.shuffle(list);
int price = list.remove(0);
System.out.println(getName()+"又产生了一个"+price+"元大奖");
}
}
//让线程休眠 结果尽可能随机
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程池
以前写多线程的弊端 用到线程就创建 用完后消失 这样是浪费资源的
创建一个线程池可以解决这个问题.
通过Executors:线程池的工具类,调用方法返回不同类型的线程池对象
public static ExecutorService newCachedThreadPool(int nThreads);
线程池核心原理
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
- 但是如果提交任务时,线程池中没有空闲线程,也无法创建新的线程,任务就会排队等待.
package com.lanqiao.homework;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author: lans
* @date: 2025/4/3
* @name: 刘宇
*/
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
// public static ExecutorService newCachedThreadPool(int nThreads);
//1.获取线程池对象
// ExecutorService pool1 = Executors.newCachedThreadPool();
ExecutorService pool1 = Executors.newFixedThreadPool(3);
//2.提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
//3.销毁线程池
// pool1.shutdown();
}
}
package com.lanqiao.homework;
/**
* @author: lans
* @date: 2025/4/3
* @name: 刘宇
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}