多线程学习笔记

1.什么是进程,什么是线程?

进程是一个应用程序,线程是进程的一次执行过程/执行单元。一个进程可以启动多个线程。
对java程序来说,在DOS窗口输入java helloworld回车之后,会先启动JVM,而JVM就是一个进程。JVM启动一个主线程执行main方法,在启动一个垃圾回收线程负责看护。现在的java程序中至少有两个线程并发。
注意:
1.进程A和进程B是独立的,资源不共享。淘宝是一个进程,qq音乐是一个进程。
2.线程A和线程B的堆内存方法区内存共享栈内存独立,一个线程一个栈。
java中之所以有多线程机制,目的就是为了提高程序的执行效率。经典案例:买票。
**思考:**使用多线程机制后,main方法结束,有可能程序不会结束。main方法结束只是主线程结束,主栈空了,其他栈(线程)可能还在压栈弹栈。

2.实现多线程的第一种方式

继承java.lang.Thread,重写run()方法。

public class ThreadTest01 {
    public static void main(String[] args) {
        //main方法,属于主线程
        //新建一个分支线程
        MyThread myThread = new MyThread();
        //start方法启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码完成任务后,瞬间就结束了。
        //启动成功的分支线程会自动调用run方法,run方法在栈底部(压栈),main方法也在栈底部。
        //run方法在分支线程中的地位等同于main方法在主线程中
        myThread.start();
        //注意,如果直接调用run()方法,则不会开辟新的栈空间,运行时依旧是单线程模式
//        myThread.run();
        //主线程中运行
        for (int i=0;i<1000;i++){
            System.out.println("------>>>>主线程"+i);
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        //这段代码运行在分支线程中
        for (int i=0;i<1000;i++){
            System.out.println("------>>>>分支线程"+i);
        }
    }
}

**注意:**直接调用run()方法以及是单线程,需要调用start()方法来开辟新的栈空间才能实现多线程。

3.实现多线程的第二种方式

实现java.lang.Runnable接口,重写run()方法。注意实现Runnable接口的类并不是一个线程,只是一个可运行的类,需要将这个类作为参数放进Thread中,启动线程。

public class ThreadTest02 {
    public static void main(String[] args) {
        //创建一个可运行的对象
        MyRunnable myRunnable = new MyRunnable();
        //将可运行的对象封装成一个线程对象
        Thread thread=new Thread(myRunnable);
        //启动线程
        thread.start();
        for (int i=0;i<1000;i++){
            System.out.println("------>>>>主线程"+i);
        }
    }
}

//这并不是一个线程类,只是一个可运行的类,还不是一个线程  需要将这个类作为参数放进Thread中
class MyRunnable implements Runnable{
    @Override
    public void run() {
        //这段代码运行在分支线程中
        for (int i=0;i<1000;i++){
            System.out.println("------>>>>分支线程"+i);
        }
    }
}

匿名内部类实现:

public class ThreadTest03 {
    public static void main(String[] args) {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<1000;i++){
                    System.out.println("------>>>>分支线程"+i);
                }
            }
        });
        //启动分支线程
        t.start();
        //主线程
        for (int i=0;i<1000;i++){
            System.out.println("------>>>>主线程"+i);
        }
    }
}

4.实现多线程的第三种方式

实现java.util.concurrent包(JUC并发包)中的Callable接口。call()方法发与run()方法类似,但是call()有返回值。

public class MyThread {
    public static void main(String[] args) throws Exception{
        //第一步:创建一个“未来任务类”对象
        //参数非常重要,需要给一个Callable接口的实现类对象
        FutureTask task=new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {//call方法相当于run方法,只不过有线程的返回结果
                System.out.println("call method begin");
                Thread.sleep(1000*5);
                System.out.println("call method end");
                int a=100;
                int b=200;
                return a+b;
            }
        });

        //创建线程对象
        Thread t1=new Thread(task);
        //启动线程
        t1.start();
        //在主线程中获取t1线程返回的值
        Object o = task.get();
        System.out.println(o);
        //main方法这里的代码想要执行必须等待task.get()执行完毕。引起主线程阻塞
        //因为get()方法拿的是t1线程的执行结果,而t1线程的执行是需要时间的
        System.out.println("hello world");
    }
}

5.线程的生命周期

在这里插入图片描述

Java线程具有五中基本状态

新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

6.获取线程对象

获取线程对象,写在哪个线程里面获取的就是该线程的内存地址。

Thread thread = Thread.currentThread();

