文章目录
多线程
什么是进程?线程?
进程是一个应用程序
线程是进程的一个执行单元(执行场景)
一个进程可以启动多个线程
进程A和进程B的内存独立不共享
线程A和线程B,在Java语言中,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈
启动10个线程,就有10个栈空间,每个栈与每个栈之间互不干扰,各自执行各自的,这就是多线程并发
多线程机制,目的就是为了提高程序处理的效率
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束
main方法结束了,只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈,还在执行
对于单核的CPU来说,不能做到多线程并行,但是能做到多线程并发,给人一种并行的感觉
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
实现线程的方式
编写一个类,继承java.lang.Thread,重写run方法,new实例,调start
启动线程:调用线程的start方法
start方法的作用是:在JVM中开辟一个栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了
启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部
run和main方法是平级的
如果直接调用myThread.run()
会有什么区别?
如果直接调用run方法,不会开辟新的栈空间,所以等于还是在一个线程里面,不能并发
编写一个类,实现java.lang.Runnable接口,实现run方法
注意,现在这并不是一个线程类,只是一个可运行的类,还不是一个线程
怎么让它编程线程?
Thread有一个构造方法,参数就是一个Runnable对象
现在才是
使用匿名内部类
两种方法对比
第二种方式,实现接口比较常用,因为要面向接口编程
实现Callable接口
这种方式实现的线程可以获取线程的返回值
思考:get()方法会不会让当前进程阻塞?
会的,必须等待get()方法结束,因为get方法是为了拿另一个线程的执行结果,而另一个线程执行是需要时间的
关于Object类中的wait和notify方法
线程生命周期
新建状态 -> 就绪状态 <->运行状态 ->死亡状态
遇到阻塞事件(接受键盘输入、sleep方法等)进入阻塞状态,阻塞状态的线程会放弃之前占有的时间片
当阻塞解除,会回到就绪状态,抢夺cpu时间片
- 刚new出来的线程对象是新建状态
- 调用start方法进入就绪状态
- 就绪状态又叫可运行状态,表示当前线程具有抢夺cpu时间片的权利(cpu时间片就是执行权)
- 当一个线程抢夺到cpu时间片之后,就开始执行run方法
- run方法的开始执行标志着线程进入运行状态
获取线程对象
获取、设置线程的名字
默认是Tread-0、Thread-1递增
获取当前线程对象
该静态方法的返回值就是当前线程
static Thread currentThread()
Thread currentThread = Thread.currentThread();
sleep方法
static void sleep(llong millis)
静态方法
参数是毫秒
作用:让当前进程进入休眠, 进入阻塞状态,放弃占有cpu时间片,让给其它线程使用
Thread.sleep() 方法,可以做到:间隔特定的时间,去执行特定的方法,隔多久执行一次
一个面试题:
线程t是否会进入休眠状态
答案是:不会
因为sleep方法跟对象没关系
sleep方法是个静态方法!!
看上去调的是t1的sleep
其实调用的是Thread的静态方法sleep
怎么叫醒正在睡眠的进程
这里的sleep异常,只能try catch,不能抛出,这是为什么?
因为子类重写父类的方法,子类方法不能比父类方法抛出更宽泛的异常
而父类方法的run里面没有抛出异常,所以子类方法也不能抛异常
但是在run里面调用其它方法,其它方法可以throws,只不过要在方法里try catch
那如何叫醒一个正在睡眠的线程?
注意:不是终断线程的执行,而是中止线程的睡眠
方式一:异常处理机制
这种终端睡眠的方式依靠了java的异常处理机制
线程会进入到catch里面,try里面的结束
执行的话,会报异常,但还会继续执行下去,打印t—>end
终止线程的执行
方式一:结束进程: stop (过时)
不是终止睡眠,是直接结束掉线程
缺点:容易丢失数据。因为这种方式是直接将线程杀死了
线程没有保存的数据会丢失
打一个布尔标记
线程调度
java中提供的关于线程调度的实例方法
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程的优先级
static void yield() 静态方法,暂停当前正在执行的方法,不是阻塞方法,让当前线程让位,让运行状态回到就绪状态
void join() 合并线程
yield() 静态方法,暂停当前正在执行的方法,不是阻塞方法,让当前线程让位,让运行状态回到就绪状态。回到了就绪状态之后,还有可能会抢到。
最低优先级是1
默认优先级是5
最高优先级是10
线程优先级
最低优先级是1
默认优先级是5
最高优先级是10
yield示例
join示例
join合并线程并不是意味着只有一个栈了,不是栈合并,是栈协调
多线程并发环境下数据的安全问题
什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
怎么解决线程安全问题?
线程排队执行,不能并发,用排队执行解决线程安全问题,这种方式叫做线程同步机制,会牺牲一部分效率
同步编程模型:
线程之间发生了等待关系,线程排队执行,效率较低
异步编程模型:
线程各自之间谁也不用等谁,效率较高
线程同步机制的语法:
synchronized(共享对象(会被加上锁)){
//线程同步代码块
}
或者在实例方法上加synchronized
但是synchrnized出现在实例方法上锁的一定是this,而且同步的一定是整个方法体,效率降低
所以这种方式不灵活
但是优点是代码少了
下面的也可以,因为"abc"在字符串常量池中,会被加一个锁
注意,锁池不是一种状态,不属于状态模型,可以理解为一种阻塞
常量没有线程安全问题:因为常量不会被修改
如果使用局部变量的话,建议使用StringBuilder,因为不存在线程安全问题
面试题:
答案是不需要,因为doOhter根本就没加锁,虽然synchronized锁的是对象,但是只锁了doSome方法
如果MyClass编程这样,问,doOhter要不要等待doSome
答案是要
因为两个方法都在争夺this对象锁
如果main方法变成了这样,还要不要等待?
答案是不要,因为抢的不是同一个对象的this对象锁,有两个对象,两把锁
现在,如果MyClass变成了现在这样,问,doOther要不要等doSome?
答案是要,因为synchronized出现在了类方法上,两个方法现在抢的是类锁,不管创建了几个对象,都只有一个类锁
死锁
synchronized 在开发中最好不要嵌套使用,可能导致死锁
守护线程
java语言中的线程可以分为用户线程和守护线程(后台线程)
比如垃圾回收线程就是个守护线程
所有的用户线程结束了之后,守护线程自动结束
守护线程一般是一个死循环
守护线程用在什么地方?
- 比如每天00:00的时候系统数据自动备份
定时器
间隔特定的时间,执行特定的程序
实现方式:
SpringTask底层的原理也是Timer
注意这是一个抽象类,需要被继承,然后实现run方法
wait 和 notify
这两个方法是Object类中自带的
wait方法作用:
Object o = new Object();
o.wait();
表示:让正在o对象上活动的线程进入等待状态,无期限等待
notify方法作用
Object o = new Object();
o.notify();
o.notifyAll();
表示唤醒正在o对象上等待的线程
public class CusProducer {
public static void main(String[] args) {
int[] num = new int[]{1};
Thread t1 = new Thread(new SingleNum(num));
Thread t2 = new Thread(new DoubleNum(num));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class SingleNum implements Runnable{
int[] num;
public SingleNum(int[] num){
this.num = num;
}
@Override
public void run() {
while(true){
synchronized (num){
if(num[0] % 2 == 0) {
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "--> " + num[0]);
num[0]++;
num.notifyAll();
}
}
}
}
class DoubleNum implements Runnable{
int[] num;
public DoubleNum(int[] num){
this.num = num;
}
@Override
public void run() {
while(true){
synchronized (num){
if(num[0] % 2 == 1) {
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "--> " + num[0]);
num[0]++;
if(num[0] > 100)break;
num.notifyAll();
}
}
}
}