栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

线程内容一

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

线程内容一

线程 线程的六种状态

说明:一般教科书和很多网上都说是五种,但查阅Java底层代码显示是六种,总体含义差不多,就采用六种方式

新建状态(NEW)

线程对象被创建但未被使用

可运行状态(Runnable)

可能正在运行(Running),也可能在等待CPU时间片(Ready)

阻塞状态(Blocked)

等待获取一个排它锁,如果线程释放了锁,就会结束当前状态

无限期等待(Waiting)

等待其他线程进行显式唤醒,否则不会分配CPU时间片

限期等待(Timed Waiting)

无需等待其他线程进行显式唤醒,在一定时间之后会被系统自动唤醒

死亡状态(Terminated)

一般是线程任务结束之后自己结束,或者是出现异常而结束

线程状态图转换


线程的运行示意图

使用线程的三种常用方法

在了解如何使用线程之前,先理解下线程和非线程的区别

非线程

public class A {
    public static void main(String[] args) {
        for(int i=0;i<5;i++){//循环5次
            System.out.println("A: "+i);
        }

        for(int j=0;j<5;j++){//循环5次
            System.out.println("B: "+j);
        }
    }
}

输出结果:

我们可以发现,两个for循环语句都是在主线程中执行的,他们的执行顺序是串行的,所以不会发生交替现象。

伪多线程

public class B extends Thread{//继承Thread类
    @Override
    public void run(){//重写run方法
        for(int i=0;i<5;i++){//for循环5次
            System.out.println("A: "+i);//打印线程结果
        }
    }

    public static void main(String[] args) {
       B b=new B();
       b.run();
       b.run();
    }
}

输出结果:

这里虽然使用Thread,但并没有实现多线程,只是顺序执行两次而已

继承Thread类

public class A extends Thread{//继承Thread类
    @Override
    public void run(){//重写run方法
        for(int i=0;i<100;i++){//for循环100次
            System.out.println("A: "+i);//打印线程结果
        }
    }

    public static void main(String[] args) {
        A a1=new A();//实例化线程a1
        a1.start();//执行线程方法

        A a2=new A();//实例化线程a2
        a2.start();//执行线程方法
    }
}


输出结果:

我们可以看到,两次执行线程出现交叉现象,说明是多线程程序

实现Runnable接口

public class A implements Runnable{//继承Thread类
    @Override
    public void run(){//重写run方法
        for(int i=0;i<100;i++){//for循环100次
            System.out.println(Thread.currentThread().getName()+"线程: "+i);//打印线程结果
        }
    }

    public static void main(String[] args) {
        A a1=new A();//实例化线程a1
        Thread t1=new Thread(a1);//实例化Thread类,将线程a1作为参数传入
        //通过调用Thread下的方法进行实现
        t1.setName("A");//设置线程名称
        t1.start();//执行线程方法

        A a2=new A();//实例化线程a2
        Thread t2=new Thread(a2);//实例化Thread类,将线程a2作为参数传入
        t2.setName("B");//设置线程名称
        t2.start();//执行线程方法
    }
}



输出结果:

实现Callable接口

