这篇博客总结了对线程核心api以及相关概念的学习,黑体字可以理解为重点,其他的都是我对它的理解
个人认为这些是学习java多线程的基础,不理解熟悉这些,后面的也不可能学好滴
目录
1.什么是线程以及优点
二,多线程如何使用
三,线程安全问题,
四,synchronized执行过程叙述
五,几个API:
六,停止线程,暂停线程
七,线程的优先级
八,守护线程
一,首先搞清楚什么是线程以及他的优点,我觉得一句话就就可以说清楚,线程就是一个进程的许多子任务。就比如你打开浏览器之后可能即浏览网页又在下载东西,你虽然你好像是同时做了两件事情,但其实是\cpu不停的在两个任务之间切换,只是切换的速度很快,感觉上是同时运行的,另外,线程是异步滴。
理解这个概念之后,来看线程的优点,都说多线程能够让任务的执行速度变快,我觉得这句话不完全准确,并且也只是其中的一点。
首先多线程提交执行速度是建立在一个基础上的,多个任务的时间执行时间是不等的,什么意思呢?比如你现在有两个任务,任务一用时10秒,任务二用时1秒,如果你顺序执行这两个任务,那么任务一需要10秒,任务二执行了1秒,但是任务二的等待时间还有10秒,所以看作是执行了11秒,这总共就是21秒的执行时间,但是如果你使用多线程呢?可能是这样子的,先执行任务一1秒,然后切换到任务二,执行任务2一秒,之后再切换回任务一,这样子的话,很明显任务二的等待时间缩短,执行时间也就更短了,相应的也就是我们的执行速度变快了。当然啦只是我自己的理解,如果你执行时间相等的话,其实切换来切换去并没有提交效率,但是这时候还是要使用多线程,为撒子?
因为多线程的另一个优点就是能够同时运行多个任务,总不可能让浏览器每次只能打开一个窗口吧,很简单,就这两个优点。
二,多线程如何使用
1.使用多线程,两种办法,很简单,第一继承Thread类,第二种实现Runnable接口,这两个种其实就是java不支持多继承,使用前者方法的话,你就不能继承其他类了,但是实现Runnable的话还是可以的,并且你也可以实现其他的接口,更为灵活一些,本质上是没有区别的,你打开Thread的源码就会发现他也是实现了Runnable接口的,另外要注意实现Runnable接口的话,一般使用时候都是实例化一个该类,用它来初始化一个Thread类,start这个start类。如下:
MyRunnable m = new MyRunnable (); //实现了Runnable接口 Thread m1 = new Thread(m); m1.setName("我的线程"); m1.start();
2.继承之后,你需要做的就是书写run() 方法以及他的其他可能需要的方法,启动线程时使用Thread.start() ,注意使用了这个方法之后,cpu就会将这个任务加入到执行列当中去,之后根据一定规则分配资源进行调度该任务,调度该任务的意思就是执行该线程当中的 run() 方法。
3.这里一定要理解一个点,相同优先级的线程之间调度顺序是随机的,和代码顺序无关,这个就不用说了,很简单滴,不相信自己写个程序测试下就行。
三,线程安全问题,
我们一直提到线程会不安全什么的,这个不安全是这个意思:首先有一个大前提,无论你有多少个线程,这些线程都是对同一个对象进行操作或者是由同一个线程初始化的,下面这种情况会不安全:
ackage 第一章;
import javax.swing.plaf.TableHeaderUI;
class MyRunnable implements Runnable{
int count = 10;
public void run(){
count--;
System.out.println(Thread.currentThread().getName() + " " + count);
}
}
public class test1{
public static void main(String[] args){
MyRunnable m = new MyRunnable();
Thread m1 = new Thread(m, "A");
Thread m2 = new Thread(m,"B");
Thread m3 = new Thread(m,"c");
Thread m4 = new Thread(m,"D");
Thread m5 = new Thread(m,"E");
m1.start();
m2.start();
m3.start();
m4.start();
m5.start();
}
}
输出的结果每一次都是不一样的,我们期望的是count从10减到5并输出每一个数值,但是事实上的话,可能B线程已经获取到了count的值,这时候准备执行减一操作并输出,但是程序跳到A线程,执行了count--操作,B可能本来获取的值是10,之后准备count--,然后输出9,但是在这两部操作之间,count被A线程又给减一了,那么B线程获取到count的值时,就获取到了A线程更改之后的值,8,但是他原本获取到的值是9,。也就是说原本输出9,现在输出了8,这很明显是不安全的。。。。这样说不知道清楚不。输出结果如下:
"C:\Program Files\Java\jdk-11.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar=60024:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\bin" -Dfile.encoding=UTF-8 -classpath C:\learn\java\practice\out\production\threadTest 第一章.test1 A 8 B 8 D 6 c 5 E 7 Process finished with exit code 0
下面这种情况是安全的:
就是将count变量写在run() 函数内部,让他成为局部变量,这样每一个线程虽然是由同一个对象初始化的,但是他们的count是属于各自的,你无法在A线程里更改B线程的count的值,这时候就是安全的,就这样,不难理解,这里就不放代码啦。
这里为了解决我们的不安全问题,引出了synchronized概念,
四,synchronized执行过程叙述
这个关键字其实就是给一个对象的之中的某个方法上锁,每当一个线程访问该对象的该方法时,他会首先拿到这个锁,然后开始执行里面的代码,之后如果有另一个线程再来执行同一个对象的该方法,他会先尝试拿到锁,拿不到的话就会一直等着,直到锁被释放,再拿到锁执行代码,这样子就解决了安全问题,因为相当于每一个线程进入该方法之后,就是它自己的领地了,对count变量的操作都是以它自己为标准的,其他线程不能在中途更改count的值,直到该线程执行完毕。
五,几个API:
1.CurrentThread() :获取当前线程,当前线程指的是当前代码片段是在哪一个线程上被调用的,比如下面的代码,构造函数MyRunnable() 是在main 线程当中被调用的,但是run()函数却是在线程A中调用的, 另外注意和this,getName() 的区别,后者是针对于当前this对象而言的Name
class MyRunnable implements Runnable{ int count = 10; MyRunnable(){ System.out.println(Thread.currentThread().getName()); //输出 main } synchronized public void run(){ count--; System.out.println(Thread.currentThread().getName() + " " + count); //输出 A } } public class test1{ public static void main(String[] args){ MyRunnable m = new MyRunnable(); Thread m1 = new Thread(m, "A"); m1.start(); } }
2.isAlive():当前线程是否处于活动状态,简单地说就是是否start()了并且run() 函数还没有运行完 ,用法:Thread.CurrentThread.isAlive()
3.sleep(): 让当前线程休眠一段时间,单位毫秒,注意这里这个当前线程指的是正在运行的线程,就是当前代码片段处于哪一个线程当中,注意放在try语句中,用法:Thread.sleep()
4.getId(): 获取当前线程唯一标识
六,停止线程,暂停线程
概述:停止线程简单来说就是让cpu放弃当前线程的操作,先去执行其他线程,但是如果使用不当,会有数据不同步,独占锁等问题
终止线程的方法:
1.线程run函数调用结束,自动结束
2.调用 interrupt 函数
Thread.interrupt() : 这个函数并不是说立刻停止停止线程,让线程停止操作,而是说在当前位置给线程加一个标识,表示该线程是应该停止的,但是并不会立即停止,那如果想要真正的停止该怎么办呢?使用 isTerrupted()和interrupted(),简单来讲,就是使用interrupt()停止线程添加标识之后,再使用相应的函数判断是否存在这个停止标识,存在的话就不执行某一些代码了,这样来达到停止的效果,但是这两个函数是不同的,看下面
interrupted(): 测试当前线程是否中断,如果中断了,就将中断标识,或者说状态清除掉。简单来讲就是执行完这个函数,线程一定是处于运行状态的
isInterrupted(): 测试线程是否中断,没有清楚中断状态的功能,并且注意判断的并不是当前线程
理解两点:1,当前线程和线程的区别,2.是否会清除中断状态,
看下面例子:
Thread.interrupt() :
class MyRunnable implements Runnable{ int count = 10; MyRunnable(){ System.out.println(Thread.currentThread().getName()); } public void run(){ for(int i=0;i<1000;i++) { System.out.println(i); } } } public class test1{ public static void main(String[] args){ MyRunnable m = new MyRunnable(); Thread m1 = new Thread(m, "A"); m1.start(); m1.interrupt(); //注释该行,不注释下一行,输出true,false //Thread.currentThread().interrupt(); System.out.println("测试是否中断1:"+Thread.interrupted()); //输出false System.out.println("测试是否中断2:"+Thread.interrupted()); //输出false } }
根据运行结果false来讲,我们停止了m1,线程,但是输出为false,因为调用interrupted方法代码处于的线程是main线程,main并没有停止,所以输出false,但是如果将注释去掉,将m1.interrupt()注释掉,会输出true ,false,为什么第二是false?这就是因为该函数具有清除中断状态的作用,第一次执行之后main就不再是中断状态了,所以输出false
isterrupted()方法:不具有清除状态作用,检测的是线程对象,一个具体的对象,不是当前线程,如下
package 第一章; import javax.swing.plaf.TableHeaderUI; class MyRunnable implements Runnable{ int count = 10; MyRunnable(){ System.out.println(Thread.currentThread().getName()); } public void run(){ for(int i=0;i<1000;i++) { System.out.println(i); } } } public class test1{ public static void main(String[] args){ MyRunnable m = new MyRunnable(); Thread m1 = new Thread(m, "A"); m1.start(); m1.interrupt(); //Thread.currentThread().interrupt(); System.out.println("测试是否中断1:&&&&&&&&&&&&&&&&&&&&&&&"+m1.isInterrupted()); //输出true System.out.println("测试是否中断2:&&&&&&&&&&&&&&&&&&&"+m1.isInterrupted()); //输出true } }
都输出true,就是因为他们调用的是对象m1的,m1被停止了,所以肯定输出true,不过注意,这个true会输出在任意位置,cpu调用决定的,所以你如果测试的话加点标识找起来方便。
所以,结合来讲的话,停止线程,run方法可以修改为如下:
public void run(){ for(int i=0;i<1000;i++) { if(Thread.interrupted()){ System.out.println("停止啦"); break; } System.out.println(i); }
//如果给这里加上语句,不判断是否停止,他还是会运行的 }
执行结果,可以看到输出了一些数字之后就停止了
。。。
205 206 207 208 209 210 211 212 停止啦 Process finished with exit code 0
当然这是一种软性的停止,你需要判断他是否停止了,下面介绍一种硬性的停止,抛出异常,很简单,将run 方法改为如下:
public void run(){ try{ for(int i=0;i<1000;i++) { if(Thread.interrupted()){ System.out.println("停止啦"); throw new InterruptedException(); } System.out.println(i);
}
//for循环外面有语句也不会执行了,捕捉到异常
}catch (InterruptedException e){ e.printStackTrace(); } }
一种情况,在沉睡中停止线程,
什么意思呢?假设你现在线程中断了,然后你再去调用Thread.sleep()函数让线程休眠,或者线程已经休眠了,你再去调用interrrupt()函数,那就会产生异常,并清除线程的中断状态。这两种情况是对立矛盾的,所以会产生异常,并且因为产生了异常,所以线程一定是被中断的
比如下面的代码,m1.start()之后,m1线程进行了sleep(),之后在main中又中断了m1线程,这明显是不合理的,所以会输出异常之中的内容
class MyRunnable implements Runnable{ int count = 10; MyRunnable(){ System.out.println(Thread.currentThread().getName()); } public void run(){ try{ for(int i=0;i<1000;i++) { System.out.println(i); Thread.sleep(1000); }}catch (InterruptedException e){ System.out.println("先休眠再停止"); e.printStackTrace(); } } } public class test1{ public static void main(String[] args){ MyRunnable m = new MyRunnable(); Thread m1 = new Thread(m, "A"); m1.start(); try { //加这个语句是为了让m1的中断执行在睡眠之后 Thread.sleep(10); }catch (InterruptedException e){ e.printStackTrace(); } m1.interrupt(); } }
输出如下:
main 0 先停止再休眠 java.lang.InterruptedException: sleep interrupted at java.base/java.lang.Thread.sleep(Native Method) at 第一章.MyRunnable.run(test1.java:15) at java.base/java.lang.Thread.run(Thread.java:834) Process finished with exit code 0
3.使用stop
这种方法已经被废除了,会造成很多问题
最简单的一个问题,数据不同步,假设你去银行,取1000,存1000,然后给你操作时候,这两个操作时在一个方法里面的,并且这个方法是上了锁的。在程序执行完了取1000之后,程序去上了个厕所,休眠了1秒,休眠期间,你使用了stop函数将线程强行停止,释放了对象锁,相当于后面存1000这个操作就没有做了,这很明显是不合理的,数据不同步了。
4.暂停线程
暂停线程是说将当前线程的操作暂停,过一会再回来继续执行,注意和中断的区别,它并不会释放对象锁
suspend()暂停函数,
resume()恢复函数
这两个函数也已经被废除了,因为可能造成独占锁和不同步的问题,废除我们也要知道他为撒子废除了,不想看的朋友可以跳过下面
独占锁问题:
不想放太多的代码,放一点,然后用文字说明,更能表达思路
有这样一个对象,比较简洁,不想写没用的代码,意思就是碰到名字为a的线程,就暂停当前线程
class test{
synchronized public void printStr() { if(Thread.currentThread().getName().equals("a")){
System.out.println("打印。。。。。"); Thread.currentThread().suspend(); } }
}
class MyRunnable implements Runnable{ int count = 10; MyRunnable(){ System.out.println(Thread.currentThread().getName()); } public void run() { //调用test实例的printStr方法 } }
现在,你初始化了a,b两个线程,使用了一个MyRunnable实例初始化的,然后你先start了a线程,a线程执行了printStr()方法,获取到了test实例的对象锁,然后被suspend了,但是它并没有释放test实例的对象锁,这时候如果b线程开启,他是无法执行printStr()方法的,因为它他拿不到对象锁,只能等着,这是不合理的。而且这里有一个有意思的问题,如果你这里使用了suspend的话,你如果想要在其他线程里面使用System.out.println() 方法,是不可以的,因为out对象的println()方法已经被锁定了。就是说你无法拿到他的锁,使用不了这个方法了,看下面的例子
class MyThread extends Thread{ private int i=0; public void run(){ while(true){ System.out.println(i); //占用了println() 方法 i++; } } } public class test1{ public static void main(String[] args){ MyThread m = new MyThread(); m.start(); try { Thread.sleep(10); }catch (InterruptedException e){ e.printStackTrace(); } m.suspend(); System.out.println("main"); //不会打印 } }
输出结果就是打印到某一个数字然后停止,永远不会打印 main ,其实你看一下println()方法源码就明白了:里面的代码被锁住了,a线程独占了这个锁
public void println(int x) { synchronized (this) { print(x); newLine(); } }
不同步问题:
这个我觉得和前面stop的问题是一样的,不说了
七,线程的优先级
概述:前面我们看到的main线程还有自己创建的线程,代码执行都是随机的,55开滴,但是现实中线程肯定是有轻重缓急的,所以就有了线程优先级这一个概念,线程优先级高的执行的概率会大一点,但是也不是一定的,只是相对来讲大一点。
个人感觉这块没撒说的,就几个概念也都很好理解,自己写一些代码看看就行。
1.setPriotity(int ) 设置优先级
2.getPriotity() 获取优先级
3.优先级具有继承性:比如你在A线程里启动了B线程,则B线程的优先级是和A线程一样的,除非你手动进行了设置。很简单吧
4.优先级具有随机性:这个也很理解,优先级高的不一定每一次都比优先级低先执行完,因为cpu在调度的时候肯定是以概率的方式来调度线程的,概率嘛,什么都有可能,只能说样本足够大时,优先级高的一定先执行完。
八,守护线程
java里面有两种线程,用户线程和守护线程,用户线程就是我之前定义的那些,当然也包括main线程,守护线程,举一个最简单的例子,java的垃圾回收机制,它是在什么时候回收的呢?你会跟随着用于线程,有没有delete的空间了,就清除掉它,直到所有线程都结束之后,它才会自动销毁。简单来说,就是一直陪伴着用户线程,帮助用户线程做一些事情,用户线程全部结束了,它也就结束了。
设置方法:thread.setDaemon(true); 默认是false的
个人觉得可能做一些需要不停的做的事情会用到该线程。