获取线程对象的名字:

String name = 线程对象.getName();

修改线程对象的名字:

 线程对象.setName("xxx");

完整代码:

public class ThreadTest04 {
    public static void main(String[] args) {
        //获取当前线程对象 返回的是当前线程的内存地址 写在main方法中代表返回的是主线程的地址
        Thread thread = Thread.currentThread();
        //创建线程对象
        MyThread2 thread2=new MyThread2();
        //设置线程的名字
        thread2.setName("Thread2");
        //获取线程的名字
        String name = thread2.getName();
        System.out.println(name);
        thread2.start();
    }
}
class MyThread2 extends Thread{
    @Override
    public void run() {
        //这段代码运行在分支线程中
        for (int i=0;i<1000;i++){
            System.out.println("------>>>>分支线程"+i);
        }
    }
}

7.线程的sleep方法

static void sleep(long millis)
1.静态方法
2.参数是毫秒
3.作用:让当前线程休眠,进入“阻塞状态”,放弃占有的CPU时间片,让其他线程使用。

public class ThreadTest05 {
    public static void main(String[] args) {
        for (int i=0;i<10;i++){
            System.out.println("----每隔一秒输出一个数字----"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

面试题:以下代码是主线程进入休眠还是分支线程进入休眠

public class ThreadTest06 {
    public static void main(String[] args) {
        //创建线程对象
        Thread t=new MyThread3();
        t.setName("t");
        t.start();
        //调用sleep方法
        try {
            //问题:这行代码会让线程t进入休眠状态吗?
            t.sleep(1000*5);//静态方法,在执行时会被转化成Thread.sleep(1000*5);  让当前线程(主线程)进入休眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //五秒之后这里才会执行
        System.out.println("hello world");
    }
}

class MyThread3 extends Thread{
    @Override
    public void run() {
        System.out.println("t线程");
    }
}

8.中断线程睡眠

并不是中断线程,而是中断睡眠!!

public class ThreadTest07 {
    public static void main(String[] args) {
        Thread thread = new Thread(new myRunnable1());
        thread.start();
        //希望5s后  t线程醒来(5秒之后主线程活就干完了)
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断线程t的睡眠
        thread.interrupt();//干扰 会让正在睡眠的线程出异常,直接进入catch语句块  采用的异常处理机制中断睡眠
    }
}

class myRunnable1 implements Runnable{
    //重点:在run里面调用sleep会有异常,该异常不能throws,只能try-catch捕捉
    //因为在父类中run方法没有抛异常,子类不能比父类抛出更宽泛的异常
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"begin");
        try {
            Thread.sleep(1000*60);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"end");
    }
}

9.中断线程的两种方法

1.thread.stop()直接中断线程(过时,不建议使用)
不建议使用的原因:没有保存的数据容易丢失
2.布尔标记做if判断,决定是否要结束run方法(常用,推荐)

public class ThreadTest07 {
    public static void main(String[] args) {
        myRunnable1 myRunnable1 = new myRunnable1();
        Thread thread = new Thread(myRunnable1);
        thread.start();
        //希望5s后  t线程醒来(5秒之后主线程活就干完了)
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断线程t的睡眠
        myRunnable1.run=false;
    }
}
class myRunnable1 implements Runnable{
    //标记
    boolean run=true;
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            if (run){
                System.out.println(Thread.currentThread().getName()+"---->"+i);
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                //中断线程 结束run方法   return之前做好数据保存之类的工作
                return;
            }
        }
    }
}

10.线程调度

1.常见的线程调度模型
抢占式调度模型:根据线程的优先级(java采用),优先级越高抢占的时间片越多。
均分式调度模型:平均分配CPU时间片
2.java提供了哪些方法与线程调度有关?
实例方法:设置优先级

        thread.setPriority(Thread.MAX_PRIORITY);//10
        thread.setPriority(Thread.MIN_PRIORITY);//1
        thread.setPriority(Thread.NORM_PRIORITY);//5

实例方法:获取优先级

thread.getPriority();

静态方法:暂停当前正在执行的线程对象,并执行其他线程,让当前线程从“运行状态”—>“就绪状态”,而sleep()方法是“运行状态”—>“阻塞状态”

Thread.yield();

实例方法:合并线程,当前线程从“运行状态”—>“阻塞状态”

