java---基本多线程问题汇总

先说java多线程

1、创建多线程的方式有几种?

1、 继承Thread 方式

2、实现Runnable接口的方式

3、实现Callable接口的方式

2、使用的方式的优缺点

1、继承Thread的方式

对于Thread的方式,它的优点就是简单易操作,但是它的缺点是单继承

2、实现Runnable 接口

它解决了Thread单继承的缺点,并且能够实现多个接口,适合多个相同的程序代码的线程去处理同一个资源,但是它不能拿到线程的返回值。

3、实现Callable

       使用Callable接口可以得到线程返回的结果,不过它需要使用FutureTask类作为简单的适配类,因为Thread 构造方法只能接受Runnable 接口参数

3、java 如何使用多线程

1 、使用Thread

package com.example.demothread.util;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 14:59
 * @Description
 */
public class DemoThread {


    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        MyThread myThreadSecond = new MyThread();
        MyThread myThreadThird = new MyThread();
        //设置每个对象的名字
        myThread.start();
        myThreadSecond.start();
        myThreadThird.start();

    }

}

class MyThread extends Thread {
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }
}


运行结果

2、使用Runnable 

 1、 实现Runnable接口

 2、 实现run() 方法,编写线程执行体

 3、调用start() 方法,启动线程

package com.example.demothread.util;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:09
 * @Description
 */
public class DemoRunnable implements  Runnable {

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(Thread.currentThread().getName()+ ":" + x);
        }
    }
}
package com.example.demothread.util;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:13
 * @Description
 */
public class MainDemo {

    public  static void main(String args[]) {
        DemoRunnable demoRunnable = new DemoRunnable();
        DemoRunnable demoRunnableSecond = new DemoRunnable();
        DemoRunnable demoRunnableThird = new DemoRunnable();

        Thread thread = new Thread(demoRunnable);
        Thread threadSecond = new Thread(demoRunnableSecond);
        Thread threadThird = new Thread(demoRunnableThird);

        thread.start();
        threadSecond.start();
        threadThird.start();
    }
}

实现结果:

 3、实现Callable接口

  步骤:  1、实现Callable 接口

             2、重写call方法

             3、创建实现Callable类对象DemoCallable, 创建FutureTask 对象并将DemoCallable对象当做参数传递到其构造参数中

              4、创建Thread并将FutureTask对象当做构造函数参数,调用Thread的start方法

              5、调用FutureTask的get方法

代码实现:

package com.example.demothread.util;

import java.util.concurrent.Callable;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:28
 * @Description
 */
public class DemoCallable implements Callable<Integer> {
    @Override
    public Integer call() {
         Integer sum = 0;
        for (int i=1;i<=100;i++) {
            sum = sum +i;
        }
        return sum;
    }
}
package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.FutureTask;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:13
 * @Description
 */
@Slf4j
public class MainDemo {

    public static void main(String args[]) {
        DemoCallable demoCallable = new DemoCallable();
        DemoCallable demoCallable1Second = new DemoCallable();
        DemoCallable demoCallableThird = new DemoCallable();

        FutureTask<Integer> futureTask = new FutureTask<>(demoCallable);
        FutureTask<Integer> futureTaskSecond = new FutureTask<>(demoCallable1Second);
        FutureTask<Integer> futureTaskThird = new FutureTask<>(demoCallableThird);

        new Thread(futureTask,"first").start();
        new Thread(futureTaskSecond,"second").start();
        new Thread(futureTaskThird,"third").start();
        try {
            System.out.println("---结果"+futureTask.get());
            System.out.println("---结果"+futureTaskSecond.get());
            System.out.println("---结果"+futureTaskThird.get());
        } catch (Exception ex) {
            log.error("获取异步线程结果异常---{}",ex.getMessage(),ex);
        }
    }
}

实现结果:

 

4、Thread 的状态
   

 1、   线程大概有以下几种状态 

      1 new  初始状态

       2 runnable  运行状态

       3 blocked 阻塞状态

       4 waiting 等待状态

      5 timed_waiting 超时等待状态

       6 Termineted 终止状态

  

2、状态的转化

  1、new Thread  表示 new 的状态

  2、 调用start方法来到runnable状态

  3、调用synchronized 方法或者模块的到blocked 状态 获取锁之后 又来到了Runnable 状态

  4 调用object 的wait方法、 Thread的join方法、LockSupport的park 方法来到waiting 等待状态

   5 调用 Object的notify 方法、Object的notifyAll方法、LockSupport的unpark(Thread) 方法状态又回    到了Runnable 状态

    6 线程执行完成之后来到Terminated 终止状态

