JAVA--多线程学习

本文详细介绍了Java中实现多线程的四种方式:继承Thread类、实现Runnable接口、实现Callable接口以及使用线程池,并分析了它们的优缺点。此外,还探讨了线程安全问题,通过同步机制解决并发问题,包括同步代码块、同步方法、wait()、notify()和notifyAll()的使用。最后,通过银行存钱和线程通信的例子展示了线程同步的实际应用。

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

创建方式:

目录

方式一:继承Thread类的实现方式

//实现遍历100以内的所有偶数
//1.创建一个继承于Thread类的子类
class MyThread extends Thread{
    //2.重写Thread类的run()
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }

        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子类的对象
        MyThread myThread = new MyThread();
        MyThread myThread2 = new MyThread();
        //4.通过此对象调用start():①:启动当前线程②:调用当前线程的run()
        myThread.start();
        //问题一:不能直接调用run()方法启动线程
        //myThread.run()
        //问题二:再启动一个线程,遍历100以内的偶数。不可以让已经start的线程去执行
        //myThread.start();
        myThread2.start();
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
        new Thread(){
            @Override
            public void run(){
                for (int i = 0; i < 100; i++) {
                    if(i%2!=0){
                        System.out.println(Thread.currentThread().getName()+":"+i);
                    }

                }
            }

        }.start();
    }
}
* 1.start():启动当前线程;调用当前线程的run()
* 2.run()
* 3.currentThread():静态方法,返回当前代码的线程
* 4.getName():获取当前线程的名字
* 5.setName():设置当前线程的名字
* 6.yield() 释放当前cpu的执行权
* 7.join():在线程a中调用线程b的join()方法,此时线程a进入阻塞状态,直到线程b完全执行完以后  、
*   线程a才继续执行
* 8.stop():已过时。当执行此方法时,强制结束当前线程
* 9.sleep(long millitime):当前线程睡眠
* 10.isAlive() :当前线程是否还存活
*
* 线程优先级
* 1.
* MAX_PRIORITY:10
* MIN_PRIORITY:1
* NORMAL_PRIORITY:5
* 2.如何获取和设置当前线程的优先级
*    getPriority()
*    setPriority(int p)

方式二:实现Runnable接口

//1.创建一个实现Runnable接口的类
class MyThreadRunnable implements Runnable {
    //2.实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class ThreadRunableTest {
    public static void main(String[] args) {
        // 3.创建实现类的对象
        MyThreadRunnable myThreadRunnable = new MyThreadRunnable();
        // 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread thread = new Thread(myThreadRunnable);
        // 5.通过Thread类的对象调用start()
        thread.start();
        //再启动一个线程,遍历100以内的偶数
        Thread thread2 = new Thread(myThreadRunnable);
        thread2.start();

    }
}

