java多线程(一):初步了解线程
一、线程生命周期
一个线程被实例化完成,到线程销毁的中间过程
1.新生态:New
一个线程对象被实例化完成,但是没有做任何操作
2.就绪态度:Ready
一个线程被开启,并且开始抢占CPU时间
3.运行态:Run
一个进程抢到的CPU时间片,并且开始执行线程中的逻辑
4.阻塞态:Interrupt
一个线程运行中,放弃了已经获取的CPU时间片,不再参与CPU时间片的抢占,此时线程处于挂起状态
5.死亡态:Dead
一个线程对象需要被销毁
二、简单创建线程的两种方式
1.继承Thread类来创建
步骤如下:
- 定义Thread类的子类,并重写该类的run()方法
- 创建Thread子类的实例、
- 调用线程的start()方法启动线程
/**
* 1.继承Thread类,自定义线程类
*/
class MyThread extends Thread {
/**
* 重写run方法,将并发任务写入
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是线程中run的方法,执行次数" + (i + 1));
}
}
/**
* 线程的命名
*/
public MyThread(String name) {
//在可以在实例化线程时向Thread类的构造方法传参
super(name);
//也可以调用set方法
this.setName(name);
}
}
2.实现Runnable接口创建
步骤如下:
- 定义Runnable接口的实现类,重写run()方法
- 创建Runnable实现类的实例,并在实例化Thread类时传入
- 调用线程对象的start()方法来启动线程
/**
* 2.通过runablele接口
* @return
*/
Thread getRunable() {
Runnable runnable = () -> {
for (int i = 0; i < 10; i++) {
System.out.println("我是线程中Runable的方法,执行次数" + (i + 1));
}
};
return new Thread(runnable);
}
三、Thread线程类常用方法
Thread类有以下方法:
-
String getName():返回该线程的名称。
-
void setName(String name):设置线程名称
-
int getPriority():获取线程的优先级。
-
void setPriority(int newPriority):修改线程的优先级。
-
boolean isDaemon():获取该线程是否为守护线程。
-
void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
-
static void sleep(long millis):线程阻塞
-
void interrupt():中断线程。
-
static void yield():暂停当前正在执行的线程对象,并执行其他线程。
-
void join():等待该线程终止。
-
void run():线程的执行体
-
void start() :启动线程
-
从Object类继承来的方法:void notify();void wait()
下面对常用方法做一下简略的演示:
1.sleep 阻塞
Thread.sleep(times)
使当前线程从Running状态放弃处理器进入Block状态,休眠times毫秒,再返回Runnable状态。
/**
* 1.线程阻塞
*/
void setSleect(){
new Thread(() -> {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
System.out.println("我是要阻塞的线程,执行次数" + (i + 1));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "sleepThread").start();
}
2.priority 优先级
setPriority(int newPriority)
按1-10为线程的时间片分配机会设置优先级,优先级越高,执行概率越高
按以下实例,最终结果是线程1比线程2更早循环完100次
/**
* 2.设置时间优先级
*/
void setPriority() {
Runnable runnable = () -> {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":执行第" + (i + 1) + "次");
}
};
Thread thread1 = new Thread(runnable, "线程1");
Thread thread2 = new Thread(runnable, "线程2");
//设置优先级
thread1.setPriority(10);
thread2.setPriority(1);
//启动
thread1.start();
thread2.start();
}
3.yield 线程礼让
Thread.yield()
让出当前线程对象分配到的cup时间片,执行其他线程。
/**
* 设置线程礼让
*/
void setThreadYield() {
Thread thread1 = new Thread(getRunable(10), "线程1");
Thread thread2 = new Thread(getRunable(35), "线程2");
//启动
thread1.start();
thread2.start();
}
Runnable getRunable(int yieldNum) {
return new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//设置在第几个数字的时候进行礼让
if (i == yieldNum) {
Thread.yield();
System.out.println("我让位!");
}
System.out.println(Thread.currentThread().getName() + ":执行第" + (i + 1) + "次");
}
}
};
}
3. join 等待线程
Thread.join()
让当前线程进入等待队列,带某线程执行完毕后当前线程再开始执行,可以理解为将两个线程的关系由并行变为串行,但是并不影响其他线程的并行执行
/**
* join
*/
void getJoin() {
Thread t1 = new Thread(() -> {
System.out.println("t1执行了!");
for (int i = 0; i < 10; i++) {
System.out.println("t1:" + i);
}
});
Thread t2 = new Thread(() -> {
System.out.println("t2等待t1执行完毕....");
try {
t1.join();
for (int i = 0; i < 10; i++) {
System.out.println("t2:" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2执行了!");
});
Thread t3 = new Thread(() -> {
System.out.println("t3不等待t1执行完毕....");
for (int i = 0; i < 10; i++) {
System.out.println("t3:" + i);
}
System.out.println("t3执行了!");
});
t1.start();
t3.start();
t2.start();
}
四、创建几个线程合适?
对于CPU密集型的计算场景,理论上线程的数量=CPU核数就是最合适的。不过在工程上,线程的数量一般会设置为CPU核数+1,这样的话,当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上,从而保证CPU的利用率
对于I/O密集型的计算场景,最佳线程数=1+(I/O耗时/CPU耗时)
针对多核CPU,我目前见过两种比较合理的公式:
最佳线程数=CPU核数×[1+(I/O耗时/CPU耗时)]
线程数=CPU核数×目标CPU利用率×(1+平均等待时间/平均工作时间)
参考: