JAVA线程基础知识
一, 概念
- 进程: 指可执行程序并且存放在计算机存储器的一个
指令的序列
,他是一个动态执行的过程
- 线程: 每个运行的程序都是一个进程,在一个进程中还可以有多个执行单元同时运行,这些执行单元可以看作程序执行的一条条线索, 被称为线程.
1.1, 线程的生命周期
二, 线程的两种创建方式:
2.1, 第一种: 继承Thread类,改写run()
Thread类中的方法:
构造方法:
常用方法:
- 示例代码
//继承Thread类,重写run方法
public class ThreadTest extends Thread{
public void run() {
System.out.println(getName()+"线程开始执行");
}
///测试类
public static void main(String[] args) {
System.out.println("主线程开始执行");
ThreadTest tt = new ThreadTest();
tt.start();
System.out.println("主线程还在执行");
}
}
上述代码执行结果:
如果多加了一个 tt.start();
特点:
- main()也是一个线程,最先执行,叫主线程;
- 一个线程不能多次使用start(),否则会抛出IlligalThreadStateException;
- 线程的运行(即获得cpu的使用权)是
随机
的
2.1, 第二种: 实现Runnable接口,重写run()
- 特点:
- Runnable是Java中用以实现线程的接口;
- 任何实现线程功能的类都必须实现该接口;
- Runnable接口中只有一个run()方法;
Q: 为什么推荐使用这种方式?
Java不支持多继承
(继承了Thread类就不能再继承其他类了)不打算重写Thread类的其他方法
(Thread类中还有其他方法,比如获得线程的标识符,状态,名称等等.)- 另外,在用这种方法创建线程对象时,我们不会使用start()开启线程,而是借用Thread 有参构造方法
Thread(Runnable target)
,也可以使用Thread的另一个带参构造Thread(Runnable target, String ThreadName)
为线程赋值噢
示例代码:
public class RunInterfaceTest implements Runnable{
@Override
public void run() {
System.out.println("线程执行了");
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
RunInterfaceTest rtTest = new RunInterfaceTest();
创建Thread对象,然后把rtTest作为Thread的参数
Thread thread = new Thread(rtTest);
thread.start();
}
}
三, 线程的常用方法(▷):
3.1, sleep方法
- 格式: public static void sleep(long millis)
- 作用:
让正在执行的线程休眠(暂停执行)若干毫秒
,线程调用sleep()后会进入阻塞状态. - 注意:
使用sleep方法必须用try..catch处理
, 应对线程执行中断的情况. 发生异常的类型是
InterruptedException.
3.2, join方法
- 格式1: public final void join()
- 格式2: public final void join(long miles) -等待线程死亡millis毫秒
- 作用:
抢占cpu, 等待调用该方法的线程结束后才能执行
- 注意: 使用joint方法必须用try…catch处理,应对cpu被抢占的情况. 发生异常的类型是InterruptedException.
3.3, yield方法
- Thread的静态方法
- 格式: Thread.yield();
- 作用: 当前占用cpu运行的线程使用了yield方法后,会主动把cpu让出,而且与sleep不同的是,使用yield()不会阻塞该进程,只是把该进程转换为就绪状态.
- 当线程
调用yield()方法后
,只有与当前线程优先级相同或更高的线程才能获得执行
的机会.
四, 线程的优先级
- Java为线程类提供了十个优先级. 优先级可以用1-10的范围来表示,超过范围会被抛出异常,
主线程默认优先级为5
- 优先级常量:
MAX_PRIORITY 优先级为10
MIN_PRIORITY 优先级为1
NORM_PRIORITY 优先级为5 - 设置优先级(拿设置最高优先级为例)
xx.setPriority(Thead.MAXPRIORITY);
xx.setPriority(10); - 优先级相关的方法
public int getPriority() 获取线程优先级
public void setPriority(int newPriority) 设置优先级
五,线程的调度
- 常见的线程调度模型:
- 抢占性调度模型:
- 抢占式调度模型让线程去抢夺cpu时间片,线程的优先级越高,获得时间片的概率就越高.
- 均分式调度模型
- 平均分配时间片,每个线程占用的时间片长度一样.
Java 是抢占式调度模型!!!
java虚拟机采用抢占式调度模型
,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。一个线程会因为以下原因而放弃CPU。一, java虚拟机让当前线程暂时放弃CPU,转到就绪状态,使其它线程或者运行机会。二,当前线程因为某些原因而进入阻塞状态。三,线程结束运行。
六, 线程的安全问题:
- 什么时候数据在多线程并发的环境下存在安全性问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,
三个条件:
条件一:多线程并发
条件二:有数据共享
条件三:共享数据有修改的行为
满足以上三个条件之后,就会存在线程安全问题。
6.1, Java三种变量与线程安全
实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中
以上三大变量中:
局部变量永远都不会存在线程安全问题
。
因为局部变量永远都不会共享。(一个线程一个栈)
局部变量在栈中,所以局部变量永远不会共享。
实例变量和静态变量都可能存在
线程安全问题。
局部变量 + 常量 不会有线程安全问题
成员变量可能会有线程安全问题
6.2, 用同步去解决线程安全
- 前言: 线程安全问题实际上就是多个线程同时处理共享资源导致的,为了使得处理共享资源的代码一个时刻仅有一个线程访问,Java提供了同步机制.
- synchronized关键词用在:
成员方法
静态方法
语句块
线程同步,实际上就是线程不能并发,线程必须排队执行。
6.2.1, 同步代码块:
- 当多个进程使用同一个共享资源,可以将处理共享资源的代码放置在一个代码块中,并使用synchronized去修饰,成为同步代码块.
- 线程同步格式:
synchronized(lock){
操作共享资源的代码块
}
- 代码示例:
实现Runnable接口
public class RunInterfaceTest implements Runnable {
三窗口售票的实现
private int ticketNum = 10;
///生成锁对象
Object lock = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (lock) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.getMessage();
}
if (ticketNum > 0) {
System.out.println(Thread.currentThread().getName() + "卖出一张票, 此时余票: " + --ticketNum);
} else {
break;
}
}
}
}
}
测试类
public class runThreadTest {
public static void main(String[] args) {
RunInterfaceTest runInterfaceTest = new RunInterfaceTest();
for (int i = 1; i <= 3; i++) {
Thread th = new Thread(runInterfaceTest, "线程" + i);
th.start();
}
}
}
5.2.2, 同步方法:
- 同步代码块可以有效的解决线程的安全问题,当把共享资源的操作放在synchornized定义的区域内,便为这些操作加了同步锁. 在方法前面同样可以使用 synchronized关键字来修饰, 被修饰的方法为同步方法
//格式:
synchronized 返回值类型 方法名(参数列表)
- 示例代码:
///含tun()的线程类
public class SynMethod implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (ticketNum < 0)
break;
if (ticketNum > 0)
ticketSale();
}
}
private synchronized void ticketSasle() {
ticketNum--;
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticketNum + "张票");
}
///测试类
public class SynTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
SynMethod syn = new SynMethod();
for(int i=1; i<=3; i++) {
Thread th = new Thread(syn);
th.start();
}
}
}
- 同步代码块的锁是
自己定义的任意类型的对象
,而对于同步方法来说,它的锁就是当前调用该方法的对象
!也就是this指向的对象. 这样做的好处: 同步方法被所有线程共享,方法所在的对象相对所有线程是唯一的,从而保证了锁的唯一性 - 同步解决了多个线程访问共享数据时的数据安全问题,只要加上同一个锁,在同一时间内只能有一条线程执行,但是, 线程在执行时每次都要判断所的状态,消耗资源且效率低下.
- 死锁:
七, Java中线程的分类 (▷)
7.1, 定义
- Java中有两种线程, 用户线程和守护线程(后台线程),
- 举个栗子, main()就是用户线程, 而垃圾回收线程属于守护线程.
- 对Java程序来说, 只要还是有一个前台线程还在运行,这个线程就不会结束. 但是如果程序中只有后台线程运行,这个进程就会结束.
- 这里提到的前台和后台进程是个相对的概念,新创建的线程默认都是前台线程,如果某个线程在启动之前调用 setDaemon(true)语句,这个线程就会变为一个后台线程.
某个线程启动之前就是说,在start()方法之前调用setDaemon()
后台进程示例代码:
八, 多线程通信(待强化) (▷)
线程通信用到的方法:
wait() 中断方法的执行,使线程等待;
notify() 唤醒处于等待的某一个线程,使其结束等待
notifyAll() 唤醒所有等待的进程
- 生产者和消费者问题
示例代码:
///含
public class Queue {
private int n;
boolean flag=false;
public synchronized int get() {
if(!flag){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("消费:"+n);
flag=false;//消费完毕,容器中没有数据
notifyAll();
return n;
}
public synchronized void set(int n) {
if(flag){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("生产:"+n);
this.n = n;
flag=true;//生产完毕,容器中已经有数据
notifyAll();
}
}
consumer
public class Consumer implements Runnable{
Queue queue;
Consumer(Queue queue){
this.queue=queue;
}
@Override
public void run() {
while(true){
queue.get();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
///producer
public class Producer implements Runnable{
Queue queue;
Producer(Queue queue){
this.queue=queue;
}
@Override
public void run() {
int i=0;
while(true){
queue.set(i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
test类
public class Test {
public static void main(String[] args) {
Queue queue=new Queue();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示