简单来说就像一个人在跑步一样,.

   一开始在起跑线等待发令枪响,这时候为new  状态

      发令枪响了(调用了start)  这时候为runnable 状态

      跑着跑着 突然有人插队挡道(Thread.join),或者跑到被什么挡住了(Object.wait), 自己只能等待, 这时候为waiting状态

      插队的人跑远了,或者障碍物被移走了(notify),自己又回到了Runnable状态

      当跑到终点了, 跑步也就结束,此时的状态为terminated 状态

 

3、Thread 方法

      1、Sleep

         slepp 是帮助其他线程获得运行机会的最好方法,但是如果当时线程有锁, sleep 并不会出让锁。(注意wait方法释放了锁)

           线程到期会自动苏醒,返回到就绪状态,等待cpu调用

          sleep是静态方法,只能控制当前正在进行的线程

 锁往往是使用synchronized 创建的

        Thread.sleep(long time) 就是将调用的这个代码睡眠,进入waiting状态。到时间之后自动进入就绪状态

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 16:18
 * @Description
 */
@Slf4j
public class DemoSleep implements Runnable {
    private static int count;

    public DemoSleep() {
        count = 0;
    }

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println("线程名:" + Thread.currentThread().getName() + "---" + (count++));
                    Thread.sleep(100);
                } catch (Exception ex) {
                    log.error("捕获到异常---{}", ex.getMessage(), ex);
                }
            }
        }
    }

    public int getCount() {
        return count;
    }
}
package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:13
 * @Description
 */
@Slf4j
public class MainDemo {

    public static void main(String args[]) {
        DemoSleep demoSleep = new DemoSleep();
        Thread threadFirst = new Thread(demoSleep,"first");
        Thread threadSecond = new Thread(demoSleep,"second");
        threadFirst.start();
        threadSecond.start();
    }
}

运行的结果为:

 过程分析: 

     代码上

         DemoSleep 有一个 static   类属性, count这个类是归属于DemoSleep类的,而且仅此一份。在创建对象的时候,初始化了其值为0

       DemoSleep实现了Runnable接口,并重写了Runnable方法,其方法体中使用了sysnchronized 修饰,锁定了这个对象。

        main方法运行的时候创建了一个DemoSleep对象,并且使用这个对象创建了两个Thread,之后调用了两个Thread 的start() 方法。

   执行逻辑上:

         main方法调用两个Thread的start方法后,第一个Thread 先拿到了DemoSleep对象的锁,开始执行run 方法。当第二个Thread 想去执行run方法的时候,并没有拿到锁,所以就只有等待第一个Thread执行完成后释放对象,第二个才能执行。

2、interrupt

        interrupt 是中止线程对象,并且抛出异常

   

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 16:56
 * @Description
 */
@Slf4j
public class DemoInterrupt implements Runnable  {

    @Override
    public void run() {
        try {
            // 本线程睡眠两秒
            Thread.sleep(2000);
        } catch (Exception ex) {
            log.error("捕获到异常---{}",ex.getMessage(),ex);
        }

    }
}

 

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:13
 * @Description
 */
@Slf4j
public class MainDemo {

    public static void main(String args[]) {
        DemoInterrupt demoInterrupt = new DemoInterrupt();
        Thread demoInterruptThread =   new Thread(demoInterrupt);
        demoInterruptThread.start();
        try {
            Thread.sleep(1000);
            demoInterruptThread.interrupt();
        } catch (InterruptedException e) {
            log.error("中断线程");
        }
    }
}

分析: 

 代码分析:

     DemoInterrupt 实现了Runnable接口,run方法体中调用Thread.sleep(2000) 表示当前线程睡眠2000

    main主线程将DemoInterrupt 作为Thread的构造参数传进Thread的构造方法中,然后调用start方法

    main方法中调用Thread.sleep(1000) 将主线程暂1秒

 逻辑分析;

       先调用了start方法执行了demoInterrupt的run方法,然后执行了Thread.sleep(2000) 睡眠2秒

       主线程调用Thread.sleep(1000)后 ,调用了demoInterrpuptThread.interrupt方法,中止了线程。demoInterrupt被中止后抛出了异常,紧跟着线程结束。  

        就像一个人的任务是睡觉,正睡着,突然被敲醒了,被告知不用睡了。这时候这个人肯定不知所措,就会抛出异常。

3、join

   这个方法的作用是先将当前线程挂起, 待其他线程结束之后再执行当前线程的代码。ThreadB中使用了threadA.join 方法则 ThreadB需要等待threadA执行完成之后才能执行。

 原理:

     join 的核心代码为

while(isAlive()) {
   wait(0);
}

       使用了While(isAlive() ) 做循环, 循环体为wait(0) ,由于线程执行完成会自动执行notifyAll方法

 也就是join调用了wait方法阻塞了主线程。

使用:

