Java的多线程问题

本文详细介绍了Java中的多线程概念,包括通过继承Thread类和实现Runnable接口两种创建方式,以及start()与run()的区别、线程安全问题,如卖票示例中出现的问题,并探讨了解决多线程安全的锁机制和死锁等高级话题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是多线程

线程是依赖于进程而存在的。
    A:进程    正在运行的应用程序
    B:线程    进程的执行路径,执行单元

多线程的创建方式

  • 通过继承Thread类
  • 通过实现Runnable接口

实现方式一代码如下

package com.wy.threadcreate1;
/*
 * 创建线程的第一种方式,继承Thread类
 */
public class MyThread extends Thread{
    //重写run方法,重写run方法中的代码之后
    //当启动了这个线程之后,这个线程就会执行run方法中的代码
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(i);
        }
    }

}

测试类如下:

  package com.wy.threadcreate1;
public class ThreadTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();
    }
}

实现方式2代码如下:

package com.wy.threadcreate2;

/*
 * 创建线程的第二种方式,实现Runable接口
 */
public class MyThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(i);
        }
    }
}

多线程方法介绍

  1. 启动线程的方法

    run()
    
  2. start()和run()的区别

    start():1.开启线程  2.执行run()方法里面的代码
        run():执行的是线程里面执行的代码,并不会开启线程
    
  3. 为什么要重写run()方法?

    因为每个线程需要执行的代码都是都是不一样的,
        我们需要将每个线程自己独立执行的代码写到run()方法中执行
    
  4. 线程可以多次启动么?
    不可以多次启动
package com.wy.start_run;

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(i);
        }
    }
}
///////////////////////////////////////////////////////////////////////////
package com.wy.start_run;

public class ThreadTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        MyThread mt2 = new MyThread();

        //mt.start();//1.首先开启了一个独立的线程 
        //2.让这个独立的线程执行run方法中的代码

        mt.start();
        mt2.start();

        //注意:同一个线程不能多次启动,在运行期会报错
        //java.lang.IllegalThreadStateException
        mt.start();

        //1.普通的创对象,调方法  2.代码实在主线程执行,不会开辟新线程
        mt.run();
        mt.run();

    }
}
  1. 线程的名称设置与优先级设置

    线程休眠(Thread.sleep(毫秒值))
    线程名称(setName(),getName();)
    线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间)
    什么叫线程优先级:说白了就是设置你抢占cpu执行权抢占到的概率
    
package com.wy.sleep;

public class MyThread extends Thread{
    @Override
    public void run() {
        //让线程睡眠秒
        try {
            MyThread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        for (int i = 0; i < 20; i++) {
            System.out.println(this.getName()+i);
        }
    }
}
/////////////////////////////////////////////////////////////////////
package com.wy.sleep;
public class ThreadTest {
    public static void main(String[] args) {
        //创建三个线程对象
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();

        //给三个线程对象设置名字
        mt1.setName("Thread one");
        mt2.setName("Thread two");
        mt3.setName("thread three");

        //设置线程的优先级
        //线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间)
        mt1.setPriority(10);
        mt2.setPriority(2);

        //如果设置的值不在1-10之间,则会报java.lang.IllegalArgumentException
        //mt3.setPriority(100);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

如果实现的是Runnable接口,则通过Thread.currentThread().getName()来获取正在运行的线程名称。

多线程安全问题

  1. 继承Thread卖票,会出现问题
package com.wy.multithread;

public class MyThread extends Thread{
    //模拟卖票

    //如果不定义成静态的,则每一个进程都会从50到1进行
    //int ticket = 50;

    //当设置为静态的时候,结果是会出现0,这是因为线程读取了ticket,然后进行了休眠,下一个线程也读取了此数据
    static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            //只有当ticket>0的时候,才可以出售票
            if (ticket>0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(this.getName()+"正在出售第:"+ticket--+"张票");
            }
        }
    }
}
///////////////////////////////////////////////////////////////////////////
package com.wy.multithread;

public class MultiTest {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();

        mt1.setName("thread one");
        mt2.setName("thread two");
        mt3.setName("thread three");

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

2.实现Runnable卖票(睡一会出现线程安全问题)

