Java高级特性_01_线程

一、为什么学习线程

1. 应付面试:必问题

——线程的实现方式,解决线程完全问题,线程池

2. 并发编程:多个用户同时执行相同的操作

——实际工作中,项目一定要解决并发问题,保证多个用户可以同时执行操作

——实际开发过程中,很少写线程的代码,这部分代码已经被封装了起来

——使用的多,写得少,必须掌握

二、进程和线程

1. Ip

比如某台计算机连接了某个网络,一台计算机可以有多个ip地址(win+R打开命令提示符,输入ipconfig)

2. 进程

计算机上正在运行的某一个应用程序,是资源(内存,CPU等)分配的基本单位,当在计算机上运行某个应用程序时,操作系统会给当前的应用程序,分配资源,并且创建对应的进程,将进程放入进程队列中,进程调度器会为当前进程分配对应的CPU时间,运行程序。

一个进程可以有多个线程

3. 线程

线程是一条执行线路,是程序执行的最小单位,是进程的一个执行流,是CPU调度和分流的最基本的单位,一个进程可以有多个线程,线程之间共享进程的所有资源,每个线程有自己的内存空间,线程由CPU调度执行,允许多个线程同时运行。

4. 并发

当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。这种方式我们称之为并发(Concurrent)。(同一时间,不能同时执行)

5. 并行

当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。(同一时间,同时执行)

6. 区别

并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。

7. 高并发

登录(同一时间多个人同时登录),商品浏览,订单...(同一时间多个人同时操作)

系统需要在同一时间处理不同的操作,多个操作需要同时执行

高并发(High Concurrency)是现在互联网设计系统中需要考虑的一个重要因素之一,通常来说,就是通过严谨的设计来保证系统能够同时并行处理很多的请求。这就是大家常说的「 高并发 」。

高并发是指多个执行单元同时、并行被执行,而并发的执行单位对于共享资源(硬件资源和软件上的全局变量、静态变量等)的访问很容易导致竞态(race conditions)

8. 进程和线程关系

进程:计算机上正在运行的某一个应用程序

线程:内存空间分配,线程是一条执行线路,是程序执行的最小单位

        一个执行的应用程序就是一个进程(QQ),一个程序中可以包含多个线程(语音+信息)

        一个线程一定属于某一个进程,一个进程可以有多个线程,线程是进程执行的最小单位

        进程分配内存空间,同一进程下的所有线程共享进程的所有资源,同一进程中的所有线程共享代码/数据

        真正运行的是线程

三、多线程


Thread类

特点:

        继承

        可以选择性的重写父类的fun()方法

        run()方法没有返回值,不能抛出异常

        创建的类对象即为线程对象

        this对象是thread对象,可以调用thread类提供的方法

        使用new类()创建对象

        无法实现资源共享

代码

创建一个普通java项目即可

public class MyThread extends Thread{
    private int tickect=5;
    @Override
    public void run() {
        while (tickect>0){
            System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--);
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        // 创建的类对象即为线程对象
        MyThread myThread1=new MyThread();
        //setName 给线程起别名
        myThread1.setName("eee");
        // 使用start()方法启动线程
        myThread1.start();

        MyThread myThread2=new MyThread();
        myThread2.setName("abc");
        myThread2.start();
    }
}

这里用两个人卖车票举例,代码中将MyThread重建了两次,也就是现在有两个线程对象(两个人)进行MyThread类中的卖车票循环,这里先看运行结果

由于是建了两个对象,就相当于是a执行一遍,b又执行了一遍。他们两人各自都卖了五张票,顺序问题不用管,因为是线程在抢运行资源,谁抢到谁运行,所以才会出现这种情况

Runnable接口

特点:

        必须实现run()方法

        run()方法没有返回值,不能抛出异常

        创建的对象就是类对象,不是线程,要使用线程,需要先new Thread(runnable)

        this对象类对象,不可以直接调用Thread类提供的方法

        先创建类对象,再new Thread(类对象)

        创建线程时,类对象可以是同一个,实现资源共享

代码:

public class MyThread implements Runnable{
    private int tickect=5;
    @Override
    public void run() {
        while (tickect>0){
            System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--);
        }
    }
}
public class MyTest {
    public static void main(String[] args) {

        MyThread myThread1=new MyThread();

        Thread thread1=new Thread(myThread1,"test1");
        Thread thread2=new Thread(myThread1,"test2");

        thread1.start();
        thread2.start();

    }
}

运行结果:

这里就是他们两人共卖五张票,这个运行结果每次运行出来的都不一样

