day18
Thread类
/*
进程:是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。
举例:IDEA, 阿里云盘, wegame, steam
线程:是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。
java提供了一个类用来描述线程:Thread
线程是程序中执行的线程。
Java虚拟机允许应用程序同时执行多个执行线程。
每个线程都有优先权, 具有较高优先级的线程优先于优先级较低的线程执行
创建线程的方式:实现Runnable接口,借助Thread类创建线程对象和继承Thread类方式
若将来每一个线程执行的逻辑是一样的话,推荐采用实现Runnable接口方式实现多线程
若将来每一个线程执行逻辑不一样的话,推荐采用继承Thread类方式实现多线程
*/
代码案例
创建线程的二种方式:
1.继承Thread类通过创建一个新的类继承Thread类,并重写其run方法来创建线程。
若将来每一个线程执行逻辑不一样的话,推荐采用第一种继承Thread类方式实现多线程
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
//run方法是将来线程对象启动时要执行的计算逻辑
for(int i=1;i<=200;i++){
System.out.println(getName()+" - "+i);
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
//创建一个线程对象
// MyThread t1 = new MyThread();
// MyThread t2 = new MyThread();
MyThread t1 = new MyThread("李刚");
MyThread t2 = new MyThread("钱志强");
//给线程起名字
// 方式1:调用setName()方法起名字
// t1.setName("李刚");
// t2.setName("钱志强");
// 方式2:使用构造方法起名字
// t1.run();
// t2.run();
t1.start(); // 系统分配资源给线程t1,启动线程,t1线程具备了执行的资格,具体等到抢到cpu执行权的时候才会执行
t2.start();
}
}
2.实现Runnable接口,通过创建一个类实现Runnable接口,并实现其run方法。然后可以将Runnable实例传递给Thread的构造方法来创建线程。
若将来每一个线程执行的逻辑是一样的话,推荐采用第二种实现Runnable接口方式实现多线程
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
//可以先通过获取当前执行的线程对象,调用内部getName()方法
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
public class RunnableDemo1 {
public static void main(String[] args) {
//创建Runnable对象
MyRunnable r1 = new MyRunnable();
//创建线程对象
Thread t1 = new Thread(r1);
//创建线程对象同时起名字
Thread t1 = new Thread(r1, "李刚");
//获取线程优先权 默认线程优先级是 5
System.out.println("t1: "+t1.getPriority());
//设置线程优先级 线程优先级范围是从 Thread.MIN_PRIORITY (值为1) 到 Thread.MAX_PRIORITY (值为10)。默认情况下,线程的优先级被设置为 Thread.NORM_PRIORITY (值为5)。
t1.setPriority(1); // 1-10
t1.start();
}
}
休眠线程 public static void sleep(long millis)
Thread.sleep(millis) 方法使当前正在执行的线程暂停执行指定的时间(以毫秒为单位),让出CPU给其他线程使用。
public class SleepExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("线程休眠前");
try {
Thread.sleep(2000); // 线程休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程休眠后");
});
thread.start();
}
}
加入线程: public final void join()
join() 方法等待调用该方法的线程终止。这通常用于主线程等待子线程完成。
public class JoinExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
//每过一秒执行一次
for (int i = 0; i < 5; i++) {
System.out.println("子线程:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
try {
thread.join(); // 主线程等待子线程结束 如果不使用这个方法 则子线程与主线程谁抢到cpu使用权就会执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
礼让: public static void yield()
Thread.yield() 方法暗示调度器当前线程愿意让出对CPU的使用权,允许相同优先级的其他线程获得CPU时间片。
public class YieldExample {
public static void main(String[] args) {
Thread.currentThread().yield(); // 当前线程礼让,让其他线程有机会执行
System.out.println("当前线程礼让后执行");
}
}
后台线程:public final void setDaemon(boolean on)
setDaemon(boolean on) 方法将当前线程标记为守护线程。守护线程通常用于为其他线程提供服务,如垃圾回收器。当所有用户线程结束时,守护线程会自动结束。
用户线程:优先级高于守护线程
守护线程【后台线程】:当一个程序没有了用户线程,守护线程也就没有了
中断线程:
public final void stop() 已弃用
public void interrupt()
java所有的线程要想变成运行状态,必须经过抢cpu执行权
public class DaemonExample {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
while (true) {
//每过一秒执行一次
System.out.println("守护线程运行中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();
try {
Thread.sleep(2000); // 主线程休眠2秒后结束 即守护线程执行两次后将自动结束进程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束,守护线程将自动结束");
}
}
线程安全问题
如何判断一个程序是否存在线程安全的问题
三要素,缺一不可:
1、存在多线程环境
2、存在共享数据/共享变量
3、存在多条语句操作着共享数据/共享变量
解决线程安全的方案
方案1:同步代码块
synchronized(对象){需要同步的代码;} 这里对象要保证多个线程对象唯一的 传入的是对象的类的class对象
同步方法:锁对象是this
同步静态方法:锁对象是当前类的class文件对象 类.class。
方案2:lock锁
// 加锁
lock.lock();{进程语句}
//释放锁
lock.unlock();
synchronized代码案例
同步代码块(synchronized block)允许你指定一个对象作为锁,来保护对共享资源的访问。当你想要同步一个静态方法时,实际上你是想要同步对一个类级别资源的访问,而不是一个实例资源。
如果静态方法内部需要同步访问类的共享资源,你可以使用这个类的Class对象或者任何与类关联的静态对象作为锁。通常,使用类的Class对象作为锁是最常见的做法,因为它是与类直接关联的
public class MyClass {
public static void staticMethod() {
synchronized(MyClass.class) { // 使用类的Class对象作为锁
// 需要同步的代码
System.out.println("访问类的共享资源");
}
}
public void instanceMethod() {
synchronized(this) { // 使用当前实例作为锁
// 实例方法中的同步代码
System.out.println("访问实例的共享资源");
}
}
}
在这个例子中,staticMethod 是一个静态方法,它使用 MyClass.class 作为同步锁来同步对类级别资源的访问。而 instanceMethod 是一个非静态方法,它使用 this(当前实例的引用)作为锁来同步对实例资源的访问。
使用类的 Class 对象作为锁是线程安全的,因为 .class 表达式会返回类的 Class 对象的引用,而这个引用在Java虚拟机中是唯一的。这意味着无论你在哪里使用 MyClass.class,它都指向同一个 Class 对象,因此所有使用这个对象作为锁的线程都将协作以确保同步。
lock锁代码案例
package com.shujia.day18;
import java.util.concurrent.locks.ReentrantLock;
/*
使用lock锁来解决线程安全的问题
*/
class Window4 implements Runnable {
int tickets = 200;
Object obj = new Object();
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 加锁
lock.lock();
if (tickets > 0) { // 1
try {
// t1
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
}
//释放锁
lock.unlock();
}
}
}
public class SellTicketsDemo4 {
public static void main(String[] args) {
Window4 window4 = new Window4();
Thread t1 = new Thread(window4, "窗口1");
Thread t2 = new Thread(window4, "窗口2");
Thread t3 = new Thread(window4, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
死锁
public class Locks {
public static final ReentrantLock LOCK1 = new ReentrantLock();
public static final ReentrantLock LOCK2 = new ReentrantLock();
private Locks(){}
}
package com.shujia.day18;
/*
死锁:线程之间存在相互等待的现象
案例:中国人和外国人
前提:中国人吃饭必须要两支筷子,外国人吃饭必须一把刀和一把叉
现在:
中国人:一支筷子和一把刀
外国人:一支筷子和一把叉
*/
class Person extends Thread{
boolean flag;
public Person(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized (Locks.LOCK1){
System.out.println("if lock1");
// p1
synchronized (Locks.LOCK2){
System.out.println("if lock2");
}
}
}else {
synchronized (Locks.LOCK2){
System.out.println("else lock2");
// p2
synchronized (Locks.LOCK1){
System.out.println("else lock1");
}
}
}
}
}
public class DieLock {
public static void main(String[] args) {
Person p1 = new Person(true);
Person p2 = new Person(false);
p1.start();
p2.start();
}
}
等待唤醒
/*
等待唤醒机制:
生产者
消费者
共享数据
测试类
等待唤醒机制:前提是要保证程序是线程安全的
*/
public class WaitNotifyDemo1 {
public static void main(String[] args) {
Student s = new Student();
//创建生产者线程对象
Product product = new Product(s);
//创建消费者线程对象
Consumer consumer = new Consumer(s);
product.start();
consumer.start();
}
}
package com.shujia.day18.bao1;
public class Product extends Thread{
Student s;
int i =0;
public Product(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();
while (true){
synchronized (s){
//作为生产者,在生产数据之前,应该先检查一下数据有没有被消费
//如果没有被消费,就等待消费者消费
if(s.flag){
//等待 锁对象调用方法等待
try {
s.wait(); // 程序走到这一步,发生阻塞,直到锁对象再次在程序中被调用了notify()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i%2==0){
s.setName("李刚");
s.setAge(18);
}else {
s.setName("钱志强");
s.setAge(10);
}
// 生产完数据后,通知消费者来消费数据
// 由锁对象来通知
s.notify();
s.setFlag(true);
i++;
}
}
}
}
package com.shujia.day18.bao1;
public class Consumer extends Thread{
Student s;
public Consumer(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();
while (true){
synchronized (s){
//消费者在消费数据之前,应该先看一看数据有没有产生【flag是否是true】
//若没有数据产生,等待生产者生产数据
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.getName()+"-"+s.getAge());
//消费者消费完数据后,通知生产者生产数据
s.notify();
s.setFlag(false);
}
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)