• java多线程


    什么是多线程

    介绍多线程之前要介绍线程,介绍线程则离不开进程。

    什么是进程?线程?

    进程可以看做一个程序。

    线程是一个程序的执行单元/一个场景。

    进程与线程的区别

    • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。

    • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。(每一个线程就是一个栈)

    多线程就是一个进程运行时产生了多个线程。

    线程对象的生命周期

    1. 新建状态
    2. 就绪状态
    3. 运行状态
    4. 阻塞状态
    5. 死亡状态

    创建线程

    一.继承Thread类

    步骤:①、定义类继承Thread;

         ②、复写Thread类中的run方法;

        目的:将自定义代码存储在run方法,让线程运行

         ③、调用线程的start方法:

        该方法有两步:启动线程,调用run方法。

    1. public class Test3 extends Thread{
    2. @Override
    3. public void run() {
    4. for (int i = 0; i < 10; i++) {
    5. System.out.println(i);
    6. }
    7. }
    8. }
    9. public class Test4 {
    10. public static void main(String[] args) {
    11. Test3 test3 = new Test3();
    12. test3.start();
    13. }
    14. }

    注意.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)

    t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
    这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
    启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
    run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

     

    实现Runnable接口:

    实现步骤: ①、定义类实现Runnable接

         ②、覆盖Runnable接口中的run方法,将线程要运行的代码放在该run方法中。

         ③、通过Thread类建立线程对象。

         ④、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程执行指定对象的run方法就要先明确run方法所属对象。

         ⑤、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

    1. public class Test3 implements Runnable{
    2. @Override
    3. public void run() {
    4. for (int i = 0; i < 10; i++) {
    5. System.out.println(i);
    6. }
    7. }
    8. }
    9. public class Test4 {
    10. public static void main(String[] args) {
    11. Thread thread = new Thread(new Test3());
    12. thread.start();
    13. }
    14. }

    3、通过Callable和Future创建线程:

    实现步骤:①、创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值。

                     ②、创建Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值

         ③、使用FutureTask对象作为Thread对象启动新线程。

         ④、调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

    1. public class Test3 implements Callable {
    2. @Override
    3. public Integer call() throws Exception {
    4. int sum=0;
    5. for (int i = 0; i < 10; i++) {
    6. sum+=i;
    7. }
    8. return sum;
    9. }
    10. }
    11. public class Test4 {
    12. public static void main(String[] args) throws ExecutionException, InterruptedException {
    13. Test3 test3 = new Test3();
    14. ExecutorService es = Executors.newFixedThreadPool(1);
    15. Future future = es.submit(test3);
    16. Integer i = future.get();
    17. System.out.println(i);
    18. }
    19. }

    继承Thread类和实现Runnable接口、实现Callable接口的区别。

    继承Thread

    优点:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。

    缺点:已经继承了Thread类,无法再继承其他类。

    实现Runnable

    优点:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

    缺点:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。

    实现Callable:

    优点:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

    缺点:比较复杂、访问线程必须使用Thread.currentThread()方法

    在实际开发过程中一般使用实现Runnable的方式创建多线程。

    获取当前线程对象、获取线程对象名字、修改线程对象名字

    方法名作用
    static Thread currentThread()获取当前线程对象
    String getName()获取线程对象名字
    void setName(String name)修改线程对象名字

    当线程没有设置名字的时候,默认的名字是什么?

    • Thread-0
    • Thread-1
    • Thread-2
    • Thread-3

    关于线程的sleep方法

    方法名作用
    static void sleep(long millis)让当前线程休眠millis秒

    静态方法:Thread.sleep(1000);
    参数是毫秒
    作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
    这行代码出现在A线程中,A线程就会进入休眠。
    这行代码出现在B线程中,B线程就会进入休眠。
    Thread.sleep()方法,可以做到这种效果:
    间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。

    关于线程中断sleep()的方法

    方法名作用
    void interrupt()终止线程的睡眠

    Java进程的优先级

    方法名作用
    int getPriority()获得线程优先级
    void setPriority(int newPriority)设置线程优先级

    线程安全

    当多个线程访问是就可能会出现线程安全

    一个多窗口买票:

    1. public class MP implements Runnable{
    2. private static int a=100;
    3. private Object obj = new Object();
    4. @Override
    5. public void run() {
    6. while (true){
    7. if (a > 0) {
    8. try {
    9. Thread.sleep(10);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. System.out.println(Thread.currentThread().getName() + "票号" + a);
    14. a--;
    15. }
    16. }
    17. }
    18. }
    19. public class Test3 {
    20. public static void main(String[] args) {
    21. MP mp = new MP();
    22. Thread thread1 = new Thread(mp,"一号");
    23. Thread thread2 = new Thread(mp,"二号");
    24. Thread thread3 = new Thread(mp,"三号");
    25. Thread thread4 = new Thread(mp,"四号");
    26. thread1.start();
    27. thread2.start();
    28. thread3.start();
    29. thread4.start();
    30. }
    31. }

     我们可以看到,这里出现了一些重复票,为什么会出现这种情况,出现这种情况显然表明我们这个方法根本就不是线程安全的,出现这种问题的原因有很多,我们说最常见的一种,就是我们A线程在进入方法后,拿到了a的值,刚把这个值读取出来还没有改变a的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的a值是一样的。

    多线程安全问题解决

    同步代码块

    格式:

    1. synchronized (锁对象) {
    2. 可能会产生线程安全问题的代码
    3. }

    把上述代码加上synchronized后

    1. public class MP implements Runnable{
    2. private static int a=100;
    3. private Object obj = new Object();
    4. @Override
    5. public void run() {
    6. while (true){
    7. synchronized (obj) {
    8. if (a > 0) {
    9. try {
    10. Thread.sleep(10);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. System.out.println(Thread.currentThread().getName() + "票号" + a);
    15. a--;
    16. }
    17. }
    18. }
    19. }
    20. }

    可以看到没有了重复票

    提示:加上同步后只有一个线程在执行是CPU太好了,可以加大基数就可以看到多个线程。

    JDK1.5后的同步解决

    在JDK5之前,锁的获取和释放都是隐式的,看不见。也就说我们并没有直接看到在哪里加上了锁,在哪里释放了锁。到了JDK5的时候,java中提供了一个Lock接口,在这个接口中定义了锁的释放和获取的方法,后期程序中需要使用同步,这时可以使用Lock接口的实现类显示的完成锁的获取和释放的动作。

    public void lock():加同步锁。

    public void unlock():释放同步锁。

    注意:在使用Lock锁对象时,需要手动的书写代码用来:获取锁、释放锁

     由于Lock属于接口,不能创建对象,所以我们可以使用它的子类ReentrantLock来创建对象并使用Lock接口中的函数。

    用Lock改造上述买票后代码

    1. public class MP implements Runnable{
    2. private static int a=100;
    3. private Object obj = new Object();
    4. Lock l=new ReentrantLock();
    5. @Override
    6. public void run() {
    7. while (true){
    8. l.lock();
    9. if (a > 0) {
    10. try {
    11. Thread.sleep(10);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. System.out.println(Thread.currentThread().getName() + "票号" + a);
    16. a--;
    17. }
    18. l.unlock();
    19. }
    20. }
    21. }

    同步的前提:

      1、必须要有两个或者两个以上的线程。

      2、必须是多个线程使用同一个锁。

      3、必须保证同步中只能有一个线程在运行。

      4、只能同步方法,不能同步变量和类。

      5、不必同步类中所有方法,类可以拥有同步和非同步的方法。

      6、如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

    死锁

      进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以它们就互相等待对方占有的资源释放,所以也就产生了一个循环等待死锁。

    1. public class DeadLock {
    2. public static void main(String[] args) {
    3. Thread t1 = new Thread(new DeadLockTest(true));
    4. Thread t2 = new Thread(new DeadLockTest(false));
    5. t1.start();
    6. t2.start();
    7. }
    8. }
    9. class DeadLockTest implements Runnable {
    10. private boolean flag;
    11. static Object obj1 = new Object();
    12. static Object obj2 = new Object();
    13. public DeadLockTest(boolean flag) {
    14. this.flag = flag;
    15. }
    16. public void run() {
    17. if (flag) {
    18. synchronized (obj1) {
    19. System.out.println("if lock1");
    20. synchronized (obj2) {
    21. System.out.println("if lock2");
    22. }
    23. }
    24. } else {
    25. synchronized (obj2) {
    26. System.out.println("else lock2");
    27. synchronized (obj1) {
    28. System.out.println("else lock1");
    29. }
    30. }
    31. }
    32. }
    33. }

     

    注意:在开发中一旦发生了死锁现象,不能通过程序自身解决。必须修改程序的源代码。

    在开发中,死锁现象可以避免,但不能直接解决。当程序中有多个线程时,并且多个线程需要通过嵌套对象锁(在一个同步代码块中包含另一个同步代码块)的方式才可以操作代码,此时就容易出现死锁现象。

    可以使用一个同步代码块解决的问题,不要使用嵌套的同步代码块,如果要使用嵌套的同步代码块,就要保证同步代码块的上的对象锁使用同一个对象锁(唯一的对象锁)

  • 相关阅读:
    居舍杂志居舍杂志社居舍编辑部2022年第27期目录
    [Firefox/快捷键] 禁用Ctrl-W快捷键
    运维必备 | ansible 自动化运维工具之变量的定义与调用
    Java:本地文件通过表单参数接口发送后大小变成0
    6、Linux驱动开发:设备-更简单的设备注册
    Oracle:poor sql导致的latch: cache buffers chains案例
    Vue项目 -- 解决Eslint导致的console报错问题
    牛客刷题<11>4位数值比较器电路
    如何在不同的 IP 地址上运行多个 Docker 容器
    使用Idea连接Mysql以及基本的连接Mysql方式
  • 原文地址:https://blog.csdn.net/weixin_48031392/article/details/126505933