 thread.join();//当前线程进入阻塞,thread执行完毕后,当前线程才可以继续执行
public class ThreadTest07 {
    public static void main(String[] args) {
        myRunnable1 myRunnable1 = new myRunnable1();
        Thread thread = new Thread(myRunnable1);
        thread.start();
        try {
            thread.join();//当前线程进入阻塞,thread执行完毕后,当前线程才可以继续执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i=0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"-main--->"+i);
        }
    }
}

class myRunnable1 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"-thread--->"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

11.Object类中的wait()和notify()【生产者消费者模式】

1.wait和notify不是线程对象的方法,任何一个java对象都拥有这两个方法,不能通过线程对象来调用。
2.wait()方法的作用?

 Object o=new Object();
        o.wait();//让正在o对象上活动的线程进入无限等待状态,直到被唤醒

3.notify()方法的作用?

Object o=new Object();
        o.notify();//将正在o对象上等待的线程唤醒

生产者和消费者模式

12.生产者消费者模式代码实现

1.使用wait()和notify()实现生产者和消费者模式。
2.什么是“生产者和消费者模式”
生产线程负责生产,消费线程负责消费。生产线程和消费线程要达到均衡,这是一种特殊的业务需求,在这种特殊的情况下要使用wait()和notify()方法。

3.wait()和notify()方法不是线程对象的方法,是普通java对象都有的方法。
4.wait()和notify()方法建立在线程同步的基础上,因为多线程要同时操作一个仓库

生产者线程:

class Producer extends Thread{
    private List list;

    public Producer(List list) {
        this.list = list;
    }
    @Override
    public void run() {
        //死循环,一直生产
        while (true){
            //给仓库对象List加锁
            synchronized (list){
                //说明仓库有值  生产一个消费一个
                if (list.size()>0){
                    try {
                        //当前线程进入等待状态,并释放之前占有的对象锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //程序能够进行到这里说明仓库是空的,所以就可以生产
                Object o=new Object();
                list.add(o);
                System.out.println(Thread.currentThread().getName()+"--->"+o);
                //唤醒消费者进行消费
                list.notify();
            }
        }
    }
}

消费者线程:

class Custom extends Thread{
    private List list;

    public Custom(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        //一直消费
        while (true){
            synchronized (list){
                //仓库已空,消费者等待
                if (list.size()==0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //执行到这里 说明仓库有值,可以消费
                Object remove = list.remove(0);
                System.out.println(Thread.currentThread().getName()+"---->"+remove);
                //唤醒生产者进行生产
                list.notify();
            }
        }
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        List list=new ArrayList();
        //创建两个线程对象
        //生产者线程
        Thread producer=new Producer(list);
        //消费者线程
        Thread custom=new Custom(list);
        producer.setName("生产者线程");
        custom.setName("消费者线程");
        producer.start();
        custom.start();
    }
}

13.使用ExecutorService、Callable、Future实现由返回结果的多线程

可返回值的任务必须实现Callable接口,无返回值的任务必须实现Runnanle接口。执行Callable任务后,可以获取一个Future对象,在该对象调用get就可以获取到Callable任务返回的Object了。再结合线程池接口ExecutorService就可以实现传说中的有返回结果的多线程了。

public class MyCallable implements Callable<Object> {
    private String taskSum;

    MyCallable(String taskSum){
        this.taskSum=taskSum;
    }
    @Override
    public Object call() throws Exception {
        System.out.println("==========taskSum任务启动===============");
        Date date1 = new Date();
        Thread.sleep(10000);
        Date date2 = new Date();
        long time = date2.getTime() - date1.getTime();
        System.out.println("==========taskSum任务结束===============");
        return taskSum+"任务返回执行结果,任务花费时间:"+time;
    }
}
public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("================程序开始运行===============");
        Date date1 = new Date();
        // 线程池的线程数量
        int taskSize = 5;
        // 创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        // 创建有多个返回值的任务
        ArrayList<Future> futures = new ArrayList<>();
        for (int i=0;i<taskSize;i++){
            // 创建Callale对象
            Callable myCallable = new MyCallable(i + " ");
            //执行任务并获取Futrue对象
            Future submit = pool.submit(myCallable);
            futures.add(submit);
        }
        //关闭线程池
        pool.shutdown();
        // 获取所有并发任务的执行结果
        for (Future future : futures) {
            // 从Future对象上获取任务的返回值,并打印
            System.out.println("=====返回值=====");
            System.out.println(future.get().toString());
        }
        Date date2 = new Date();
        System.out.println("程序运行结束,耗时:"+(date2.getTime()-date1.getTime()));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值