Java线程
`
文章目录
- Java线程
- 一、线程创建
- 二、线程运行
- 三、线程运行
- 四、主线程和守护线程
- 五、线程的五种状态
- 六、线程的六种状态
- 七、烧水泡茶案例
一、线程创建
创建线程方法一:
Thread重写run方法
@Slf4j(topic = "c.MyTest1")
public class MyTest1 {
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run() {
log.info("好人");
}
};
t.setName("线程1");
t.start();
log.info("main测试");
}
}
创建方法2:
直接写一个Runable传给线程Thread
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Runnable r = () -> {log.debug("running");};
Thread t = new Thread(r, "t2");
t.start();
}
}
Thread和Runable的区别
Thread实现了Runable方法,也就是说Thread可以覆盖Runable的任务方法执行run。但是也可以通过传入任务来执行对应的Runable方法(如下面代码所示)。
@Override
public void run() {
if (target != null) {
target.run();
}
}
创建方法3:
FutureTask实现了Runnable和Future,Future主要是用来返回值的。其实相当于也是一个任务类但是需要实现Callable实现类对象传入到task上。并且执行之后才能够使用task的get来获取数据,如果没有执行线程那么get就会阻塞
@Slf4j(topic = "c.MyTest2")
public class MyTest2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task=new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("你好");
return 4;
}
});
Thread t=new Thread(task,"t1");
t.start();
log.debug("{}",task.get());
}
}
总结:更推荐方法2,原因是Runable就是任务,把任务和线程分开才能更好的分配任务。
二、线程运行
命令
jps:查看java进程
tasklist:windos查看所有进程
taskkill /F /PID XXX:杀死某个进程
三、线程运行
线程的栈
每次开启一个线程都会产生一个线程的栈给线程使用,实际上就是一开始分配的虚拟机栈。
每个栈都有多个栈帧
线程只能有一个活动栈帧
线程执行的过程
先分配栈给线程,线程调用main方法,在栈分配一个栈帧给main方法,栈帧保存锁记录、局部变量表、操作数栈、返回地址(返回到原来的栈帧方法的下一条指令)
线程上下文切换
导致上下文切换的条件:
时间片用完
优先级
垃圾回收
自己调用sleep、wait等
线程的状态包括:
程序计数器,记录执行到什么位置
虚拟机栈,包括所有栈帧信息
常见方法
start
线程进入就绪状态,等待调度器调用。这里相当于是开启了一个新的线程
run
执行Runnable里面的方法。这里并没有开启线程,只是通过本线程执行代码
如果开启了两次start就会出现线程状态异常的问题IlegalThreadStateException
线程的两个状态
NEW
start之后就是Runnable等待调度
sleep
线程睡眠,并把状态改为Timed Waiting,被打断的时候会抛出异常InterruptedException。可以通过TimeUnit.SECONDS.sleep来规定睡眠时间的单位。睡眠可以使用在while循环自转的地方,如果长时间自转就会消耗CPU的使用时间,其他线程无法使用
interrupt
唤醒线程,如果线程处于睡眠状态那么就会抛出异常
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("enter sleep...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("wake up...");
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
}
yield
其实就是把线程状态从Running转变到Runnable暂时让出cpu,重新去竞争
setPriority
设置优先级是给调度器进行提示,先执行这个线程,但是仍然没有办法控制线程。
join
join实际上就是卡点,就是一定要等待调用join的线程完成后才能执行下面的代码
@Slf4j(topic = "c.Test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
同步应用案例
多线程的join只需要等待最长的那个线程就可以了,因为它们是并行执行的。
test3案例,实际上就是限时等待,如果超过时间那么就不等了。如果没有超过时间,那么结束join还是以处理完线程的任务时间为主,而不是最大的等待时间
@Slf4j(topic = "c.TestJoin")
public class TestJoin {
static int r = 0;
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(2);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 线程执行结束会导致 join 结束
log.debug("join begin");
t1.join(3000);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
});
t1.start();
t2.start();
long start = System.currentTimeMillis();
log.debug("join begin");
t1.join();
log.debug("t1 join end");
t2.join();
log.debug("t2 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
});
t1.start();
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
interrupt
打断阻塞
打断sleep和wait。打断后会抛出异常到那时不会给它打上打断标记。而且这里需要给主线程睡眠一会,不然t1线程还没睡眠,主线程就已经调用打断,那么这个时候的打断是打断t1,并且加上打断标记,但是打断睡眠并不会有打断标记
@Slf4j(topic = "c.Test11")
public class Test11 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("sleep...");
try {
Thread.sleep(5000); // wait, join
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
log.debug("打断标记:{}", t1.isInterrupted());
}
}
打断正常
当我们打断的事正常的线程(没在sleep或wait),那么就会给这个线程加上打断标记。但是要不要打断取决于被打断线程的意愿,其它线程只能给一个通知。
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted){
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
}
LockSupport
其实就是一个锁的支持类,它的park方法可以模拟sleep把线程进行阻塞,但是需要标记是false的时候。如果打断标记是true那么就无法使用。但是这个地方可以使用Thread.interrupted来获取打断标记状态和消除标记。
public static void test5() {
Thread t1 = new Thread(()->{
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.interrupted());
LockSupport.park();
log.debug("unpark...");
},"t1");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
}
四、主线程和守护线程
守护线程其实就是Daemon。也就是在其它非守护线程运行完之后,无论守护线程是否还有任务需要执行都会强制停止。
垃圾回收器是守护线程
tomcat的Acceptor和poller
@Slf4j(topic = "c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
}
log.debug("结束");
}, "t1");
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.debug("结束");
}
}
五、线程的五种状态
1、初始:new线程的时候
2、可运行:执行了start
3、运行:线程可以使用cpu的时候
4、阻塞:不能被cpu调度器使用的时候
5、终止:线程结束的时候
六、线程的六种状态
NEW:初始化
RUNNABLE:包括了运行、可运行和阻塞,通常表示正在运行
WAITING:没有时间限制的等待
TIMED_WAITING:有时间限制的等待
BLOCKED:阻塞
TERMINATED:终止
七、烧水泡茶案例
join思路
其实就是老王洗烧壶和烧水,小王洗茶壶、茶杯、茶叶,等待烧水完成之后泡茶。可以用join来等待t1处理。
Slf4j(topic = "c.Test16")
public class Test16 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("洗茶壶");
Sleeper.sleep(1);
log.debug("烧水");
Sleeper.sleep(5);
}, "老王");
Thread t2=new Thread(()->{
log.debug("洗茶壶");
Sleeper.sleep(1);
log.debug("洗茶叶");
Sleeper.sleep(2);
log.debug("洗茶杯");
Sleeper.sleep(1);
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("泡茶");
},"小王");
t1.start();
t2.start();
}
}