20220802 第六组 张嘉源 学习笔记
多线程
一个进程包含一个或者多个线程,一个线程不能独立的存在,它必须是进程的一部分
线程的生命周期
从产生到死亡的过程
NEW:这个状态主要是线程未被start()调用执行
RUNNABLE:线程正在JVM中被执行,等待来自操作系统的调度
BLOCKED:阻塞。因为某些原因不能立即执行需要挂起等待。
WAITING:无限期等待。Object类。如果没有唤醒,则一直等待。
TIMED_WAITING:有限期等待,线程等待一个指定的时间
TERMINATED:终止线程的状态,线程已经执行完毕。
线程的优先级
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
创建线程
1.继承Thread类
创建一个新的类,该类继承Thread类,然后创建一个该类的实例
继承类必须重写run()方法,必须调用start()方法执行
class MyThread extends Thread {
@Override
public void run() {
System.out.println(2);
}
}
public class Ch01 {
public static void main(String[] args) {
System.out.println(1);
MyThread myThread = new MyThread();
myThread.start();
System.out.println(3);
System.out.println(4);
}
}
2.实现Runnable接口
class MyThread implements Runnable{
// 重写run方法
@Override
public void run() {
System.out.println(2);
}
}
public class Ch01 {
public static void main(String[] args) {
System.out.println(1);
MyThread myThread=new MyThread();
// 多态
Thread t=new Thread(myThread);
t.start();
System.out.println(3);
System.out.println(4);
// 输出顺序为:1342
}
}
3.通过Callable和Future创建线程
创建Callable接口的实现类,并实现call()方法,该call()方法有返回值
创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 实现Callable接口
class MyThread3 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(2);
return "call方法的返回值";
}
}
public class Ch04 {
public static void main(String[] args) {
System.out.println(1);
// Callable-->FutureTask-->RunnableFuture-->Runnable-->Thread
FutureTask<String> futureTask = new FutureTask<>(new MyThread3());
new Thread(futureTask).start();
System.out.println(3);
System.out.println(4);
}
}
常用方法
sleep()方法
使当前线程睡眠一会,让其他线程有机会继续执行,让线程从运行状态变为阻塞状态,方法调用结束后,线程从阻塞状态变为可执行状态,以毫秒为单位,需要捕捉异常
join()方法
把指定的线程加入到当前线程,可以将两个交替执行线程合并为顺序执行的线程,比如在主线程中调用了线程A的join()方法,线程A执行完毕后,才会继续执行线程B
线程同步
java允许多线程并发控制,当锁哥线程同时操作一个可共享的资源变量时,会导致数据不准确,相互之间产生冲突,因此加锁避免在该线程没有完成操作之前被其他线程调用。
线程安全
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。
1.数据不可变:java中一切不可变的对象一定是线程安全的
对象的方法的实现方法的调用者,不需要再进行任何的线程安全的保障措施
比如:final关键字修饰的基本数据类型,字符串
只要一个不可变的对象被正确的创建出来,那外部的可见状态永远都不会改变
2.互斥同步:加锁,悲观锁
3.非阻塞同步:无锁编程,自旋
4.无同步方案:多个线程需要共享数据,但是这些数据又可以在单独的线程中计算,得出结果
可以把共享数据的可见范围限制在一个线程之内,这样就无需同步,把共享的数据拿过来
你用你的,我用我的,从而保证线程安全
Synchronized关键字
控制线程同步,确保数据的完整性,一般加在方法上
public synchronized void save(){}
守护线程
java中提供两种类型的线程:
用户线程:我们平常创建的普通线程
守护线程:用来服务用户线程,我么也可以手动创建一个守护线程
当存在任意一个用户线程的时候,JVM就不会退出
在调用start()方法前,调用setDaemon(true)
把该线程标记为守护线程。
如何检查一个线程是守护线程还是用户线程:使用isDaemon()
方法。
- thread.setDaemon(true) 必须在 thread.start() 之前设置,否则会抛出
IllegalThreadStateException
异常。 - 在Daemon线程中产生的新线程也是Daemon的。
JVM 中的垃圾回收线程就是典型的守护线程