一.线程的常用方法
1.线程制止
1.1通过成员对线程举行制止
变量创建必要以static修饰而且变量成员必要以final修饰大概非final修饰(稳固的常量)才气进入到run方法中,但是假如你想制止的话,你肯定要修改它的值,以是我们必须要界说一个外部成员。
- public class Demo7 {
- //这里的成员默认false
- private static boolean isQuit;
- public static void main(String[] args) throws InterruptedException {
- Thread thread=new Thread(()->{
- //不为true进入循环
- while(!isQuit){
- System.out.println("Thread is Running");
- try {
- //thread线程睡眠1000
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- //这里跳出循环后打印
- System.out.println("Thread closing");
- });
- thread.start();
- //这里进入start后线程和主线程同时运行,程序继续往下走
- //这里sleep睡眠5000ms为条件,达成条件后继续往下执行
- Thread.sleep(5000);
- isQuit=true;
- //这里睡眠2000在执行下面代码,这时候打印为TERMINATED
- Thread.sleep(2000);
- System.out.println(thread.getState());
- }
- }
复制代码 1.2通过Thread当地方法制止
通过上述我们明确假如我们创建,可以看到缺点。
1.必要手动创建变量。
2.当线程内部在sleep的时间,主线程修改为ture,这时间竣事了循环,但是新的线程内部无法及时的做出相应。
这时间我们就要通过Java中的当地方法来举行线程的制止。
- public class Demo7 {
- public static void main(String[] args) throws InterruptedException {
- Thread thread=new Thread(()->{
- //这里的currentThread方法可以获得当前线程的实例,但是无法直接写成thread,这时候我们的thread还没有构造完成。
- //isInterrupted是标志位置,判断线程是否结束。
- //isInterrupted 默认为false
- while(!Thread.currentThread().isInterrupted()){
- System.out.println("Thread is running");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println("结束线程");
- });
- thread.start();
- Thread.sleep(3000);
- //这里修改为true,循环会结束?
- thread.interrupt();
- Thread.sleep(2000);
- System.out.println("Thread exit");
- }
- }
复制代码
这里我们可以看到线程照旧在实行,为什么呢?
正常来说sleep会进入休眠,此处给到interrupt改为true之后就可以使用sleep内部触发一个非常,提前被唤醒,扫除该实例的标志位。
这里扫除该实例标志位后假如不使用break大概throw出非常则会继承实行,可以将选择权限交给开辟者举行选择,我们可以通过break来跳出大概直接抛出。
- while (!Thread.currentThread().isInterrupted()) {
- System.out.println("Thread is working");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- throw new RuntimeException();
- //或者直接通过上述代码的打印异常增加一个break,break前可以增加一些想要添加的代码。
- //break;
- }
- }
- });
复制代码 2.线程的期待join
join 是实现线程期待的结果,在主线程调用thread.join(),此时是主线程期待thread线程先竣事。
让一个线程,期待另一个线程实行竣事,在继承实行,本质上就是控制线程竣事的序次的方式。
2.1不增长参数
- public class Demo7 {
- public static void main(String[] args) throws InterruptedException {
- Thread thread =new Thread(()->{
- while(!Thread.currentThread().isInterrupted()){
- System.out.println("Thread is running:1");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- break;
- }
- }
- });
- thread.start();
- Thread.sleep(3000);
- thread.interrupt();
- //这里的join就是阻断,让其执行完成后在继续执行其他线程
- thread.join();
- for(int i=0;i<3;i++){
- System.out.println("Thread is running:2");
- Thread.sleep(1000);
- }
- }
- }
复制代码 2.2增长参数
当增长参数后我们会限定join的期待时长,假如凌驾该期待时长则继承实行
- public class Demo7 {
- public static void main(String[] args) throws InterruptedException {
- Thread thread = new Thread(() -> {
- for (int i=0;i<10;i++) {
- System.out.println("Thread is running:1");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- throw new RuntimeException();
- }
- }
- });
- thread.start();
- // 这里等待thread执行完成则主线程开始执行
- // 如果join带参数后,则是需要等待的时间,如果等待时间过长我们就需要将主线程继续进行
- thread.join(3000);
- for(int i=0;i<3;i++){
- System.out.println("Thread is running:2");
- }
- }
- }
复制代码 2.3线程的休眠时间
线程的sleep是有毛病的,由于线程的调治开销也是必要时间,以是大概不是很正确,比力随机。
- public static void main(String[] args) throws InterruptedException {
-
- long start = System.currentTimeMillis();
- Thread.sleep(1000);
- long end = System.currentTimeMillis();
- System.out.println("开始和结束的时差(以ms为单位):"+(end-start));
- }
复制代码
二.线程的其他状态
线程状态分析NEWTHREAD的对象已经存在,start方法还没调用,没有创建新的线程TERMINATEDTHREAD的对象仍然存在,内核中的线程已经烧毁RUNNABLE停当状态(线程已经在cpu上实行了/线程正在列队期待在cpu实行)TIMED_WAITING壅闭状态,由于sleep这种固定时间的方式产生的壅闭WAITING壅闭状态,由于wait这种不固定时间的方式产生的壅闭BLOCKED壅闭状态,由于锁竞争导致的壅闭
- public static void main(String[] args)throws InterruptedException {
- Thread thread=new Thread(()->{
- });
- //在调用start之前获取的状态,此时是NEW状态
- System.out.println(thread.getState());
- }
复制代码
- public static void main(String[] args) throws InterruptedException {
- Thread thread=new Thread(()->{
- });
- thread.start();
- thread.join();
- //这里start开始创建运行线程,当线程结束后(因为join对其阻塞)这时候线程销毁状态为TERMINATED
- System.out.println(thread.getState());
- }
复制代码
- public static void main(String[] args) {
- Thread thread=new Thread(()->{
- });
- thread.start();
- //这里start已经创建好线程正在就绪,是RUNNABLE状态
- System.out.println(thread.getState());
- }
复制代码
- public static void main(String[] args) throws InterruptedException {
- Thread thread=new Thread(()->{
- while (true){
- try {
- Thread.sleep(800);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- });
- thread.start();
- while(true){
- //通过thread线程的进行sleep阻塞的状态sleep是TIMED_WAITING状态
- Thread.sleep(1000);
- System.out.println(thread.getState());
- }
- }
复制代码 三.线程安全
线程安满是什么?
线程安满是当一段代码在单个线程中跑的时间,不会出现题目。
但是假如放到了多个线程中则出现题目(bug),我们把这种叫做“线程的安全题目”大概是“线程不安全”。
3.1多线程实行
假设界说一个成员变量count默以为0,通过两个线程将count存储至十万数值。
- private static int count;
- public static void main(String[] args) throws InterruptedException {
- Thread t1=new Thread(()->{
- for(int i=0;i<50000;i++){
- count++;
- }
- });
- Thread t2=new Thread(()->{
- for(int i=0;i<50000;i++){
- count++;
- }
- });
- t1.start();
- //如果t1和t2start同时执行,没有join的约束则会出现bug
- t1.join();
- //线程两个如果并发开始进行则出现bug,我们的实际结果输出十万
- t2.start();
- t2.join();
- System.out.println(count);
- }
复制代码 这里的count每次自增的使用本质是分成三步举行(站在cpu的角度通过三个指令举行实现)⬇️
1.load: 将数据从内存中,读到cpu寄存器中。
2.add: 将寄存器的数据举行+1自增。
3.save: 把寄存器中的数据,生存到内存中。
假如多个线程实行上述的代码,由于线程的调治序次是“随机”的,因在count++中会实行N次而且三个指令的cpu指令序次差别,就会导致在有些调治序次下,并发水平高,上述逻辑就会出现题目。
通过上述可以意识到,在多线程中,在随机调治的时间,多个线程之间存在诸多大概的先后序次,我们必须要在包管全部大概的情况下,代码是准确的。
3.2线程加锁
使用线程加锁来制止办理线程的辩说。
synchronized 在使用的时间,要搭配一个代码块{} 进入{就会加锁 出了}就会解锁,这里synchronized()中必要表现一个用来加锁的对象,这个对象是谁不紧张,紧张的是通过这个对象来区分两个线程是否在竞争同一块锁。
在已经加锁的状态中,另一个线程实行同样的加锁,就会产生“锁辩说/锁竞争”,后一个线程就会壅闭期待,不绝到前一个线程解锁为止。
- public class Test {
- private static int count;
- public static void main(String[] args) throws InterruptedException {
- //这里是上锁,前提是线程公用一个锁!!
- Object locker =new Object();
- Thread t1=new Thread(()->{
- for(int i=0;i<50000;i++){
- synchronized (locker){
- count++;
- }
- }
- });
- Thread t2=new Thread(()->{
- for(int i=0;i<50000;i++){
- synchronized(locker){
- count++;
- }
- }
- });
- t1.start();
- t2.start();
- t1.join();
- t2.join();
- System.out.println(count);
- }
- }
复制代码 synchronized紧张的特性,在Java中针对一个对象加多个锁是可重入的。
所谓的可重入锁,指的是一个线程,一把锁,加锁两次,会出现死锁,就是“不可重入“,反之,就是”可重入“。
3.2.1死锁
在多个线程中,假如是有N把锁(嵌套锁),无论是否可重入,都会进入死锁状态。
- public class Test {
- private static final Object locker1=new Object();
- private static final Object locker2=new Object();
- public static void main(String[] args) {
- Thread t1=new Thread(()->{
- synchronized(locker1){
- System.out.println("hello t1:1");
- synchronized (locker2){
- System.out.println("hello t1:2");
- }
- }
- });
- Thread t2=new Thread(()->{
- synchronized(locker2){
- System.out.println("hello t2:2");
- synchronized (locker1){
- System.out.println("hello t2:1");
- }
- }
- });
- t1.start();
- t2.start();
- }
- }
复制代码
3.2.2哲学家进餐
这里的锁可以以下列来举例,哲学家进餐。
注意:这里的叉子相当于筷子。
这里每个哲学家都有一份面而且左右两边都有一副叉子,每一个哲学家必须使用两副叉子才气举行用饭,叉子在同一时间只能被同一个人使用
,假如哲学家没有拿到两副叉子,则无法吃面。
这里哲学家的状态只有两种:一种是吃面,一种是思索(期待)。
3.2.3并行算法
这里假如哲学家在用饭和思索的状态下切换,这里假如哲学家用饭和思索都是恣意时间段,假如每个哲学家饿的时间和思索的时间不固定,则即可瓜代实行。
这里假如一个哲学家想要拿这个叉子,而另一个哲学家也想拿,这时间则为死锁,两个哲学家都吃不到面。
3.2.4死锁的告竣条件
1.互斥使用:(锁的根本特性)当一个线程中持有着一把锁,另一个线程也想要获取锁,这时间就要壅闭期待。
2.不可抢占:(锁的根本特性)当锁已经被线程1拿到之后,线程2只能等线程1自动开释掉,不能强占。
3.哀求保持:(代码的结构)一个线程中假如获取多把锁(当获取第一把锁之后在获取第二把锁,在获取的过程中,锁1不会被开释。)
4.循环期待/环路期待:期待的依靠关系,形成循环。
假如有两把锁设a、b,线程1获取到锁a,然后线程2也获取到锁b,这时间线程1嵌套获取b锁,线程2嵌套获取a锁,则循环期待锁开释,进不去出不来。
比方:你此时在车库,打不开车库门,这时间车钥匙在家,你这时间给你你妻子打电话,让他回家帮你拿钥匙,而你妻子这时间说家的钥匙我落到车库啦~ 。这时间就陷入一种嵌套循环,形成死锁。
⬆️以上为死锁的四个条件,只管制止锁嵌套结构!
3.3volatile关键字
盘算机在运行过程中必要访问数据,这些数据会放在内存中(界说一个变量时,变量就是在内存中)。
cpu在使用这个变量时,就会把这些内存中的数据读取出来,放到cpu的寄存器中加入运算(load读取内存值放到寄存器中)。
寄存器读取速率>内存读取速率>硬盘读取运算。
为了办理上述的题目,这时间编译器大概对代码举行优化,把一些原来要读取内存的使用,优化成读取寄存器,镌汰读取内存的次数来进步代码的团体服从
- public class Test {
- private static int isQuit=0;
- public static void main(String[] args) {
- Thread thread1=new Thread(()->{
- //循环没有内容,但是也会循环许多次
- while(isQuit==0){
- }
- System.out.println("isQuit");
- });
- thread1.start();
- Thread thread2=new Thread(()->{
- //用户通过第二个线程输入值不为0,则循环会结束?
- Scanner scanner=new Scanner(System.in);
- isQuit=scanner.nextInt();
- });
- thread2.start();
- }
- }
-
复制代码 这时间一个线程读和一个线程写,仍然在实行,此时出现bug(引起线程安全题目),此时就是由于内存可见性引起。
1.load读取内存中的isQuit值放入寄存器中。
2.通过cmp指令比力寄存器中的值是否为0,绝对是否要继承循环。
此时会发生大量的循环,大量load和cmp使用,这时间编译器/JVM发现load出来的结果isQuit都是雷同的,而且load读取内存到寄存器中很耗费时间(1次load的时间相当于上万次的cmp)。
此时编译器则为了更方便的使用,只有第一次循环时读取内存器,后续不在读取内存,而是直接从寄存器中取isQuit的值,此时内存中无法读取数据编译器却没有举行读取,而是不绝在寄存器中实行,此时就出现了bug。
上述方案则因在寄存器中读取快,但是算的禁绝。
而volatile关键词来办理此方案,而volatile则是让JVM继承读取内存中的数据然后通过寄存器在逐个比力,办理此方案。
- public class Test {
- private volatile static int isQuit=0;
- public static void main(String[] args) {
- Thread thread1=new Thread(()->{
- while(isQuit==0){
- //循环没有内容,但是也会循环许多次
- }
- System.out.println("isQuit");
- });
- thread1.start();
- Thread thread2=new Thread(()->{
- //用户通过第二个线程输入值不为0,则循环会结束?
- Scanner scanner=new Scanner(System.in);
- isQuit=scanner.nextInt();
- });
- thread2.start();
- }
- }
复制代码
我们可以镌汰读取的次数,通过sleep就寝来限定load的读取次数,此时就不会出发内存可见性题目,但是什么时间代码会被JVM优化,我们人是不清楚的,只有呆板扫除,以是我们只管使用volatile更保险一些,此方法知道即可。
- public class Test {
- private static int isQuit=0;
- public static void main(String[] args) {
- Thread thread1=new Thread(()->{
- while(isQuit==0){
- //循环没有内容,但是也会循环许多次
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- System.out.println("isQuit");
- });
- thread1.start();
- Thread thread2=new Thread(()->{
- //用户通过第二个线程输入值不为0,则循环会结束?
- Scanner scanner=new Scanner(System.in);
- isQuit=scanner.nextInt();
- });
- thread2.start();
- }
复制代码 3.4 wait和notify
wait和notify是用来调和多个线程中的实行序次,本身多个线程的实行序次是随机的,很多的时间,必要通过一些本领来对线程举行干预,前面说过join也是一种干预,但只是影响了线程的竣事的先后序次,很多时间,我们渴望线程不要竣事,也要举行干预形成先后序次。
wait(不带参数): 让指定的线程进入壅闭状态,且无notify则处于不绝壅闭状态,代码无法向下实行。
wait(带参数):指定线程的时间,超出时间则向下继承实行代码。
notify: 唤醒对应壅闭状态的线程。
notifyAll:唤醒全部处于壅闭状态的线程。
- public class Test {
- public static void main(String[] args) {
- Object obj=new Object();
- Thread t1=new Thread(()->{
- synchronized(obj){
- try {
- System.out.println("wait等待1");
- obj.wait();
- System.out.println("wait1结束");
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- });
- Thread t3=new Thread(()->{
- synchronized(obj){
- try {
- System.out.println("wait等待2");
- obj.wait();
- System.out.println("wait2结束");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- Thread t2=new Thread(()->{
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- synchronized (obj){
- obj.notifyAll();
- System.out.println("通知");
- }
- });
- t1.start();
- t2.start();
- t3.start();
- }
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |