Java中的多线程

一,初识多线程:

                        程序,进程,线程:


                                ➢程序(program):是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。 (程序是静态的)
                                ➢进程(process):是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 (进程是动态的)是一个动的过程 ,进程的生命周期  :  有它自身的产生、存在和消亡的过程 
                                ➢线程(thread),进程可进一步细化为线程, 是一个程序内部的一条执行路径。
                                 若一个进程同一时间并行执行多个线程,就是支持多线程的。

                     并行和并发:


                                并行:多个CPU同时执行多个任务
                                并发:一个CPU“同时”执行多个任务(采用时间片切换)

                                注:java中的多线程实际上是并发,属于“感觉上的多线程”。

二,创建线程的三种方式:

1继承父类Thread创建线程:

                首先基础Thread:

然后重写其中的Thread的run方法(写入想开辟的线程的工作内容):

public class XianCheng2 extends Thread {
    public XianCheng2(String name) {//副线程的有参构造方法
        super(name);
    }

    public XianCheng2() {//副线程的无参构造
    }
    
       //副线程要执行的内容
    @Override
    public void run() {
        for (int i = 0; i <101 ; i++) {
            System.out.println(super.getName()+"i="+i);
        }
    }

然后在具有main方法的类中调用创造的线程类创建线程:

                注:main方法所在的类,进行的线程称为主线程。

 public static  void main(String[] args) throws InterruptedException {
        //main方法所在的线程称为,主线程
        XianCheng2 num=new XianCheng2();//利用线程类创建线程实例
        num.setName("11");//给创建的线程设置名称
        num.start();//让创建的线程进入准备状态
        /**
         * 主线程要执行的内容
         */
        for (int i = 0; i <101 ; i++) {
            System.out.println(Thread.currentThread().getName()+i);
            if (i==50){
                Thread.sleep(5000);//单位是毫秒
            }
        }
    }

                                        补:创建两个以上线程。可以直接使用继承了线程类的类来直接new对象进行创建。

                        

 public static  void main(String[] args) throws InterruptedException {
        XianCheng2 num=new XianCheng2();//利用线程类创建线程实例
        num.setName("11");//给创建的线程设置名称
        num.start();//让创建的线程进入准备状态
        XianCheng2 num2=new XianCheng2();//创建第二个副线程
        num.start();//让创建的第二个线程进入准备状态
        /**
         * 主线程要执行的内容
         */
        for (int i = 0; i <101 ; i++) {
            System.out.println(Thread.currentThread().getName()+i);
            if (i==50){
                Thread.sleep(5000);//单位是毫秒
            }
        }
    }

2实现Runnable接口创建线程:

                首先实现Runnable接口

 //TestThread实现了这个接口,才会变成一个线程类 
public class TestThread implements Runnable{
    @Override
    public void run() {
        //输出1-10数字:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"----"+i);
        }
    }
}

创建线程:

public class Test {
    public static void main(String[] args) {
        //创建子线程对象:
        TestThread tt = new TestThread();
        Thread t = new Thread(tt,"子线程");
        t.start();
        //主线程里面也是打印1-10数字:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

3实现Callable接口:

首先实现Callable接口:

public class TestRandomNum implements Callable<Integer> {
    /*
    1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型
    2.如果带泛型,那么call的返回值就是泛型对应的类型
    3.从call方法看到:方法有返回值,可以跑出异常
     */
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(10);//返回10以内的随机数
    }
}

创建线程:


    //这是main方法,程序的入口
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //定义一个线程对象:
        TestRandomNum trn = new TestRandomNum();
        FutureTask ft = new FutureTask(trn);//FutureTask:系统自带的方法类
        Thread t = new Thread(ft);
        t.start();
        //获取线程得到的返回值:
        Object obj = ft.get();
        System.out.println(obj);
    }

4,三种线程比较:

        第一种方式:继承父类创建线程:
                                                 缺点:占用父类
                                                 优点:启动线程效率高


         第二种方法:实现Runnable接口:
                                                缺点:启动线程的效率低,速度慢
                                                优点:不占有父类位置,共享资源的能力强,资源不用加static。

         第三种方法:实现Callable接口:

                                                对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法,
                但是这个run方法有不足:

                                        (1)没有返回值
                                        (2)不能抛出异常

基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现Callable接口:

                                        实现Callable接口好处:(1)有返回值  (2)能抛出异常
                                                                   缺点:线程创建比较麻烦

三,线程的生命周期:

新生状态(New):

                当一个线程对象被创建但还没有开始它的执行时,它处于新建状态。在这个阶段,线程对象已经被分配了资源,但尚未调用其 start() 方法启动线程。