Callable接口

特点:

        必须实现call()方法

        call()方法有返回值Object,能够抛出异常

        可以添加泛型,定义call()方法的返回值

        先创建类对象,再创建FutureTask对象,再创建Thread(FutureTask)对象

        start()启动线程,自动调用run()方法,run()方法再调用call()方法,对于同一个FutureTask对象,call()方法只调用一次

代码:

public class MyThread implements Callable {
    private int tickect=5;
    @Override
    public Object call() throws Exception {
        while (tickect>0){
            System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--);
        }
        return "ok";
    }
}
public class MyTest {
    public static void main(String[] args) {
            MyThread myThread3=new MyThread();
            FutureTask<String> futureTask1=new FutureTask<>(myThread3);
            FutureTask<String> futureTask2=new FutureTask<>(myThread3);

            Thread thread1=new Thread(futureTask1);
            Thread thread2=new Thread(futureTask2);

            thread1.start();
            thread2.start();

            //获取返回值
            String s=null;
            try {
                s=futureTask1.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println(s);

    }
}

运行结果如下:

这里没有起别名,Thread-0是线程默认的名字

四、Thread类的常用方法

方法作用
setPriority/getPriority设置优先级,优先级越高,线程优先执行
currentThread()获取当前线程对象
setName/getName设置线程名字
yield()礼让,如果有其他同步执行的线程,礼让其他线程先执行,当前线程后执行;是Thread类的方法,Thread.yield()
sleep(毫秒)休眠,当前线程停止运行,进入休眠状态,到达休眠时长,线程恢复正常运行。;是Thread类的方法,Thread.sleep(1000)
wait()等待,必须在同步代码块中使用,当前线程停止运行,进入等待状态,是Object类的方法,this.sleep(1000)
wait(5000)到达等待时长,线程会自动唤醒,继续向下运行
notify()唤醒,唤醒之前进入等待状态的线程,只唤醒第一个,使线程重新恢复到运行状态
notifyAll()唤醒,唤醒之前进入等待状态的所有线程,使线程重新恢复到运行状态
join()加入,将当前线程加入到父线程中,加入后,父线程会进入wait状态,一直到子线程运行结束后,父线程会继续恢复运行
interrupt()中断线程,检查线程状态,如果线程处于中断状态返回true,如果线程处于非中断状态返回false,并且清除中断状态(将中断状态设置为false)

setPriority()

优先级测试:可以设置1-10不同的优先级,优先级高的线程,有更多的机会(更高的概率)获取到CPU的时间片

public class MyTest {
    public static void main(String[] args) {
        //new 一次
        MyThread myThread1=new MyThread();

        Thread thread1=new Thread(myThread1,"test1");
        //MAX_PRIORITY 最大值
        thread1.setPriority(thread1.MAX_PRIORITY);
        Thread thread2=new Thread(myThread1,"test2");
        //MIN_PRIORITY 最小值
        thread2.setPriority(thread2.MIN_PRIORITY);
        thread1.start();
        thread2.start();

    }
}
public class MyThread implements Runnable{
    private int tickect=5;
    @Override
    public void run() {
        while (tickect>0){
            System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--);
        }
    }
}

Sleep()

线程休眠,会让当前线程处于阻塞状态,指定时间过后,线程就绪状态

public class MyThread implements Runnable{
    private int tickect=5;
    @Override
    public void run() {
        while (tickect>0){
                //Thread.sleep(1000); 睡眠一秒然后运行,这里需要抛出异常
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--);
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        //new 一次
        MyThread myThread1=new MyThread();

        Thread thread1=new Thread(myThread1,"test1");
        Thread thread2=new Thread(myThread1,"test2");
        thread1.start();
        thread2.start();

    }
}

yield()

让步,一旦调用该方法,当前线程的状态就会从运行状态,变成就绪状态,和别的线程重新抢CPU时间片。

