多线程是什么?
概念: 一个java程序是由多个线程共同组成的,我们在学习多线程之前,主方法就是一个线程(main线程),而垃圾回收机制实质上也是一个线程,我们在写程序的时候垃圾回收机制在后台一直运行着,和主线程并存。多线程是将任务分成多个子任务一同运行,提高了程序的效率。
线程的执行权: 多个线程会一同抢夺CUP的时间片,抢到时间片就会执行任务,抢到时间片的概率大小和线程优先级有关。
线程的状态分为就绪状态,运行状态,阻塞状态:
就绪状态: 和其它线程一起抢夺CUP时间片,抢到了CPU的时间片就执行任务。
运行状态: 抢到时间片之后会进入运行状态,开始执行任务,时间片用完之后会再次回到就绪状态再次抢夺CPU的时间片,再次执行任务时会接着上次的任务继续执行。
阻塞状态: 例如在线程执行的时候接受用户的输入,这个线程就会暂停执行,等待用户输入后会回到就绪状态再次抢夺CPU时间片。
线程的生命周期: 从就绪状态开始一直到运行状态运行完任务,线程就死亡了。
多线程并发: 就是多个线程抢夺时间片执行各自的任务,提高了程序的效率,但这可能是存在线程安全问题。多线程并发存在两种机制:同步机制和异步机制(下一篇博客关于线程安全的问题的时候会讲到)
好了接下来都是干货了,先来一波多线程常用的方法来压压惊。
多线程的常用方法
1. currentThread()方法:
作用:表示当前线程,常与getName()方法连用,Thread.currentThread().getName();表示当前线程的名字
语法:线程对象(引用).currentThread();或者 Thread.currentThread();
2. getName()方法:
作用:获取线程名字,返回值为String类型。
语法:线程对象(引用).getName()
3. setName()方法:
作用:修改线程名字,无返回值
语法:线程对象(引用).setName(“线程名字”);
4. setDaemon()方法:
作用:传入参数true,将普通线程变为守护线程,无返回值。
语法:线程对象(引用).setDaemon(true);
5. setPriority()方法:
作用:修改线程优先级,传入一个整型数字(1到10以内)若不是1到10以内会发生异常,无返回值。
语法:线程对象(引用).setPriority(“整型数值”);
6. start()方法:
作用:启动线程,会自动调用run()方法(实现Callbale接口的线程除外)
语法:线程对象(引用).start();
7. join()方法:
作用:将一个线程加入到另一个线程中,当前线程会进入阻塞状态(直到加入的线程运行结束,当前线程才会继续运行),也可以理解为将两个线程合并。
语法:线程对象(引用).join();
8. stop()方法:
作用:强行使线程结束(不安全,容易造成数据丢失等,让线程停止有别的办法)。
语法:线程对象(引用).stop();
9. sleep()方法:
作用:让当前线程睡眠,传入一个整型参数,睡眠时间与传入的参数有关,参数单位为毫秒。
语法:Thread.sleep(整型参数);或者线程对象(引用).sleep(整型参数);
注意:sleep()方法有编译时异常需要处理。(关于异常可以看我异常概述的博客)。
10. schedule()方法:(这是定时器常用的方法,在此篇博客后面会对定时器进行讲解。)
作用:设置某个时间开始执行某个任务,可以使其每隔特定时间执行一次某个任务(常用于数据的备份)
语法:Timer类对象(引用).schedule(参数1,参数2,参数3);
第一个参数:需要将继承TimerTask类的对象传入,是执行的任务(也就是run()方法中所写的内容)
第二个参数:是开始执行的时间
第三个参数:每隔多久执行一次run()方法中的内容(单位是毫秒)
11. wait()方法:
作用:使在对象上进行的线程进入等待(也就是暂停线程的执行)
语法:对象(引用).wait();
注意:wait()方法并不是Thread类中的方法,是Object类中的方法,所有的java对象都有此方法。(常与线程安全问题有关,下一篇博客我将重点对此进行讲解)
12. notify()和notifyAll()方法:
作用:notify():唤醒在对象上等待的线程(也就是让暂停的线程开始执行),notifyAll():唤醒在对象上等待的所有线程(也就是让暂停的所有线程开始执行)
语法:对象(引用).notify(); 对象(引用).notifyAll();
注意:notify()方法和notifyAll()方法并不是Thread类中的方法,是Object类中的方法,所以的java对象都有此方法(常与线程安全问题有关,下一篇博客我将重点对此进行讲解)
线程优先级:
MIN_PRIORITY:最低优先级,值为1。
MAX_PRIORITY:最高优先级,值为10。
NORM_PRIORITY:默认优先级,值为5。
1.线程优先级为1到10,数字越大,优先级越高,默认线程优先级为5。
2.优先级越高表示抢到执行权的概率越大,优先级为最高优先级时也不并不代表一定会抢到执行权,只是概率大,同理,优先级为最低优先级也不代表一定抢不到执行权。
3.可以调用setPriority()方法对优先级进行修改,也可以调用getPriority()方法获取线程的优先级。
第一种实现线程方法:继承Thread类
步骤:
1. 继承Thread类重写run()方法
2. 创建Thread类型的重写类的对象(多态)
请看代码。
public class PracticeThreadWay01 {
public static void main(String[] args){
Thread t=new Thread1();
t.setName("T线程");
t.start();
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"线程执行了!--->"+i);
}
}
}
class Thread1 extends Thread{
public void run(){
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"执行了!--->"+i);
}
}
}
第二种实现线程方法:实现Runnable接口
步骤:
1. 实现Runnable接口,重写run()方法。
2. 创建Thread对象,将实现Runnable接口的类的对象传入。
请看代码。
public class PracticeThreadWay02 {
public static void main(String[] args) {
Thread t=new Thread(new Thread2());
t.setName("T线程");
t.start();
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"线程启动了!--->"+i);
}
}
}
class Thread2 implements Runnable{
public void run(){
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"启动了--->"+i);
}
}
}
也可以采用匿名内部类的方式实现线程
步骤:
1. 创建Thread对象,在创建对象的时候new Runnable并且实现run()方法。
请看代码。
public class PracticeThreadWay04 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable(){
public void run(){
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"线程启动了!--->"+i);
}
}
//将线程名字初始化
},"T线程");
//启动线程
t.start();
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"线程启动了!--->"+i);
}
}
}
第三种实现线程的方法:实现Callable接口
步骤:
1. 实现Callable接口,重写public Object call()方法,返回值为Object。
2. 创建FutureTask对象,并且将实现Callable接口的类传入。
3. 创建Thread对象,将FutureTask对象传入。
优点: 这种实现线程的方式有返回值,可以通过接收返回值来判断线程是否结束。
缺点: 判断线程是否结束的时候会让其它线程进入阻塞状态,必须得到返回值结果才能进行后续程序(也就是说等待call()方法的结束),会使程序效率降低。
请看代码。
public class PracticeThreadWay03 {
public static void main(String[] args) throws Exception {
FutureTask task=new FutureTask(new Thread3());
Thread t=new Thread(task);
t.setName("T线程");
t.start();
for (int i = 0; i <20 ; i++) {
//main线程
System.out.println(Thread.currentThread().getName()+"启动");
}
//判断T线程是否结束,必须等待get()方法返回才可以进行后面的程序,也就是说这时候main线程进入了阻塞状态
//"over".equals()的写法可以避免空指针异常的出现(算是一个小细节)
if("over".equals(task.get())){
System.out.println(t.getName()+"结束了");
System.out.println(Thread.currentThread().getName()+"还在进行中");
}
//让主线程睡眠2秒
Thread.sleep(2000);
}
}
class Thread3 implements Callable {
public Object call(){
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"启动了!");
}
return "over";
}
}
守护线程的实现
1. 守护线程概述: 守护线程是一种非用户线程(例如垃圾回收机制就是守护线程),守护线程在用户线程结束后会自动结束,当程序全是守护线程的时候,程序会自动结束。
2. 用户线程和守护线程的区别:
(1).主线程结束后用户线程还会继续运行,JVM存活(只要还有一个用户线程没有结束,程序就不会结束,JVM就一直在运行)
(2).如果用户线程结束了,那么JVM结束,守护线程会自动结束(所有的线程都会结束)。
3. 设置守护线程的办法: 调用setDaemon()方法,传入参数为true时就代表将普通线程(也就是用户线程)设置为了守护线程,传入参数为false时代表设置为用户线程。
4.实现守护线程:
步骤:
(1)创建一个普通线程(线程的run方法为死循环)
(2)调用setDaemon()方法,传入参数true将其修改为守护线程(设置为守护线程的方法,定时器除外)
请看代码。
我这里是采用匿名内部类的方式,也可以采用其它创建线程的方式都可以。
public class PracticeProtectedThread {
public static void main(String[] args) {
Thread t=new Thread(new Runnable(){
public void run(){
while(true) {
System.out.println(Thread.currentThread().getName() + "启动了!--->");
}
}
//设置线程名字
},"守护线程");
//将此线程变为守护线程
t.setDaemon(true);
t.start();
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"线程*****************************************"+i);
}
}
}
定时器的实现
1.定时器概述: 设置特定时间开始执行某个任务,其每隔特定时间执行一次某个任务(常用于数据的备份)
2.定时器的实现步骤:
(1)创建Timer类的对象,可传入参数true(代表将此定时器修改为守护线程),也可以不设置为守护线程。
(2)创建一个类继承TimerTask,并且重写run()方法。
(3)创建一个日期时间作为任务开始的执行时间。
(4)调用Timer对象的schedule()方法,schedule()方法一共有三个参数,schedule()方法中的参数说明如下。
第一个参数:需要将继承TimerTask类的对象传入,是执行的任务(也就是run()方法中所写的内容)
第二个参数:是开始执行的时间(例如:2020-4-16 08:20:00)
第三个参数:每隔多久执行一次run()方法中的内容(单位是毫秒)
请看代码。
public class PracticeTimerThread {
public static void main(String[] args) throws Exception {
Timer timer=new Timer("定时器线程",true);
//创建日期格式
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//将一定格式的日期转化为时间对象
//注意一定要与创建的日期格式一样
Date time=sdf.parse("2020-4-16 22:36:00");
//设置定时任务,每隔5秒执行一次
timer.schedule(new TimerThread(),time,1000*5);
//让主线程睡眠20秒
Thread.sleep(1000*20);
}
}
class TimerThread extends TimerTask {
public void run(){
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time=sdf.format(new Date());
System.out.println(time+" "+Thread.currentThread().getName()+"将数据备份了");
}
}
这次的博客就先到这里啦,下篇博客我将讲述线程安全问题以及生产者消费者模式。
有用就好,喜欢就点赞,持续关注,精彩不断!
码字不易,不要白嫖,觉得有用的,可以给我点个赞。感谢!
因技术能力有限,如文中有不合理的地方,希望各位大佬指出,在下方评论区留言,谢谢,希望大家一起进步,一起成长。
如需转载请注明来源,谢谢!