多线程基础

程序进程线程

在操作系统中的运行的程序就是进程,而进程通常采用多进程执行

  • 程序

    指令和数据的有序集合,本身没有任何运行的含义,是静态概念

  • 进程

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

  • 线程

    一个进程通常包括多个线程,线程是CPU调度和执行的单位

 

run()方法是需要执行的方法体

start()方法是开启多线程的方法体

 

Thread 类

不建议使用:避免OOP单继承局限性

1.thread使用方法

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

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

  3. 主线程中创建线程对象,使用start方法启动线程

 

public class TestThread01  extends Thread{
    @Override
    public void run() {
        //在run()方法中编写执行体
        for (int i = 0; i < 100; i++) {
            System.out.println("在子线程中执行--" + i);
        }
    }

    public static void main(String[] args) {
        //在主线程中创建线程对象,使用start方法启动线程
        TestThread01 testThread = new TestThread01();
        testThread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("在主线程中执行---" + i);
        }

    }
}

public class TestThread02 extends Thread{

    private String url;
    private String filename;

    //构造函数传入所需的url和filename
    public TestThread02(String url, String filename) {
        this.url = url;
        this.filename = filename;
    }

    @Override
    public void run() {
        //调用downLoader来获取文件
        webDownLoader webDownLoader = new webDownLoader();
        webDownLoader.downLoader(url, filename);
        System.out.println(filename + "下载完成");
    }


    public static void main(String[] args) {
        //传入三张图图片的url
        TestThread02 t1 = new TestThread02("https://i0.hdslb.com/bfs/album/" +
                "481ba607ba31e9932b90e383f3698fec4c1d9577.jpg@518w_1e_1c.jpg", "pic01.jpg");
        TestThread02 t2 = new TestThread02("https://i0.hdslb.com/bfs/album/" +
                "98d71302c030aa86258eb17a5db084bfadf8ff39.jpg@518w_1e_1c.jpg", "pic02.jpg");
        TestThread02 t3 = new TestThread02("https://i0.hdslb.com/bfs/album/" +
                "70849f611883c3e7feffc730c4f1e7b7173c9695.jpg@518w_1e_1c.jpg", "pic03.jpg");

        //开始多线程下载
        t1.start();
        t2.start();
        t3.start();
    }
}

class webDownLoader
{
    /**
     * 通过url获取文件内容,并保存
     * @param url
     * @param name
     */
    public void downLoader(String url, String name)
    {

        try {
            //调用Commons_io包里面的方法copyURLToFile
            FileUtils.copyURLToFile(new URL(url),  new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downLoader出现问题");
        }
    }
开启三个线程,执行了三条下载路径。

Runnable 接口

 推荐使用:避免单继承的局限性,方便同一个对象被多个进程调用

  1. 自定义MyRunable类实现Runable接口

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

  3. 主线程中创建线程对象,使用start方法启动线程

public class TestThread03 implements Runnable{
    @Override
    public void run() {
        //在run()方法中编写执行体
        for (int i = 0; i < 100; i++) {
            System.out.println("在子线程中执行--" + i);
        }
    }

    public static void main(String[] args) {
        //创建Runable接口的实现类对象
        TestThread03 testThread = new TestThread03();
        //创建线程对象,通过线程对象来开启线程,也就是代理
        Thread thread =  new Thread(testThread);
        thread.start();

        //也可以缩写为:
        //new Thread(testThread).start();

        for (int i = 0; i < 100; i++) {
            System.out.println("在主线程中执行---" + i);
        }

    }
}

1.Callable使用方法

通过服务提交线程

  1. 实现Callable接口,需要返回值类型

  2. 重写call方法, 需要抛出异常

  3. 创建目标对象

  4. 创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);

  5. 提交执行: Future result1 = ser.submit(t1);

  6. 获取结果: boolean r1 = result1.get()

  7. 关闭服务: ser.shutdownNow();

 

public class TestCallable01 implements Callable<Boolean> {
    private String url;
    private String filename;

    //构造函数传入所需的url和filename
    public TestCallable01(String url, String filename) {
        this.url = url;
        this.filename = filename;
    }

    @Override
    public Boolean call() {
        //调用downLoader来获取文件
        webDownLoader webDownLoader = new webDownLoader();
        webDownLoader.downLoader(url, filename);
        System.out.println(filename + "下载完成");
        return true;
    }


    public static void main(String[] args) {
        //传入三张图图片的url
        TestCallable01 t1 = new TestCallable01("https://i0.hdslb.com/bfs/album/" +
                "481ba607ba31e9932b90e383f3698fec4c1d9577.jpg@518w_1e_1c.jpg", "pic01.jpg");
        TestCallable01 t2 = new TestCallable01("https://i0.hdslb.com/bfs/album/" +
                "98d71302c030aa86258eb17a5db084bfadf8ff39.jpg@518w_1e_1c.jpg", "pic02.jpg");
        TestCallable01 t3 = new TestCallable01("https://i0.hdslb.com/bfs/album/" +
                "70849f611883c3e7feffc730c4f1e7b7173c9695.jpg@518w_1e_1c.jpg", "pic03.jpg");

        //创建执行服务(参数为线程池的大小)
        ExecutorService service = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> r1 = service.submit(t1);
        Future<Boolean> r2 = service.submit(t2);
        Future<Boolean> r3 = service.submit(t3);

        //获取结果(不写应该也是可以的,这里应该是作为一种检验参数)

        //这里会发生阻塞。
        try {
            boolean rs1 = r1.get();
            boolean rs2 = r2.get();
            boolean rs3 = r3.get();

            System.out.println(rs1);
            System.out.println(rs2);
            System.out.println(rs3);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        //关闭服务
        service.shutdown();

    }
}

 

 

停止线程

