Java线程学习

线程简介

  • 任务

  • 进程

  • 线程

  • 多线程

    进程:是执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位

    通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位

    注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器;如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

线程实现

  • Thread class 继承Thread类

    1. 自定义线程类继承Thread类
    2. 重写run()方法,编写线程执行体
    3. 创建线程对象,调用start()方法启动线程

    注意:线程开启不一定立即执行,由cpu调度执行

  • Runnable接口 实现Runnable接口

    1. 自定义线程类继承Runnable类

    2. 重写run()方法,编写线程执行体

    3. 创建线程对象,调用start()方法启动线程

    区别

    继承Thread类 实现Runnable接口
    子类继承Thread类具备多线程能力 实现接口Runnable具有多线程能力
    启动线程:子类对象.start() 启动线程:传入目标对象+Thread对象.start()
    不建议使用,避免OOP单继承局限性 推荐使用,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
  • callable接口 实现callable接口(了解

    实现callable接口 需要返回值类型

    重写call方法 需要抛出异常

    //创建执行服务
    ExecutorService ser= Executors.newFixedThreadPool(1);
    //提交执行
    Future<Boolean> r1=ser.submit(t1);
    //获取结果
    boolean rs1=r1.get();
    //关闭服务
    ser.shutdownNow();
    
  • 静态代理

    //真实对象和代理对象都要实现同一个接口
    //代理对象要代理真实角色
    //好处:
    //代理对象可以做很多真实对象做不了的事情
    //真实对象专注做自己的事情
    
  • Lamda表达式

    为什么使用lamda表达式:

    避免匿名内部类定义过多

    使代码更整洁

    留下核心逻辑

    new Thread (()->System.out.println("多线程学习。。。")).start();
    
    ILove love=(int a) ->{
                System.out.println("i love you-->"+a);
            };
    love.love(2);
    

    总结:只有一行则简化为一行,多行则用代码块

    ​ 前提是接口为函数式接口

    ​ 多个参数也可以去掉参数类型,要去得都去掉,且必须加括号

线程状态

  1. new

    Thread t=new Thread(),线程对象一旦创建就进入到了新生状态

  2. 就绪状态

    当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行

  3. 运行状态

    进入运行状态,线程才真正执行线程体的代码块

  4. 阻塞状态

    当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行

  5. dead

    线程中断或者结束,一旦进入死亡状态,就不能再次启动

    方法 说明
    setPriority(int newPriority) 更改线程的优先级
    static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
    void join() 等待该线程终止
    static void yield() 暂停当前正在执行的线程对象,并执行其他线程
    void interrupt() 中断线程,不用这个方式
    boolean isAlive() 测试线程是否处于活动状态
  • 停止线程

    不推荐使用jdk提供的stop(),destroy()方法,推荐线程自己停下来;使用一个标志位进行终止变量,当flag=false,则终止线程运行

    public class TestStop implements Runnable{
        //1.线程中定义线程体使用的标识
        private boolean flag=true;
        
        @Override
        public void run(){
            //2.线程体使用该标识
            while(flag)
        }
    }
    
  • 线程休眠

    指定当前线程阻塞的毫秒数;存在异常InterruptedException;时间达到后线程进入就绪状态;可以模拟网络延时,倒计时等;每一个对象都有一个锁,sleep不会释放锁;

    Thread.sleep(1000);
    
  • 线程礼让

    让当前正在执行的程序暂停,但不阻塞;让线程从运行状态转为就绪状态;让cpu重新调度,礼让不一定成功

    Thread.yield();//礼让
    
  • Join

    join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞;可以想象成插队

    Thread.join();//插队
    
  • 线程观察状态

    Thread.State state=thread.getState();
    
  • 线程优先级

    线程优先级用数字表示,1~10

    Thread.MIN_PRIORITY=1;
    Thread.MAX_PRIORITY=10;
    Thread.NORM_PRIORITY=5;
    //使用以下方式改变或获取优先级
    getPriority.setPriority(int xxx)
    

    优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用,都是看cpu的调度

  • 守护线程(daemon)

    线程分为用户线程守护线程

    虚拟机必须确保用户线程执行完毕,不用等待守护线程执行,如后台记录操作日志,监控内存

    thread.setDaemon(true);//默认为false,表示用户线程,正常的线程都是用户线程
    

线程同步

  • 并发:同一个对象多个线程同时操作

  • 为保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待

  • 同步块方法及同步块

    public synchronized void method(int args[]){}
    

    synchronized方法控制对‘对象’的访问,每个对象一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程阻塞;方法一旦执行,就独占该锁,直到该方法返回才释放,后面被阻塞的线程才能获得这个锁,继续执行

    同步块:

    synchronized(Obj){}
    //Obj称之为同步监视器,推荐使用共享资源作为同步监视器
    
  • 死锁

    某一个同步块同时拥有“两个以上对象的锁”时,就可能发生“死锁”问题。

    产生死锁的四个必要条件:

    1. 互斥条件:一个资源每次只能被一个进程使用
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
    3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
  • Lock(锁)

    ReentrantLock类实现了Lock,它拥有与synchronize相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。

    synchronized与Lock对比:

    ​ lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动 释放。

    ​ lock只有代码块锁,synchronized有代码块锁和方法锁。

    ​ 使用lock锁,jvm将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性

    ​ 优先使用顺序:Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法 体之外)

线程通信问题

java提供了几个方法解决线程之间的通信问题

方法名 作用
wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait方法的线程,优先级别高的线程优先调度

均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常

//练习线程,实现多线程同步下载
public class TestThead02 extends Thread{

    private String url;
    private String name;

    public TestThead02(String url,String name){
        this.name=name;
        this.url=url;
    }

    //下载图片执行体
    @Override
    public void run() {
        WebDownloader webDownloader=new WebDownloader();
        webDownloader.download(url,name);
        System.out.println("下载的文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThead02 t1=new TestThead02("图片地址","1.jpg");
        TestThead02 t2=new TestThead02("图片地址","2.jpg");
        TestThead02 t3=new TestThead02("图片地址","3.jpg");

        t1.start();
        t2.start();
        t3.start();
    }
}

//下载器
class WebDownloader{
    //下载方法
    public void download(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("io异常,download方法出现问题");
        }
    }
}
/*结果:
下载的文件名为:3.jpg
下载的文件名为:1.jpg
下载的文件名为:2.jpg
执行顺序为123,但结果是按照网速为准,谁先下完就是谁
线程不一定执行,CPU安排调度
*/
posted @ 2022-08-03 17:04  尧天天  阅读(35)  评论(0)    收藏  举报