java 线程基础
文章目录
1.线程相关概念
-
线程由进程创建 是进程的一个实体
-
一个进程可以拥有多个线程
-
单线程:同一个时刻 只允许执行一个线程
-
多线程:同一个时刻 可以执行多个线程
-
并发:在同一个时刻 多个任务交替执行 造成一种“貌似同时”的错觉 简而言之 单核CPU实现的多任务就是并发
另一种描述(出自计算机操作xi’t):并发是指两个或多个事件在同一时间间隔内发生
-
并行: 同一个时刻 多个任务同时执行 多个任务之间互不干扰的同时执行 例如:多核CPU可以实现并行
-
查看当前电脑CPU个数的Java程序
public class DemoTest { public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); int i = runtime.availableProcessors(); System.out.println("当前电脑CPU数量:"+i); } }
2.线程基本使用
-
创建线程的两种方式:
- 继承Thread类 重写run方法
-
当一个类(自己的类) 继承Thread类 该类就可以当作线程使用
-
并且我们要重写Thread类的run方法 写上自己的业务逻辑
-
run Thread类 实现了Runnable接口的run方法
-
样例代码:
public class DemoTest { public static void main(String[] args) throws InterruptedException { /*创建Myth对象 可以当作进程使用*/ Myth myThread = new Myth(); myThread.start();//启动线程 /*说明:当main线程启动一个子线程 Thread-0 主线程不会阻塞 会继续执行*/ System.out.println("主线程继续执行:"+Thread.currentThread().getName()); for (int i = 0; i < 60; i++) { System.out.println("主线程 i="+i); /*主线程休眠1s*/ Thread.sleep(1000); } } } class Myth extends Thread { int times=1; @Override public void run() { while (true) { /*创建一个线程 每隔1s 在控制台输出*/ System.out.println(times+"我是线程!!"+"线程名:"+Thread.currentThread().getName()); times++; /*设置程序休眠1s IDEA快捷键:选中该句 Ctrl+Alt+t*/ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } if(times==80){ break; } } } }
-
流程图如下:
-
当子线程Thread-0结束 整个进程结束
-
为什么要用start方法 不直接调用run方法呢?
- 由以下start方法的源码可知 其中调用了start0方法 而实现多线程效果的其实是start0方法 并不是run方法 所以直接调用run方法 会直到run方法全部运行完毕 才会接着运行下面的代码 故并不会看到多线程效果
- start0是本地方法 由JVM调用 底层由C/C++实现
- start方法调用start0方法后 该线程并不会立刻执行 而是将线程变成了可运行状态 具体什么时候执行 取决于CPU(你自己电脑的操作系统 不同操作系统不同) 由CPU统一调度
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
- 实现Runnable接口 重写run方法
- 该方法存在的原因:并不是所有类都可以继承Thread类的 由于Java是单继承(一个类最多只能继承一个父类) 所以当某个类 已经继承一个父类后 便不可再继承Thread类 来创建进程了 在这种情况下 就要通过实现Runnable接口来创建线程
- 样例代码:
package hspedu.TestZeta;
public class DemoTest {
public static void main(String[] args){
/*通过实现接口Runnable 来创建进程*/
Dog dog = new Dog();
/*这里还是要调用start方法 但是Runnable只有一个run()方法 怎么办?*/
/*创建Thread对象 把dog对象(实现Runnable) 放入Thread*/
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable{
int count=0;
@Override
public void run(){
while (true){
System.out.println("小狗汪汪叫..hi"+(++count)+Thread.currentThread().getName());
/*休眠1s*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count == 10) {
break;
}
}
}
}
3. 多线程实现
- 样例代码:
package hspedu.TestZeta;
/*多线程执行*/
public class DemoTest {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread = new Thread(t1);
Thread thread1 = new Thread(t2);
thread.start();
thread1.start();
}
}
class T1 implements Runnable {
int count = 0;
@Override
public void run() {
/*每隔1s 输出一次hello,world 输出10次*/
while (true) {
System.out.println("hello,world" + (++count));
try {
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
if(count==100){
break;
}
}
}
}
class T2 implements Runnable {
int count = 0;
@Override
public void run() {
/*每隔1s 输出一次hi 输出5次*/
while (true) {
System.out.println("hi" + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count==50){
break;
}
}
}
}
4.线程退出
-
线程完成任务后 会自动退出
-
还可以通过使用变量来控制run方法退出的方式停止线程 即通知方式
-
样例代码:
package hspedu.TestZeta;
/*启动一个线程t 要求在main线程中 去停止线程t*/
public class DemoTest {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
/*改变loop的值 就可以退出线程*/
/*让t退出run方法 从而中止 t线程-> 通知方式*/
/*先让主线程 休眠10s 再通知 t线程退出*/
System.out.println("主线程休眠10s");
Thread.sleep(10*1000);
/*通知 t线程退出*/
t.setLoop(false);
}
}
class T extends Thread{
int count=0;
/*变量控制线程退出:
* 1.设置一个控制变量*/
private boolean loop=true;
@Override
public void run(){
while (loop){
try {
Thread.sleep(1000);//休眠50ms
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("T 运行中..."+(++count));
}
}
/*要在main函数 调用set方法 去修改loop的值 就可以完成退出线程*/
public void setLoop(boolean loop) {
this.loop = loop;
}
}
5. 线程常用方法
-
1.setName 设置线程名称 使之与参数name相同
2.getName 返回该线程名称
3.start 使该线程开始执行 JVM底层调用该线程的start0方法
- start底层会创建新的线程 调用run run就是一个简单的方法调用 不会启动新线程
4.run 调用线程对象run方法
5.setPriority 更改线程的优先级
- 线程优先级的范围:MAX_PRIORITY:10 MIN_PRIORITY:1 NORM_PRIORITY:5
- 优先级高的线程被操作系统调度的优先级较高(操作系统对高优先级线程,调度更频繁)
- 但不能代表,通过设置优先级来确保高优先级的线程一定会先执行,只是会提高一定的优先级。
6.getPriority 获取线程的优先级
7.sleep 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) 需要抛出异常(用try-catch)
- sleep(long millis):接受一个以毫秒为单位的时间参数,表示线程要休眠的时间长度。
- sleep(long millis, int nanos):接受一个以毫秒为单位和一个以纳秒为单位的时间参数,表示线程要休眠的时间长度。
- 线程执行同步代码块或同步方法调用sleeo()方法时 只是暂停当前线程的执行 不会释放锁
8.interrupt 中断线程
- 只是中断线程 并没有真正结束线程 所以一般用于中断正在休眠线程
-
样例代码:
package hspedu.TestZeta;
/*启动一个线程t 要求在main线程中 去停止线程t*/
public class DemoTest {
public static void main(String[] args) throws InterruptedException {
/*测试方法*/
T t = new T();
t.setName("哈哈 ");
t.setPriority(Thread.MIN_PRIORITY);
t.start();
/*主线程输出5个hi 就中断子线程休眠*/
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi"+i);
}
System.out.println(t.getName()+"线程的优先级="+t.getPriority());
/*执行到这 中断t线程的休眠*/
t.interrupt();
}
}
class T extends Thread{
@Override
public void run(){
while (true) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "吃包子~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "休眠中~~~");
Thread.sleep(20000);
} catch (InterruptedException e) {
/*InterruptedException捕获一个中断异常*/
System.out.println(Thread.currentThread().getName() + "被 interrupt");
}
}
}
}
-
9.yield: 线程的礼让 让出CPU 让其他线程执行 但礼让的时间不确定 所以也不一定礼让成功
- yield() 方法用于提示线程调度器当前线程愿意放弃对 CPU 的使用,以便其他具有相同优先级的线程有机会执行。调用 yield() 方法会暂停当前正在执行的线程,并将执行机会交给其他具有相同优先级的线程。
10.join:线程的插队 插队的线程一旦插队成功 则肯定先执行完插入的线程所有任务
- join()方法的底层是利用wait()方法实现的
- join()是通过在内部使用synchronized + wait()方法来实现的,所以join()方法调用结束后,会释放锁
11.wait:wait() 方法是 Object 类的实例方法,需要在同步代码块或同步方法中调用,即在持有锁的情况下才能调用。该方法会使当前线程进入等待状态,释放对象锁资源,(会使当前线程让出持有的"this锁",允许其它线程参与竞争CPU执行权(this锁),而sleep的休眠过程中,当前线程不会让出持有锁!!!)等待其他线程调用相同对象的 notify() 或 notifyAll() 方法来唤醒。
12.notify/notifyAll:
- 线程的唤醒指的是从等待状态(如调用了wait()方法)中唤醒线程,使其继续执行;
- 在Java中,线程的唤醒方法有:
notify()方法:随机唤醒等待的某个线程;
notifyAll()方法:唤醒全部等待线程;
-
样例代码:
package hspedu.TestZeta;
/*启动一个线程t 要求在main线程中 去停止线程t*/
public class DemoTest {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for (int i = 1; i <= 20; ++i) {
Thread.sleep(1000);
System.out.println("主线程 吃了 " + i + " 包子");
if (i == 5) {
System.out.println("主线程让子线程 先吃");
/*
t2.join();//t2子线程 插队
*/
Thread.yield();//礼让 但不一定成功
System.out.println("子线程 吃完了 主线程 接着吃");
}
}
}
}
class T2 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 20; ++i) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程 吃了 " + i + " 包子");
}
}
}
6.守护线程
-
用户线程:也叫工作线程 当线程的任务执行完成或通知方式结束
-
守护线程:一般是为工作线程服务的 当所有的用户线程结束 守护线程自动结束
-
常见的守护线程:垃圾回收机制
-
设置守护线程的方式:在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程(样例代码的第9行)
-
当主线程结束时,守护线程也会自动终止,即使它还在执行。
-
样例代码 :
package hspedu.TestZeta;
/*把一个线程设置为守护线程*/
public class DemoTest {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
/*设置为守护进程*/
myDaemonThread.setDaemon(true);
/*进程开始*/
myDaemonThread.start();
/*如果我们希望当main线程结束后 子线程自动结束
* 只需将子线程设为守护线程即可*/
for (int i = 1; i < 11; i++) {/*main线程*/
System.out.println("宝强在辛苦工作"+i);
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread{
public void run(){
for(;;){//无限循环
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("马蓉和宋喆快乐的聊天 哈哈哈哈");
}
}
}
7.线程的生命周期
-
1.NEW:当一个线程的实例被创建即使用new关键字和Thread类或其子类创建一个线程对象后,此时该线程处于新建状态。处于新建状态的线程有自己的内存空间,但该线程并没有运行,此时线程还不是活着的。
2.RUNNABLE:在Java虚拟机中执行的线程处于此状态(可运行状态 ) 通过调用线程实例的start()方法来启动线程使线程进入就绪状态。处于就绪状态的线程已经具备了运行条件,但还没有被分配到CPU即不一定会被立即执行,此时处于线程就绪队列,等待系统为其分配CPU。
- Ready:就绪状态
- Running:运行状态
3.BLOCKED:通过调用join()、sleep()、wait()或者资源被暂用使线程处于阻塞状态。处于Blocking状态的线程仍然是活着的。
4.WAITING:正在等待另一个线程执行特定的动作的线程处于此状态
5.TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
6.TERMINATED:当一个线程的run()方法运行完毕或被中断或被异常退出,该线程到达死亡状态。
7.当线程启动后,它可以在Runnable、Blocked、Waiting和Timed Waiting这几个状态之间切换,直到最后变成Terminated状态,线程终止。
-
韩顺平 P592关于生命周期的图
8.线程同步机制 synchronized
-
线程同步:即当有一个线程对内存进行操作时 其他线程都不可以对这个内存地址进行操作 直到该线程完成操作 其他线程才能对该内存地址进行操作
-
同步一般时通过互斥锁实现的
-
同步代码块:
synchronized(对象){ //得到对象的锁 才能操作同步代码
//需要被同步代码;
}
- 同步方法(在同一个时刻 只能由一个线程来执行m方法):
public synchronized void m(String name){
//需要被同时的代码
}
9.互斥锁
-
引入对象互斥锁 来保证共享数据操作的完整性 通过控制多个线程对共享资源访问的机制 确保在任何时刻只有一个线程可以访问被保护的资源 从而避免多个线程同时修改共享数据 造成数据不一致或其他问题
-
关键词synchronized 来与对象的互斥锁联系 当某个对象用synchronized修饰时 表示该对象在任意时刻只能由一个线程访问
-
同步(加锁)的局限性:导致程序的执行效率降低
-
同步方法(非静态)的锁 可以是this 也可以是其他对象(要求是同一个对象)
-
同步方法(静态)的锁为当前类本身(当前类的类名.class)
-
注意事项和细节
- 同步方法如果没有使用static修饰(非静态方法) 默认锁的对象为this
- 如果方法使用static修饰(静态方法) 默认锁的对象:当前类名.class
- 多个线程的锁对象必须是同一个
10.线程的死锁
- 线程的死锁是指两个或多个线程在互相等待对方释放资源的情况下陷入僵局,无法继续向前执行的现象。这种情况发生时,每个线程都在等待另一个线程释放资源,而同时又不释放自己的资源,导致所有线程都无法继续执行下去,形成了死锁。
- 样例代码:
package hspedu.TestZeta;
/*模拟线程死锁*/
public class DemoTest {
public static void main(String[] args) throws InterruptedException {
/*模拟死锁现象*/
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B线程");
A.start();
B.start();
}
}
/*线程*/
class DeadLockDemo extends Thread{
static final Object o1=new Object();//保证多线程 共享一个对象 所以使用static
static final Object o2=new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run(){
/*分析:
* 1,如果flag为true 线程A 就会先持有 o1 对象锁 然后尝试去获取o2对象锁
* 2.如果线程A 得不到o2对象锁 就会Blocked
* 3.如果flag为false 线程B 就会先持有 o2 对象锁 然后尝试去获取o1对象锁
* 4.如果线程B 得不到o1对象锁 就会Blocked
* 故以上行为 很容易发生死锁*/
if(flag){
synchronized (o1){//对象互斥锁 以下是同步代码块
System.out.println(Thread.currentThread().getName()+" 进入1");
synchronized (o2){
System.out.println(Thread.currentThread().getName()+" 进入2");
}
}
}else {
synchronized (o2){
System.out.println(Thread.currentThread().getName()+" 进入3");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+" 进入4");
}
}
}
}
}
11.释放锁
- 以下操作会释放锁
- 当前线程的同步方法 同步代码块执行结束
- 当前线程在同步代码块 同步方法中遇到break return
- 当前线程在同步代码块 同步方法中出现了未处理的Error或Exception 导致异常结束
- 当前线程在同步代码块 同步方法中执行了线程对象的wait()方法 当前线程暂停 并释放锁
- 以下操作不会释放锁
- 线程执行同步代码块或同步方法调用sleeo()方法时 只是暂停当前线程的执行 不会释放锁
- 线程执行同步代码块时 其他线程调用了该线程的suspend()挂起方法将该线程挂起 该线程不会释放锁
- 应尽量**避免使用suspend()和resume()**来控制线程 方法不再推荐使用
zetazero 2024-4-30 02:04