关于线程理解的笔记
1:关于线程的理解
1.1:什么是线程?
在了解线程之前,我们不得不提到一个词"进程"。那什么是进程呢?进程是系统进行资源分配的基本单位,是操作系统结构的基础。而线程是进程的一条执行路径,也是cpu的基本单位。简单来说,当我们启动一个程序就会在系统中开展一个进程,而通过任务管理器我们就可以看到这个系统所运行的进程。
1.2:什么是线程
线程又称轻量级进程,线程是进程中的一条执行路径,也是cpu的基本调度单位。
1.3:什么是多线程
一个进程由一个或多个线程组成,并且在同一时间执行完成不同的工作
1.4:线程与进程的区别
线程就相当于进程中中独立的运行的子任务,一个线程可以包含多个进程,多个线程可以同时运行。而多线程的出现使我们更加可以更加充分的利用cpu的资源,大大提高了系统的执行能力。如果是一个单线程,那么系统的的执行能力就会特别的低下,运行效率大大降低。
2:java如何创建线程?
java中线程的的实现方式,大致有四种实现方式,目前只学会两种。
1.继续Thread;
//继承Thread
public class MyThread extends Thread{
private int takbov=100;
//重写run方法
@Override
public void run(){
for(int i=0;i<20;i++){
System.out.println("线程"+i);
}
}
}
测试类
public class Text {
public static void main(String[] args) {
// 创建一个线程
MyThread m1 =new MyThread();
//开启线程,当前线程和main主线程同时争夺CPU时间片
m1.start();
for (int i = 0; i <20 ; i++) {
System.out.println("main"+i);
}
}
}
2.实现Runnable
public class MyRunable implements Runnable {
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"线程:"+i);
}
}
}
测试类:
public class MyRunableTest {
public static void main(String[] args) {
MyRunable m1=new MyRunable(); //线程任务
//线程对象
Thread t1=new Thread(m1,"线程A");
t1.start();
//线程对象
Thread t2=new Thread(m1,"线程B");
t2.start();
for (int i = 0; i <20 ; i++) {
System.out.println("main线程:"+i);
}
}
}
2.1:为线程起名获取线程名称
【1】获取线程名称
(1)this.getName()必须是Thread的子类
(2)Thread.currentThread.getName();在任意位置获取线程名称
【2】为线程取名:
(1)通过调用线程对象的setName方法。
(2)通过线程子类的构造方法赋值。
package Thread;
public class Threads extends Thread{
private int takbov=100;
@Override
public void run(){
while (takbov>0){
takbov--;
//该方法可以在任何位置获取线程名:默认系统会为每个线程-- Thread-n
System.out.println(Thread.currentThread().getName()+"剩余"+takbov+"张票");
}
}
}
测试类:
package Thread;
public class Text {
public static void main(String[] args) {
Threads mo1 =new Threads();
// 通过调用对象名.setName()
mo1.setName("线程A");
//2.开启线程.start()---底层会自动调用线程的run方法,来执行该线程的任务
mo1.start();
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"==="+i);
}
}
}
3:实现Callable
public class Text {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// MyCallable myCallable=new MyCallable();
// FutureTask<Integer> task=new FutureTask<Integer>(myCallable);//把线程任务封装到该类中,该类可以获取线程任务执行后的结果.
// Thread t=new Thread(task);
// t.start();
//
// System.out.println(task.get());
//自己创建的线程执行非常麻烦。都使用线程池来执行任务。---所以不要自己创建线程对象,而是使用线程池中的对象
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Integer> submit = executorService.submit(new MyCallable());
System.out.println(submit.get());
}
}
class MyCallable implements Callable<Integer> {
//线程任务:
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<=10;i++){
sum+=i;
}
return sum;
}
}
class MyRunnable implements Runnable{
public void run() {
}
}
Callable与Runnable的区别
1:重写的方法不同:Callable重写Call方法Runnable重写run方法。
2:返回值不同:Callable有返回值Runnable无返回值。
3:线程中常用的方法
1.:休眠
public static void sleep(long)
作用:是当前主动休眠millis毫秒
2:放弃
public static void yield();
作用:放弃本次时间片,回到就绪状态,参与下次时间片的争夺
3:加入
public void join()
作用:允许其他线程加入当前线程,直到其他线程运行结束后。当前线程才可以运行
4:优先级
线程对象.setPriority()
作用:设置线程优先级 值1-10值越大获取cpu的概率越大。默认为5
5:守护线程
线程对象.setDaemon(true);设置守护线程。
线程有两类:守护线程(后台线程)和用户线程(前台线程)。
如果程序中所有前台线程都执行完毕,后台线程也会自动结束。
垃圾回收线程属于守护线程下
4:线程安全
如果多个程序在同时运行,而这些线程可能会同时运行这段代码。程序每次与运行这段代码的结果和单线程运行的结果是一样的,而且其他变量的值和预想的也一样,就是线程安全的。
实例:
package Thread;
public class Threads2 implements Runnable {
private int ticket = 100;
@Override
public void run() {
// 窗⼝ 永远开启
while (true) {
if (ticket > 0) {// 有票 可以卖
// 使⽤sleep模拟⼀下出票时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
}
}
}
测试:
public class Text2 {
public static void main(String[] args) {
// 创建线程任务对象
Threads2 ticket = new Threads2();
// 创建三个窗⼝对象
Thread t1 = new Thread(ticket, "窗⼝1");
Thread t2 = new Thread(ticket, "窗⼝2");
Thread t3 = new Thread(ticket, "窗⼝3");
// 同时卖票
t1.start();
t2.start();
t3.start();
}
}
结果出现的现象:
从中可以发现程序中出现的两个问题:
1:相同的票数
2:不存在的票数
这种几个窗口票数不同步,这种问题被称为线程安全性问题。
5:如何解决线程安全问题、
当多个线程访问同一资源时,其多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决这种情况我们就需要使用锁,锁定的代码都是原子操作。
锁分为两种:
1:自动锁
public class Threads2 implements Runnable {
private int ticket = 100;
@Override
public void run() {
// 窗⼝ 永远开启
while (true) {
// 自动上锁和释放锁
synchronized (this){
if (ticket > 0) {// 有票 可以卖
// 使⽤sleep模拟⼀下出票时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
System.out.println(Thread.currentThread().getName()+ "正在卖:" + ticket--);
}else {
break;
}
}
}
}
}
2:手动锁:Lock
package Thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Threads2 implements Runnable {
private int ticket = 100;
// 创建手动锁
private Lock l=new ReentrantLock();
@Override
public void run() {
// 窗口开启
while (true){
// 手动上锁
l.lock();
if (ticket>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket--);
}else {
break;
}
// 手动释放
l.unlock();
}
}
}
6:什么是死锁?
死锁:当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象标记,并等待A对象锁标记,并等待A对象锁标,产生死锁。
一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经标记的锁标记,由此可能造成死锁。
死锁案例:
1:创建两个锁。
public class LBreak extends Thread{
public static Object la= new Object();// 锁A
public static Object lb=new Object();//锁B
}
2:设置两个线程。
public class TextLA extends Thread{
@Override
public void run() {
synchronized (LBreak.la){
System.out.println("拿到锁A");
synchronized (LBreak.lb){
System.out.println("拿到suoB");
System.out.println("开始执行程序");
}
}
}
}
public class TextLB extends Thread{
@Override
public void run(){
synchronized (LBreak.lb){
System.out.println("拿到锁B");
synchronized (LBreak.la){
System.out.println("拿到锁A");
System.out.println("开始执行另一道程序");
}
}
}
}
测试类:
public class Text{
public static void main(String[] args) {
TextLA textLA = new TextLA();
textLA.start();
TextLB textLB = new TextLB();
textLB.start();
}
}
运行结果:
如何解决死锁?有以下几种方式?
1:减少同步代码块的嵌套。
2:设置锁的超时时间。
3:可以使用安全类-jdk提高安全类
7:线程通信
1:如果存储处没有值,那么线程1可以往存储处有值,如果存储处有值,则线程1无法向其中存值。
2:如果存储处有值,则线程2可以取值。反之存储处无值则不可取值
3:线程1存一次线程2取一次
线程之间就可以通讯了
等待:
通知:
7.2sleep与wait的区别
1:来自不同的类:sleep来自Thread,wait来自Object类。
2:会不会释放锁资源;sleep不会释放锁资源,wait自动释放锁资源。
3:用法:sleep时间到了自然就行了,而wait需要用notify()或者notifyAll()方法唤醒。
7.3:notify与notifyAll的区别
1.notify随机唤醒等待队列中一个线程,而notifyAll会唤醒等待队列中所有的线程。
8:线程的状态
NEW:新建状态
RUNNABLE: start()就绪状态-时间片-运行状态. 统称为RUNNABLE
BLOCKED: 堵塞状态。加锁时就如该状态
WAITING: 无期等待: 调用wait方法时会进入该状态
TIMED_WAITING: 有期等待---当调用sleep方法时就会进入该状态
TERMINATED: 终止状态。线程的任务代码执行完毕或出现异常。
线程的状态之间可以通过调用相应的方法进行转换。
9:线程池
线程池的概念:
问题:
线程是宝贵的内存资源、单个线程约占1MB空间,过多分配容易造成内存溢出。
频繁的创建及销毁线程会增加虚拟机回收的频率·资源开销·造成程序性能下降。
线程池的原理:
线程容器,可定线程分配的数量上限。
将预先创建的线程对象存入池中,并重用线程池中的线程对象。
避免频繁的创建和销毁
线程池的创建:
package demo7;
import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.concurrent.*;
/*
Executor :它是线程池的跟接口;
void execute(Runnable command);执行Runnable类型的任务。
ExecutorService:它是Executor的子接口。--
void shutdown();关闭线程池,需要等待任务执行完毕
shutdownNow();立即关闭线程池,不接受新的任务
isTerminated();线程池是否终止,表示线程池的任务都执行完毕,并且线程池关闭了
isShutdown();判断是否执行了关闭
submit(Callable<T> task);提交任务,可以提交Callable
submit(Runnable task);提交任务可以提交Runnable
Executors: 它是线程池的工具类,该类提供了创建线程池的一些静态方法
*/
public class Test {
public static void main(String[] args) {
// 创建一个固定长度的线程池
// ExecutorService executorService = Executors.newFixedThreadPool(5);
// executorService.submit(new Runnable() {
// @Override
// public void run() {
// System.out.println(Thread.currentThread().getName()+"-----");
// }
// });
//2. 单一线程池。
// ExecutorService executorService = Executors.newSingleThreadExecutor();
// executorService.submit(new Runnable() {
// @Override
// public void run() {
// System.out.println(Thread.currentThread().getName()+"----");
// }
// });
//3. 可变线程池--缓存线程池
// ExecutorService executorService = Executors.newCachedThreadPool();
// for (int i = 0; i < 10; i++) {
// executorService.submit(new Runnable() {
// public void run() {
//
// System.out.println(Thread.currentThread().getName()+"----");
// }
// });
// }
//4. 延迟线程池。
// ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
// for (int i = 0; i < 20; i++) {
// executorService.submit(new Runnable() {
// public void run() {
// System.out.println(Thread.currentThread().getName()+"-----");
// }
// });
// executorService.schedule(new Runnable() {
// public void run(){
// System.out.println(Thread.currentThread().getName()+"----");
// }
// },10,TimeUnit.SECONDS);
// }
// executorService.shutdown();
//上面通过Executors工具类创建线程池,但是阿里巴巴不建议使用。阿里建议使用原生的模式创建线程池。
/*
int corePoolSize,核心线程的个数
int maximumPoolSize,最多的线程个数
long keepAliveTime, 线程空闲时间。
TimeUnit unit, 空闲的单位
BlockingQueue<Runnable> workQueue:等待队列
*/
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(5);//最多等待的任務
ThreadPoolExecutor executor= new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, arrayBlockingQueue);
//零活
for (int i = 0; i <20 ; i++) {
executor.submit(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"-----");
}
});
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本