public class MyThread implements Runnable{
    private int tickect=5;
    @Override
    public void run() {
        while (tickect>0){
            String name=Thread.currentThread().getName();
            if(name.equals("test1")){
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--);
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        //new 一次
        MyThread myThread1=new MyThread();

        Thread thread1=new Thread(myThread1,"test1");
        Thread thread2=new Thread(myThread1,"test2");
        thread1.start();
        thread2.start();

    }
}

join()

方法,排队,join()方法会使主线程进入等待池并等待t线程执行完毕后才会被唤醒

将a方法设置一个join后运行的话,b就不会再抢运行,直到a运行完以后b才运行

public class MyThread extends Thread{
    private int tickect=5;
    @Override
    public void run() {
        while (tickect>0){
            System.out.println(Thread.currentThread().getName()+"卖出去一张票,还剩"+tickect--);
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        MyThread myThread1=new MyThread();
        myThread1.setName("ned");
        myThread1.start();
        try {
            // join 插队
            myThread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        MyThread myThread2=new MyThread();
        myThread2.setName("abc");
        myThread2.start();
    }
}

interrupt()

中断线程,仅仅发送了一个中断的信号,当碰到wait(),sleep方法时,清除中断标记,抛出异常。

public class MyTest {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.setName("ndt");
        MyThread myThread2 = new MyThread();
        myThread2.setName("test");
        // 第一个线程
        myThread1.start();
        myThread1.interrupt();
        // 第一个线程
        myThread2.start();

    }
}
public class MyTest {
    public static void main(String[] args) {
        //
        MyThread myThread1 = new MyThread();
        myThread1.setName("ndt");
        MyThread myThread2 = new MyThread();
        myThread2.setName("test");
        // 第一个线程
        myThread1.start();
        //中断
        myThread1.interrupt();
        // 第二个线程
        myThread2.start();

    }
}

setDaemon()

        

设置线程为后台(守护)线程。

Java中有两类线程:

        用户线程(User Thread)        

        守护线程(Daemon Thread)

        守护线程 是指程序运行的时候在后台提供了一种通用服务的线程,比如GC垃圾回收线程,这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务。

        两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:

        如果用户线程全部退出离开,只剩下守护线程,虚拟机就会退出。

        如果还有至少一个用户线程,那么虚拟机就不会退出。

public class Shouhu extends Thread{

    @Override
    public void run(){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("守护线程");
    }
}
public class MyTest6 {
    public static void main(String[] args) {
        Shouhu shouhu=new Shouhu();//线程的实例
        shouhu.setDaemon(true);//成为守护线程
        //守护线程是 main(主线程)
        shouhu.start();
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("-----------");
    }
}

wait()/notifyAll()/notify()

        wait() 是针对已经获取对象锁的线程进行操作

        当线程获取对象锁后,调用 wait() 主动释放对象锁,同时该线程休眠

        直到其他线程调用 notify() 唤醒该线程,才继续获取对象锁,并执行

        调用 notify() 唤醒线程并不是实时的,而是等相应的 synchronized 语句块执行结束,自动释放对象锁

        再由 JVM 选取休眠的线程赋予对象锁,并执行,从而实现线程间同步、唤醒的操作

public class WaitTest{
    private static Object object=new Object();

    static class Wait extends Thread{
        @Override
        public void run(){
            synchronized (object){
                System.out.println("当前线程开始等待:"+this.getName());
                try {
                    object.wait(10000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(this.getName()+"等待结束");
            }
        }
    }

   static class Notify extends Thread{
        @Override
        public void run(){
            synchronized (object){
                System.out.println("开始唤醒线程:"+Thread.currentThread().getName());
                //唤醒
                object.notifyAll();//唤醒所有处于等待中的线程
                System.out.println("唤醒结束");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //创建处于等待中的线程
        Wait wait1=new Wait();
        wait1.setName("wait1");
        Wait wait2=new Wait();
        wait2.setName("wait2");

        wait1.start();
        wait2.start();

        //唤醒线程
//        Thread.sleep(10000);

        Notify notify=new Notify();
        notify.start();
    }
}

wait()和sleep()

        sleep必须添加时间,wait可以定义也可以不定义等待时间

        都会中断线程的执行,sleep到达休眠时间后,线程会自动恢复运行状态,wait设置了等待时间,到达等待时间自动恢复运行,未设置需要调用notify方法唤醒线程

        sleep可以直接使用,wait必须和同步代码块一起使用

wait() 和 sleep() 都可以通过 interrupt() 打断线程的暂停状态,从而使线程立刻抛出InterruptedException

        通过 interrupt() 打断线程时,只需在 catch() 中捕获到异常即可安全结束线程

        InterruptedException 是线程内部抛出,并不是 interrupt() 抛出

        当线程执行普通代码时调用 interrupt() 并不会抛出异常,只有当线程进入 wait() / sleep() / join() 后才会立即抛出

        sleep() 是 Thread 的静态方法,wait() 是 Object 的一般方法

notify()和interrupt()

        interrupt:中断线程,会检查线程状态,清除线程的中断状态(sleep,wait),使线程恢复运行

        notify:唤醒线程,将处于等待状态的线程(wait),使线程恢复运行

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值