方式三:实现Callable接口 ----JDK 5.0 新增

  1. //1.创建一个实现Callable的实现类
    class NumThread implements Callable<Integer>{
        //2.实现call方法,将此线程需要执行的操作声明在call()中
        @Override
        public Integer call() throws Exception {
            int sum = 0;
            for (int i = 0; i <= 100; i++) {
                if(i%2==0){
                    System.out.println(i);
                    sum += i;
                }
            }
            return sum;
        }
    }
    public class CallableTest {
    
        public static void main(String[] args) {
            //3.创建Callable接口实现类的对象
            NumThread numThread = new NumThread();
    
            //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
            FutureTask futureTask = new FutureTask(numThread);
            //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
            new Thread(futureTask).start();
    
            try {
                //6.获取Callable中call方法的返回值  如果不需要返回值,则不用定义
                //get()返回值即为FutureTask构造器参数Callable实现的call()的返回值
                Object sum = futureTask.get();
                System.out.println("总和为:" + sum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

1.call方法可以有返回值

2.call方法可以抛出异常,被外面的操作捕获,获取异常的信息

3.Callable是支持泛型的

方式四:线程池

class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0){
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
}
class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 != 0){
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        ThreadPoolExecutor executorService1 = (ThreadPoolExecutor) executorService;

        //设置线程池属性
        //executorService1.setCorePoolSize(15);
        //executorService1.setKeepAliveTime();

        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        NumberThread numberThread = new NumberThread();
        NumberThread1 numberThread1 = new NumberThread1();
        executorService.execute(numberThread);//适合适用于Runnable
        executorService.execute(numberThread1);//适合适用于Runnable
//        executorService.submit();//适合适用于Callable
        executorService.shutdown();
    }
}

通过executorService1设置线程池的属性

线程池主要属性分析:

引用好文:

​​​​​​​​​(51条消息) java自带线程池和队列详细讲解_过天的博客-CSDN博客_线程池队列

线程安全问题

  • 使用继承Thread类的方式实现三个窗口卖票
//同步代码块
class Window extends Thread{

    private static int ticket = 1000;
    private static Object object = new Object();
    @Override
    public void run(){
        while(true) {
            //正确的
            synchronized(Window.class){ //Class clazz = Window2.class 只加载一次
            //synchronized (object) {
            // 错误的方式
            //synchronized (this){ this 代表window window1 window2 三个对象 锁不唯一

                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
//同步方法
class Window extends Thread{

    private static int ticket = 1000;
    @Override
    public void run(){
        while(true) {
            show();
        }
    }
    private static synchronized void show(){//同步监视器:Window3.class

        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
    }
}
public class WindowTest {
    public static void main(String[] args) {
        Window window = new Window();
        Window window1 = new Window();
        Window window2 = new Window();

        window.setName("窗口1");
        window1.setName("窗口2");
        window2.setName("窗口3");

        window.start();
        window1.start();
        window2.start();

    }
}
  • 使用实现Runnable接口的方式实现三个窗口卖票
// 同步代码块
class Window1 implements Runnable{

    private int ticket = 1000;
    Object object = new Object();
    @Override
    public void run(){
        while(true) {
            synchronized(this){
            //synchronized(object) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
//同步方法
class Window1 implements Runnable{

    private int ticket = 1000;
    @Override
    public void run() {
        while (true) {
            show();
        }
    }
    private synchronized void show(){//同步监视器就是this
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
    }
}

public class WindowTest1 {
    public static void main(String[] args) {

        Window1 w = new Window1();
        Thread window = new Thread(w);
        Thread window1 = new Thread(w);
        Thread window2 = new Thread(w);

        window.setName("窗口1");
        window1.setName("窗口2");
        window2.setName("窗口3");

        window.start();
        window1.start();
        window2.start();
    }
}
* 1.问题:卖票过程出现重票和错票
* 2.原因:当某个线程操作车票得过程中,尚未操作完成时,其他线程参与进来,也操作车票
* 3.如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket时,其           *   它线程才能开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变
* 4.在java中,我们通过同步机制,来解决线程的安全问题
*
*   方式一:同步代码块
*     synchronized(同步监视器){
*         //需要同步的代码
*     }
*     说明:1.操作共享数据的代码即为需要被同步得代码
*          2.共享数据:多个线程共同操作的变量。比如ticket就是共享数据
*          3.同步监视器:俗称,锁。任何一个类的对象都可以充当锁
*             要求:多个线程必须要共用同一把锁
*
*       补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
*   方式二:同步方法
*        如果操作共享数据得代码完整得声明在一个方法中,我们不妨将此方法声明同步方法
*
*
* 5.同步的方式,解决了线程得安全问题 ---好处
*     操作同步代码时,只能有一个线程参与,其他线程等待。相当于单线程得过程,效率低 ---劣势
*
* 比较创建线程的方式比较:
* 开发中:优先选择:实现Runnable接口的方式
* 原因:1.实现的方式没有类的单继承性的局限性
*      2.实现的方式更适合来处理多个线程共享数据的情况
* 联系:public class Thread implements Runnable
* 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明再run()中

线程通信

* 线程1 与 线程2 交替打印
* 涉及的三个方法
* 1.wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
* 2.notify():一旦执行此方法,就会唤醒wait的一个线程。如果有多个线程就wait,就唤醒优先级搞得那*  个        
* 3.notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
*
* 以上三者必须使用在同步代码块和同步方法中
* 三个方法的调用者必须是同步代码块或同步方法的同步监视器
* 定义在Object中
*
* 面试题:sleep()和wait()方法的异同
* 相同点:一旦执行方法,都可以使当前的线程进入阻塞状态
* 不同点:1)两个方法声明的位置不同,Thread类中声明sleep(),objec类中声明wait()
*        2)调用的要求不同:sleep可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步 *           方法中
*        3)关于是否释放同步监视器:两个方法如果都使用在同步代码块或同步方法中,sleep()不会释 *           放,wait()会释放
class Number implements Runnable{
    private int i = 1;
    @Override
    public void run(){
        while(true){
            synchronized (this) {
                notify();
                if(i <= 1000){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+ i);
                    i++;
                    try {
                        //使得调用如下wait()方法的线程进入阻塞状态
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        }
    }
}
public class CommunicationTest {

    public static void main(String[] args) {
        Number number = new Number();
        Thread thread = new Thread(number);
        Thread thread2 = new Thread(number);
        thread.setName("线程一");
        thread2.setName("线程二");
        thread.start();
        thread2.start();
    }
}

银行存钱例子

* 两个储户分别向同一个账户存3000元,每次存1000,存三次。每次存完打印余额
* 分析:
* 1.是否是多线程问题? 是,两个储户线程
* 2.是否有共享数据?有,账户(或者账户余额)
* 3.是否有线程安全问题?有
* 4.需要考虑如何解决线程安全问题?同步机制:有三种
class Account{
    private int balance=0;

    public int addAccount(int fund){
        if(fund > 0) {
            balance += fund;
            return balance;
        }
        return balance;
    }
}

class Bank implements Runnable {
    private Account a = new Account();
    private int account = 0;
    private ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run(){
//        synchronized (this){
//            for (int i = 0; i < 3; i++) {
//                account = a.addAccount(1000);
//                System.out.println(Thread.currentThread().getName()+"存款后,账户余额为:"+ account);
//
//            }
//        }
//
        try {
            lock.lock();
            for (int i = 0; i < 3; i++) {
                account = a.addAccount(1000);
                System.out.println(Thread.currentThread().getName()+"存款后,账户余额为:"+ account);
            }
        } finally {
            lock.unlock();
        }

        //错误的方法
//        for (int i = 0; i < 3; i++) {
//            account = a.addAccount(1000);
//            System.out.println(Thread.currentThread().getName()+"存款后,账户余额为:"+ account);
//
//        }
    }
}
public class BankDemo {
    public static void main(String[] args) {
        Bank bank = new Bank();
        Thread thread = new Thread(bank);
        Thread thread1 = new Thread(bank);
        Thread thread2 = new Thread(bank);
        thread.setName("用户一:");
        thread1.setName("用户二:");
        thread2.setName("用户三:");

        thread.start();
        thread1.start();
        thread2.start();

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值