    问题:
    按照真实的情景加入了延迟,发现出现了这样的两个问题:
    A:相同的票卖了多次
        CPU的一次操作必须是原子性的(操作是CPU执行一次就可以直接完成的)
    B:出现了负数的票
        随机性和延迟导致的
    出现上面的问题称为线程安全问题。

3.什么是多线程安全问题?

    A:是否是多线程环境
    B:是否有共享数据
    C:是否有多条语句操作共享数据

4.解决多线程安全方法

*同步代码块中的问题*

    如果是同一把锁,则不会出现安全问题;
    如果不是同一把锁,则有安全问题。

锁对象问题

        a:同步代码块(定义一个抽象类,里面专门定义一个锁)
            任意对象

        b:同步方法(仅适用于实现runable接口)
        public synchronized void sellTicket(){同步代码}
            this

        c:静态同步方法
            类的字节码对象
            public static synchronized void sellTicket() {
                    需要同步的代码
            }
package com.wy.multithread_3;
/*
 * 解决多线程安全问题,有三种方式:
 * 1.同步代码快,其对应的锁是任意一个类
 * 2.同步方法,其对应的锁是this,(仅适用于实现runable接口)
 * 3.静态同步方法,其对应的锁是类字节码对象
 */
 public class MyThread implements Runnable{
    // 定义50张票
        static int ticket = 50;
        Object obj = new Object();
        int x = 0;

        @Override
        public void run() {
            while (true) {
                if (x % 2 == 0) {

                    //1.这是同步代码块对象的锁
                    synchronized (obj) {// 这样3个线程才可以共享同一把锁

                    //2.这个是同步方法对应的锁,当选择同步方法的时候,选择这个锁
                    //synchronized(this){
                    //3.这个是静态代码块对应的锁,当选择静态同步方法的时候,选择这个锁
                    //synchronized(MyThread.class){

                        if (ticket > 0) {
                            // 考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }

                            System.out.println(Thread.currentThread().getName()
                                    + "正在出售第:" + ticket-- + "张票");
                        }
                    }
                } else {
                    sellTicket();
                }
                x++;
            }

        }
        //1.这里是把同步代码块写到一个方法中

        private void sellTicket() {
            synchronized (obj) {// 这样3个线程才可以共享同一把锁
                if (ticket > 0) {
                    // 考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "正在出售第:"
                            + ticket-- + "张票");
                }
            }
        }

         //2.同步方法:同步方法是将synchronized关键字加到方法上,同步方法的锁是this
        /*
         private synchronized void sellTicket() {
         if (ticket>0) {
         //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
         try {
         Thread.sleep(100);
         } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
         }

         System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");

         }
         }*/


        // 3.静态同步方法,他的锁是本类的字节码文件对象:类名.class。
        /*
         private static synchronized void sellTicket() {
         if (ticket > 0) {
         // 考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
         try {
         Thread.sleep(100);
         } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
         }

         System.out.println(Thread.currentThread().getName() + "正在出售第:"
         + ticket-- + "张票");

         }
         }
         */
}

5.匿名内部类的实现
两种方式

    第一种方式:
    new Thread() {
        public void run() {
            ...
        }
    }.start();

    第二种方式:
    new Thread(new Runnable(){
        public void run() {
            ...
        }
    }).start();
package com.wy.anonymity;

public class Anonymity {
    /**
     * 匿名内部类的方式使用多线程
     * new Thread() { public void run() { ... } }.start();
     * 
     * new Thread(new Runnable(){ public void run() { ... } }).start();
     *
     */
    public static void main(String[] args) {
        // 方式1:
        new Thread() {
            // 重写的方法
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                }
            }
        }.start();

        // 方式2
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                }
            }
        }).start();

    }

}

6.JDK5的Lock锁

    JDK5的Lock锁,我们之前造的所有的锁都没有手动释放锁
    static Lock lock = new ReentrantLock();
    枷锁:lock.lock();
    释放锁:lock.unlock();
    可以让我们明确的知道在哪里加锁和释放锁。

7.死锁问题

    同步嵌套,锁里面套了一个锁,出现同步嵌套。

8.线程的等待和唤醒

    锁对象调用wait()锁对象调用notify()
    wait和sleep的区别
    线程等待,在等待的同时释放锁,而sleep()方法在执行的过程中是不会释放锁的
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值