public class C {
    public static void main(String[] args) {
        //实例化线程池,初始化线程个数为5
        ExecutorService es= Executors.newFixedThreadPool(5);
        //定义集合保存结果集
        List list=new ArrayList();
        for(int i=0;i<5;i++){//循环遍历5个线程
            //实例化线程对象返回线程所在的name值
            C1 c1=new C1("我的名字:"+Thread.currentThread().getName()+" ");//起名字 main
            //Future表示异步对象计算的结果
            Future future=es.submit(c1);
            try {//返回可能出现的异常
                list.add(future.get());//取出的值存入集合
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        es.shutdown();//调用方法,使线程池立即变为shutdown状态
        //不能再向线程池中添加任何任务

        list.forEach(c->System.out.print(c+" "));//遍历结果集
    }

}
class C1 implements Callable {//定义内部类C1 实现Callable接口
    private String name;//定义变量name
    public C1(String name){//定义构造器返回name的值
        this.name=name;
    }
    @Override
    public String call() throws Exception {//抛出异常
        return this.name;//返回name的值
    }

}

输出结果:

实现接口要优于继承Thread

因为Java不支持多继承,所以继承了Thread类就无法继承其他类,但可以实现多个接口

继承Thread容易造成开销过大

三种创建线程的区别

Runnable接口:

1.将线程的代码和数据分开,形成清晰的模型
2.可以继承其他类

继承Thread类:

1.不能继承其他类
2.编写简单,可以直接操作线程

Callable回调接口:

1.规定方法不同,Callable使用call方法,而Runnable使用run方法

2.Callable可以有返回值,Runnable没有返回值

3.call方法可以抛出异常,而run方法不能抛出异常

线程中的主要方法

Executor

管理多个异步任务的执行,无需程序员显式管理线程的生命周期。

分类有三种:
CachedThreadPool:一个任务创建一个线程

FixedThreadPool:创建固定大小线程

SingleThreadExecutor:创建大小为1的固定线程

sleep

使用Thread.sleep(millsec)方法会休眠当前正在执行的线程,单位为毫秒。

注意:线程中抛出的异常需要在本地进行处理

wait

让当前线程进入等待状态,直到其他线程调用相关方法后,当前线程被唤醒。

notify():唤醒当前对象的等待状态
notifyAll():唤醒所有线程

sleep和wait的区别

1.sleep方法属于Thread类,wait属于Object类

2.sleep方法暂停执行指定的时间,让出CPU给其他线程,在指定时间后会自动恢复运行状态。

3.调用sleep方法过程中,线程不会释放锁,而wait会释放当前锁。

5.使用wait方法时,只有调用notify方法后,该线程才会获取锁,进入运行状态。

yield

暂停当前正在执行的线程对象并执行其他线程。

作用:让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会从而进行适当的轮转执行。

缺点:实际中可能无法达到让步目的,让步的线程还有可能被线程调度程序再次选中。

定义:

Java中的锁主要用于保障并发线程情况下数据的一致性。

为了保障多线程中的数据一致性,通常需要在对象或方法之前加锁。

如果有其他线程也需要使用该对象或方法时,则首先需要获得锁。

如果某个线程发现锁正在被其他线程使用,则会进入阻塞队列等待锁的释放,直到其他线程执行并释放锁后,该线程才能继续进行操作。

可以保证同一时刻只有一个线程拥有锁并修改对象,保证数据的安全。

锁从乐观和悲观的角度可以分为乐观锁和悲观锁

乐观锁:
在读取数据时不加锁,而在更新操作时才加锁。

通常采用在写时读取当前版本号然后加锁

比较当前版本号与上一次版本号,如果一致则进行更新操作,否则将重复进行读写,比较操作

悲观锁:
在读写数据时都要加锁,在别的线程对数据进行操作时会被阻塞,等待直到拿到锁。

自旋锁:
如果持有锁的线程能在自旋等待时间内释放锁资源,则会进入阻塞,挂起状态。当超过最大时间后线程会退出自旋模式并释放锁。

优点:可以减少CPU上下文切换对于锁的占用时间,同时提高性能。

缺点:不适合在系统具有复杂依赖的情况下使用,会造成线程长时间获取不到锁资源,引发CPU浪费。

Synchronized
Synchronized属于独占式悲观锁,可重入锁

Synchronized作用域

1.同步一个代码块

2.同步一个方法

3.同步一个类

4.同步一个静态方法

Synchronized在修饰对象,方法,代码块时,同一时刻只能有一个线程进行访问

从获取资源的公平性角度可以分为公平锁和非公平锁

从是否共享资源的角度可以分为共享锁和独占锁

从锁的状态角度可以分为偏向锁,轻量级锁和重量级锁

ReetrantLock

是一个可重入的独占锁,通过自定义队列同步器实现锁的获取与释放。

可以实现一个线程对统一资源多次加锁的操作。

比较ReentrantLock和Synchronized

1.synchronized是由JVM实现的,而ReentrantLock是由JDK实现的

2.ReentrantLock可以中断,synchronized不可以

3.synchronized是非公平锁,ReentrantLock是公平锁

4.一个ReentrantLock可以同时绑定多个对象

尽量使用synchronized,ReentrantLock不是所有JDK版本都支持,并且synchronized不会导致死锁问题,除非需要使用ReentrantLock的高级功能。

死锁:
两个或两个以上的线程在执行的过程中,由于竞争资源或彼此通信造成的阻塞现象。

例子;A在第一次循环后使用B锁锁住了B,直到下次执行等待锁释放后才能继续,而同时B也锁住了A

这样使得两个线程同时处在挂起状态,等待对方释放锁。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/831047.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号