  • 不推荐使用JDK提供的stop()、destroy()。

  • 推荐让线程自己停下来,但也不建议使用死循环

  • 建议使用一个标志位作为终止变量,当flag==false时,线程终止运行

public class TestStop implements Runnable{

    private boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        while(flag)
        {
            System.out.println("子进程执行中---" + i++);
        }
    }

    //设置个公开方法,利用标志位停止线程
    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for (int i = 0; i < 100; i++) {
            System.out.println("主进程执行中---" + i);

            if(i == 90){
                testStop.stop();
                System.out.println("子进程结束运行");
            }
        }
    }
}

 线程休眠

       

 

 

Join

join合并线程,只能是当前线程执行完之后才能执行其他线程,对其他线程造成阻塞

       

 

 

 

public class TestState {
    public static void main(String[] args) {
        Thread thread  = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("******");
            }
        });

        //启动前
        Thread.State state = thread.getState();
        System.out.println(state);//NEW

        //启动后
        thread.start();
        state = thread.getState();
        System.out.println(state);//RUNABLE

        //阻塞时与结束时
        while(state != Thread.State.TERMINATED){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            state = thread.getState();
            //TIMED_WAITING或者TERMINATED
            System.out.println(state);
        }
    }
}

 

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

  • 线程的优先级用数字表示,范围从1~10.

    • Thread.MIN_ PRIORITY= 1;

    • Thread.MAX PRIORITY = 10;

    • Thread.NORM_ PRIORITY= 5;

  • 使用以下方式获取或改变优先级

    • getPriority() ,setPriority(int xxx)

注意:

  1. 先设置优先级再启动

  2. main方法的默认优先级为5

  3. 理论上来说优先级越高的越先执行,哪怕他start更晚

守护线程(daemon)

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

  • 虚拟机必须确保用户线程执行完毕.

  • 虚拟机不用等待守护线程执行完毕

  • 如,后台记录操作日志,监控内存,垃圾回收等待…

public class TestDaemon {
   public static void main(String[] args) {
       God god = new God();
       You you = new You();

       Thread thread = new Thread(god);
       //将上帝设置为守护线程,在false的时候为用户线程
       thread.setDaemon(true);
       thread.start();

       //开启一般的用户线程
       new Thread(you).start();
   }
}


class God implements Runnable{

   @Override
   public void run() {
       //上帝永生,无限循环
       while (true)
           System.out.println("上帝在你身旁");
   }
}


class You implements Runnable{

   @Override
   public void run() {
       //人生有限,100年后撒手人寰
       for (int i = 0; i < 100; i++) {
           System.out.println("happy everyday");
       }
       System.out.println("goodbye world");
   }
}

 

 上帝明明是个死循环,但是进程还是结束了。

因为这里将上帝线程设置为了守护线程,虚拟机不加以考虑

四、线程同步

并发:同一个对象被多个线程同时操作,也就是不同线程同时操作同一个资源地址,造成数据紊乱

同步:多个需要同时访问资源的线程进入对象的等待池,等待前面线程使用完毕

锁:每个对象都有把锁,当获取对象时,独占资源,其他线程必须等待,使用结束后才释放

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起

  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题

  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

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

public class TestLock {

    public static void main(String[] args) {
        TestLock2 testLock = new TestLock2();

        new Thread(testLock, "A").start();
        new Thread(testLock, "B").start();
        new Thread(testLock, "C").start();
    }

}

class TestLock2 implements Runnable{
    int count = 1000;

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while(true)
        {
            try {
                //进入加锁状态
                lock.lock();
                if(count > 0)
                    System.out.println(Thread.currentThread().getName() + "---" +count--);
                else
                    break;
            }
            finally {
                //解锁
                lock.unlock();
            }
        }
    }
}

在不加锁的情况下,ABC可能 会同时操作到count,导致数据紊乱

在加了锁之后,ABC排队操作

线程协作

生产者消费者模式

有一说一这个我们上个学期的操作系统讲的很详细了

生产者与消费者共享一个资源,同时生产者与消费者相互依赖互为条件

  • 生产者访问仓库,往里面放;消费者也要访问,但是只拿

主要是用以下方法达到通信的效果:

  1. wait():先释放资源,并开始等待

  2. wait(long timeout):释放资源等待timeout秒

  3. notify():唤醒一个处于等待状态的线程

  4. notifyAll():唤醒同一个对象上所有调用了wait()方法的线程,优先级高的优先调度

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具

  • 好处:

    • 提高响应速度(减少了创建新线程的时间)

    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

    • 便于线程管理

      • corePoolSize:核心池的大小

      • maximumPoolSize: 最大线程数

      • keepAliveTime: 线程没有任务时最多保持多长时间后会终止

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值