比如有三个人 小绿、小李、小王 三个人相约一起去酒店吃饭, 菜已经点好了,三个人人从不同的地方出发,只有三个人都到了酒店之后才会开始上菜, 那么 这三个人就分别代表是三个线程,这三个线程执行完成之后才会执行 “上菜” 的代码逻辑

   

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 17:25
 * @Description
 */
@Slf4j
public class DemoJoin implements  Runnable {

    @Override
    public void run() {
      log.info(Thread.currentThread().getName()+"开始出发了");
      try {
          Thread.sleep(2000);
      } catch (Exception ex) {
          log.error("捕获到异常----{}",ex.getMessage(),ex);
      }
    }
}

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 17:27
 * @Description
 */
@Slf4j
public class DemoHotelJoin implements  Runnable {
    Thread thread;

    public DemoHotelJoin(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {
        log.info(Thread.currentThread().getName()+"正在等待全体到齐");
        try {
            thread.join();
        } catch (Exception ex) {
            log.error("捕获到异常---{}",ex.getMessage(),ex);
        }
        log.info("全体到齐了"+Thread.currentThread());
    }
}

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:13
 * @Description
 */
@Slf4j
public class MainDemo {

    public static void main(String args[]) {
        Thread t1  = new Thread(new DemoJoin(),"小绿");
        Thread t2 = new Thread(new DemoJoin(),"小王");
        Thread t3 = new Thread(new DemoJoin(),"小张");

        // 三个人同时出发
        t1.start();
        t2.start();
        t3.start();

        new Thread(new DemoHotelJoin(t3),"酒店").start();

    }
}

分析:

  代码分析:

      DemoJoin 实现了Runnable 接口, 其Run方法使用Thread.Sleep(2000) 表示将对象本身睡眠2秒。

     DemoHotelJoin  实现了Runnable接口,其构造参数中将Thread 传入,并在自身的run方法中调用了thread.join方法

   main代码创建了三个Thread 方法,并将t3 当做构造参数传递到Hotel中,最后调用了Hotel.start()  方法 。

  执行逻辑:

         main代码中调用了三个线程start,每个线程中都会执行Thread.sleep(2000) ,而hotel中的thread属性为t3,并且在自身的run方法中调用了t3.join   ,则hotel要等t3执行完成之后再执行。由于这里三个线程都是睡眠2s,则t3执行完成之后,hotel也可以执行,并且认为所有线程都已经执行完成

引申: 

        

Thread.currentThread().join()

  join方法的作用是阻塞,即等待线程结束,才继续执行,如果调用了Thread.currentThread().join() 这个方法,那么线程一直在阻塞,无法终止,因为它自己在等待自己结束,就造成了四锁。

4、synchronized 

    多线程中,往往会遇到线程同步的问题,所以需要用到锁来进行控制

 synchronized 是java中的关键字,是一种同步锁,它修饰的对象有以下几种: 

       1、修饰一个代码块, 被修饰的代码被称为同步代码块,其作用的范围是大括号{}括起来的代码,作用对象是调用这个代码块的对象

   2、修饰的是一个方法,被修改IDE方式称为同步方法,其作用的范围是整个方法,作用对象是调用这个方法的对象

   3 修饰一个静态方法 其作用的方法是整个静态方法

   4 修饰一个类, 其作用范围是synchronized  后面括号括起来的部分作用的主对象是这个类的所有对象

   修饰一个代码块:

    1、  一个线程访问一个对象中的synchronized同步的代码时,其他试图访问这个对象的线程会被阻塞

  代码见 Thread.sleep() 方法说明

     2、当一个线程访问对象的一个synchronized(this) 同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this) 同步代码块
     多个线程访问synchronized 和非synchronized 代码块
 

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 18:32
 * @Description
 */
@Slf4j
public class SyncThread implements Runnable {

    private int count;

    public SyncThread() {
        count = 0;
    }

    public void countAdd() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                try {
                    log.info(Thread.currentThread().getName() + "----" + i);
                    Thread.sleep(100);
                } catch (Exception ex) {
                    log.error("捕获到异常---{}", ex.getMessage(), ex);
                }
            }
        }
    }

    // 非 synchronized 代码块, 未对count 进行读写操作 所以可以不用synchronized
    public void printCount() {
        for (int i = 0; i < 5; i++) {
            try {
                log.info(Thread.currentThread().getName() + "----" + i);
                Thread.sleep(100);
            } catch (Exception ex) {
                log.error("捕获到异常---{}", ex.getMessage(), ex);
            }
        }
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.equals("mt1")) {
            countAdd();
        } else if (threadName.equals("mt2")) {
            printCount();
        }
    }
}

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:13
 * @Description
 */
@Slf4j
public class MainDemo {