就绪状态(Runnable):

                当线程调用了 start() 方法后,线程进入就绪状态。在就绪状态中,线程已经准备好运行,但需要等待CPU调度来执行。多个就绪状态的线程等待系统分配处理器资源,以便在任何给定的时间点只有一个线程运行。


运行状态(Running):

                当线程获取到CPU资源开始执行时,它进入运行状态。在运行状态中,线程正在执行它的任务代码。


阻塞状态(Blocked/Waiting):

                线程在某些情况下会由于某种原因而放弃CPU资源,暂时停止运行。这种状态被称为阻塞状态。线程可能会进入阻塞状态,等待一些条件的发生(如等待I/O完成、等待获取锁、等待条件变量等)。当条件满足时,线程会从阻塞状态转为就绪状态,等待CPU调度执行。

死亡(Terminated/Dead):

                线程完成了它的任务或者因为异常退出了 run() 方法,进入了终止状态。一个处于终止状态的线程不可能再次运行。

四,线程安全:

1同步监视器总结:


总结1:认识同步监视器(锁子)   -----  synchronized(同步监视器){ }

                        
                        1)必须是引用数据类型,不能是基本数据类型
                        2)也可以创建一个专门的同步监视器,没有任何业务含义 
                        3)一般使用共享资源做同步监视器即可   
                        4)在同步代码块中不能改变同步监视器对象的引用 
                        5)尽量不要String和包装类Integer做同步监视器 
                        6)建议使用final修饰同步监视器


总结2:同步代码块的执行过程


                        1)第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码
                        2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open
                        3)第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态
                        4)第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open
                        5)第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)
强调:同步代码块中能发生CPU的切换吗?能!!! 但是后续的被执行的线程也无法执行同步代码块(因为锁仍旧close) 

总结3:其他


                        1)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块 
                        2)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块, 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块

2锁子类型:

        1.0同步代码块

         

  @Override
    public void run() {

            while (true) {
                synchronized (MaiPiao.class) {//同步代码块
                    if (ticket>=1){
                        System.out.println(Thread.currentThread() + "窗口,卖出第" + (ticket--) + "张票");
                    }
                    if (ticket==0){
                        break;
                    }
            }

        }

    }

2.0同步方法:

ublic class BuyTicketThread implements Runnable {
    int ticketNum = 10;
    @Override
    public void run() {
     
        for (int i = 1; i <= 100 ; i++) {
            buyTicket();
        }
   
    }
    public synchronized void buyTicket(){//锁住的是this。同步方法
        if(ticketNum > 0){
            System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
        }
    }
}

3.0 lock锁:

public class BuyTicketThread implements Runnable {
    int ticketNum = 10;
    //拿来一把锁:
    Lock lock = new ReentrantLock();//多态  接口=实现类  可以使用不同的实现类
    @Override
    public void run() {

        for (int i = 1; i <= 100 ; i++) {
            //打开锁:
            lock.lock();
            try{
                if(ticketNum > 0){
                    System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
                }
            }catch (Exception ex){
                ex.printStackTrace();
            }finally {
                //关闭锁:--->即使有异常,这个锁也可以得到释放
                lock.unlock();
            }
        }
       
    }
}

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java多线程实现的原理主要是通过线程对象的创建和启动来实现的。Java实现多线程的方式有四种:继承Thread类、实现Runnable接口、实现Callable接口、使用线程池。下面分别介绍这四种方式的实现原理: 1. 继承Thread类 继承Thread类是实现多线程的一种方式,它的实现原理是创建一个继承自Thread类的子类,并重写run()方法,在run()方法编写线程执行的代码。然后创建该子类的对象,并调用start()方法启动线程。 2. 实现Runnable接口 实现Runnable接口是实现多线程的另一种方式,它的实现原理是创建一个实现了Runnable接口的类,并实现run()方法,在run()方法编写线程执行的代码。然后创建该类的对象,并将其作为参数传递给Thread类的构造方法,最后调用start()方法启动线程。 3. 实现Callable接口 实现Callable接口是实现多线程的一种方式,它的实现原理是创建一个实现了Callable接口的类,并实现call()方法,在call()方法编写线程执行的代码。然后创建该类的对象,并将其作为参数传递给FutureTask类的构造方法,最后调用start()方法启动线程。 4. 使用线程池 使用线程池是实现多线程的一种方式,它的实现原理是创建一个线程池对象,并将任务提交给线程池执行。线程池会自动管理线程的创建和销毁,从而避免了频繁创建和销毁线程的开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值