线程的状态
- 新建:刚刚new出来的线程对象,还没start()
- 就绪:已调用start,但尚未运行,但正在积极竞争cpu
- 运行:正在运行
- 阻塞:不竞争cpu(即使cpu空闲),阻塞状态结束进入就绪状态
- 死亡:线程运行结束
线程的优先级
多个就绪态的线程,其中优先级较高的,竞争上cpu的概率较高。
优先级:1~10
默认5
Thread.MAX_PRIORITY = 10
Thread.MIN_PRIORITY = 1
Thread.NORM_PRIORITY = 5
守护线程
是为其他线程提供服务的线程,守护线程随着其它非守护线程结束而自然结束的线程。
(随JVM结束)
Thread thread_0 = new Thread(()->{
for(int i=0;;i++)//不写判断条件是因为他会随着非守护线程结束而结束。写了判断条件也只是在这个条件下结束了,不代表着这个守护线程结束。
System.out.println("这是一个守护线程");
});
thread0.setDaemon(true);//默认为false-->非守护线程。xxx--->线程名
线程安全问题
线程同步机制---->线程一个一个进行,不能并发。
拓展:
异步编程模型:
- 线程与线程各自执行各自的,实际上就是多线程并发
同步编程模型: - 线程1在执行的时候线程2得等线程一结束,效率较低,线程排队执行。
方法:
1.lock锁
2.使用线程安全的类
3.synchronized锁
4.volatile
synchronized的使用方式:
- 同步代码块,() 里的对象就是锁
- 同步实例方法(在实例方法上加synchronized)(等同于以this为锁,以整个方法体为同步代码块)
- 同步静态方法(在静态方法上加synchronized)(等同于以所属类的元类对象为锁,以整个方法体为同步代码块)
三大变量安全问题
局部变量:栈中,永远不会有安全问题
静态变量:在方法区中,方法区只有一个,所以会有线程安全问题
实例变量:在堆中,堆只有一个,所以会有线程安全问题’‘
实际开发解决线程安全问题
1.局部变量 代替 实例变量和静态变量
2.在不能使用局部变量的情况下,尝试建立多个对象,使线程的对象不共享
3.加锁,尽量不适用Synchronized锁,因为该锁效率低。
实现定时器
SpringTask就是java.util.Timer为基础的
class longTimerTask extends TimerTask {
//编写执行任务
@Override
public void run() {
System.out.println("执行的任务");
}
}
//使用定时器指定定时任务
public class TimerTaskExa {
public static void main(String[] args) throws ParseException, ParseException {
Timer timer = new Timer();//创建定时器对象
//Timer timer2 = new Timer(true);//守护线程的方式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstData = simpleDateFormat.parse("2023-5-15 18:00:00");
//第一个参数:要执行的定时器代码 第二个参数:第一次执行的时间 第三个参数:每隔多长时间执行
timer.schedule(new longTimerTask(),firstData,1000*10);
}
}
死锁
public class 死锁 {
private static Object object1=new Object();//锁1
private static Object object2=new Object();//锁2
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
System.out.println("等object1,等object2");
synchronized (object1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("object1,等object2");
synchronized (object2){
System.out.println("object1,object2");
}
}
});
Thread thread2 = new Thread(()->{
System.out.println("等object2,等object1");
synchronized (object2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("object2,等object1");
synchronized (object1){
System.out.println("object2,object1");
}
}
});
thread1.start();
thread2.start();
}
}
死锁的四个必要条件
-
互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待
-
请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放
-
不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)
-
循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
怎样避免死锁
1.加锁顺序一致
2.加锁超时时间
即线程请求资源有时间限制,一旦超出时间,就不请求了,而且释放自己的锁
ThreadLocal
ThreadLocal线程变量,其中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
public class 线程本地变量 {
private static ThreadLocal local = new ThreadLocal();
public static void f1(String str){
local.set(str);
}
public static void f2(){
System.out.println(Thread.currentThread().getName()+"..."+local.get());
}
//sleep的目的是为了验证在local里只会存和得到自己线程的有关信息
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
f1("thread1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
f2();
});
Thread thread2 = new Thread(()->{
f1("thread2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
f2();
});
thread1.start();
thread2.start();
}
}
//output:
//Thread-0...thread1
//Thread-1...thread2