    public static void main(String args[]) {
        SyncThread syncThread = new SyncThread();
        Thread threadFirst = new Thread(syncThread,"mt1");
        Thread threadSecond = new Thread(syncThread,"mt2");
        threadFirst.start();
        threadSecond.start();

    }
}

实现结果:

分析:

  代码:

   SysncThread 实现了 Runnable 接口,  重写了run方法: 根据当前方法体来判断如果是mt1则执行countAdd() 如果方法体是mt2 则执行 printAdd()方法。 其中countAdd使用了synchronized 方法来修饰

   逻辑分析:

     main方法调用了使用Thread 封装的Runnable的run方法 ,创建了两个thread对象,但使用了同一个synThread对象,所以当这两个线程运行的时候,首先threadFirst 调用run 后 会执行countAdd() ,由于方法体中有一个Thread.sleep 所以会让出执行权。这时候轮到threadSecond开始执行,访问没有被synchorinzed 修饰的方法体。

在使用synchronized 修饰方法的时候需要注意以下几点:

   1、synchronized 关键字不能被继承

    虽然可以使用synchorinzed 来定义方法,但是synchroinzed并不属于方法定义的一部分,因此,synchronized 关键字不能被继承。如果在父类的某个方法使用了synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须在子类的这个方法中加上synchronized 关键字才可以,当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但是子类调用父类的同步方法,因此,子类中的方法也就相当于同步了。

  

class Parent {

  public synchroinzed void method() {}
}

class Child extends Parent() {
  
  public synchroized void method() {}

}

 或者在子类中调用父类的同步方法

  

class Parent {
 
 public synchroized void method() {}

}

class Child extends Parent {

  public void method() { super.method(); }
}

在定义接口方法时不能使用synchroinzed 关键字,构造方法不能使用synchroized 关键字, 但可以使用synchronized 代码块来进行同步

 

修饰一个静态方法

      synchroinized 也可以修饰一个静态方法,静态方法是属于类的而不属于对象。同样的,synchronized 修饰的静态方法锁定了是这个类的所有对象

    

 

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 19:23
 * @Description
 */
@Slf4j
public class SyncStaticThread implements  Runnable {

    private static int count;

    public SyncStaticThread() {
        count = 0;
    }

    public synchronized  static  void method() {
        for(int i=0;i<5;i++) {
            try {
                log.info(Thread.currentThread().getName()+"----"+i);
            }catch (Exception ex) {
                log.error("捕获异常---{}",ex.getMessage(),ex);
            }
        }
    }

    @Override
    public void run() {
        method();
    }
}

  

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:13
 * @Description
 */
@Slf4j
public class MainDemo {

    public static void main(String args[]) {
        SyncStaticThread syncStaticThread = new SyncStaticThread();
        Thread threadFirst = new Thread(syncStaticThread,"小绿");
        Thread threadSecond = new Thread(syncStaticThread,"小黑");
        threadFirst.start();
        threadSecond.start();

    }
}

输出结果:

 分析:

  threadFirst 和threadSecond 是两个对象,但是在并发执行的时候却保持了线程同步,这是因为run中调用了静态方法method ,而静态方法是属于类,所以threadFirst和threadSecond相当于使用了同一把锁。

修饰一个类

  凡是用这个类创建的对象访问相同的方法都相当于使用同一把锁

   

public static void method() {

  synchronized(SysThread.class) {

  
   } 

}

每个对象只有一个锁与之关联,.谁拿到这个锁谁就可以运行它所控制的那段代码。

synchroized 关键字加载方法上还是加在对象上,如果它作用的对象是非静态的,则它取得的锁是对象,如果synchroized 作用的对象是一个静态方法或者一个类,则它取得的锁是对类,该类所有对象使用同一把锁

5、yield 方法

     使当前的线程从执行状态变成就绪状态,就是说当一个线程使用了这个方法之后,它就会把自己的CPU执行的时间让掉,让自己或者其他线程运行。

         

package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 19:01
 * @Description
 */
@Slf4j
public class DemoYield implements  Runnable {


    @Override
    public void run() {
        for(int i=0;i<=50;i++) {
            log.info(Thread.currentThread().getName()+"---"+i);
            if(i==30) {
                Thread.yield();
            }
        }
    }
}
package com.example.demothread.util;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lenovo
 * @version 1.0
 * @Date 2022/5/1 15:13
 * @Description
 */
@Slf4j
public class MainDemo {

    public static void main(String args[]) {
       DemoYield demoYield  = new DemoYield();
       DemoYield demoYieldSecond = new DemoYield();
       new Thread(demoYield,"小绿").start();
        new Thread(demoYieldSecond,"小黑").start();
    }
}

  实现结果:

   高兴就打赏一下

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值