JavaSE高级开发之多线程(上)

进程知识在面试中占到80%的问题!!

进程与线程的概念

进程:操作系统中一个程序的执行周期称为进程。
线程:一个进程执行多个任务。通常来讲,每一个任务就称为一个线程。
DOS系统属于单进程,许多嵌入式系统也是单进程。
无论进程还是线程,最终的目的是最大限度的提高计算机系统资源的使用效率,提高程序的并发性。

进程与线程的比较:

1.与进程相比,线程更加轻量级,创建,撤销一个线程比启动。撤销一个进程开销小得多。一个进程中的所有线程共享此进程的所有资源。
2.没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。
3.进程是操作系统资源调度的基本单位,进程可以独享资源;
线程需要依托进程提供的资源,无法独立申请操作系统资源,是OS任务执行的基本单位。
地址空间和其他资源:每个进程是占有独立的地址空间,包括代码、数据及其他资源。而属于同一个进程中的线程只能共享这些资源,不具有独立的地址空间和其他资源。

通信:进程直接的通信(简称IPC)开销较大且受到诸多限制,必须具有明确的对象或操作接口并采用统一的通信协议;而线程可以直接读写进程数据段来进行通信,需要进程同步和互斥手段的辅助,以保证数据的一致性,开销较少且简单。

切换:进程间的切换开销较大,而线程间的切换开销较小。

控制表:与进程的控制表PCB相似,线程也有自己的控制表TCP,但是TCP中保存线程状态远少于PCB。

高并发:同一时间段进程的线程访问量非常高。(由于线程过多,开销很大,因此需要部署多个服务器,就被称为分布式。)
高并发带来的问题:服务器内存不够用,无法处理新的请求。
DDos攻击:黑客利用僵尸设备同时对某个进程同时发起请求,因此占用正常用户的访问请求。
并发与并行的区别:并发是逻辑上同时发生,是指在某一段时间内运行多个进程;而并行是在物理上同时发生,是指在某一个时间点同时运行多个进程,即进程是轮换执行的。

程序与进程的区别(面):
动态性与静态性:程序是静态的,而进程是动态的。进程是一个能独立运行的单位,能与其他线程并发执行,程序不能作为一个独立的运行单位。

临时性与永久性:程序是永久的,而进程是临时的。程序可以作为一种软件资源长期保存,而进程仅是运行的程序,就有一定的声生命周期,会动态的产生和消亡。

组成不同:进程包括了程序、数据和PCB(进程控制块)

程序与进程不一定具有一一对应的关系:因为一方面,一个程序可以由多个进程公用,另一方面,一个进程在其获得中可顺序的执行若干个程序。

线程的五个状态:
创建 :使用Th’read类的构造方法创建一个线程,线程的生命状态从创建开始。

就绪状态 :创建成功后调用start方法,线程进入就绪状态。此时没能获取到CPU的时间片段,因为同一时间内单核CPU只能处理一个任务

运行状态 :当就绪状态的线程被调用并获得JVM为其分配的CPU时,程序就进入运行状态,该线程自动执行run()方法里定义的操作和功能。

阻塞状态 :线程因为某些原因放弃CPU,暂时停止运行,进入堵塞状态,线程在运行状态下,调用sleep()、suspend()或wait()等方法,线程都进入阻塞状态。进入阻塞状态的线程不能直接运行,必须再次进入就绪状态争取时间片段

终止 :线程进入死亡状态的情况主要有两种:1.线程的run()方法执行完毕并退出2.线程运行过程中产生异常。处于死亡状态的线程不具有继续运行的能力,会等待GC回收。
在这里插入图片描述

Java多线程的实现:

1.继承Thread类实现多线程

java.lang.Thread是线程操作的核心类。创建一个线程最简单的方法就是直接继承Thread类,然后覆写run()方法(相当于主线程的main()方法),是线程的入口方法。

具有多继承的局限性,业务逻辑写在run方法中,会与进程类耦合,灵活性低,复用性小,违背了开闭原则。

无论哪种方法实现多线程,线程启动一定调用Thread类提供的start方法,而不是run()方法

线程start()方法只能调用一次,多次调用会抛异常java.lang.IllegalThreadStateException
线程调用的顺序于优先级有关。

通过构造方法给Thread重命名:

public Thread(String name) {
        init(null, null, name, 0);
    }

获取线程名:线程对象名.getName();
重命名:线程对象名.setName("xxx");


class MyThread extends Thread{
    String name;
    public MyThread(String name) {
       this.name=name;
    }
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(this.name+i);
        }
    }
}
public class ThreadTest {
   //主线程
    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程1");
        MyThread myThread1 = new MyThread("线程2");
        MyThread myThread2 = new MyThread("线程3");
        myThread.start();
        myThread2.start();
        myThread1.start();

    }
}

当调用start方法(Java)时,会判断当前线程是否新创建的且未被调用过,然后进入start0(Java)方法,进行资源调度,系统分配(JVM)会调用到 JVM_StartThread 方法,利用本地方法创建线程(JVM),然后回调run(JAVA的方法)执行线程的具体操作任务

2.实现Runnable接口

因为Thread是类,因此具有单继承局限,因此引入Runnable接口
是代理模式的实现逻辑,因为Thread类也实现了Runnable接口,通过构造方法将Runnable接口对象传给Thread类public Thread(Runnable target),调用start方法创建多线程。
使用Runnable接口实现多线程的方法可以实现业务逻辑的复用

 public static void main(String[] args) {
        MyThread1 myThread = new MyThread1("线程1");
        MyThread1 myThread1 = new MyThread1("线程2");
        MyThread1 myThread2 = new MyThread1("线程3");
      //1.通过Thread的构造方法执行
        new Thread(myThread).start();
        new Thread(myThread1).start();
        new Thread(myThread2).start();

        //2.匿名内部类
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,new thread");
            }
        };
        new Thread(runnable).start();

        //3.lambda表达式
        new Thread(
                ()-> System.out.println("hello new thread")
        ).start();
    }

3.Thread与Runnable接口的区别

因为Thread是类,因此具有单继承局限
在开发之中使用Runnable还有一个特点:使用Runnable实现的多线程的程序类可以更好的描述出程序共享的概念

使用Thread实现数据共享(产生若干线程进行同一数据的处理操作)

class Thread3 extends Thread{
    private static int tick=10;
    @Override
    public void run() {
        while(this.tick>0){
            System.out.println(this.getName()+"剩余:"+this.tick--+"张票");
        }
    }
}
public static void main(String[] args) {
        //利用Thread实现数据共享
        Thread3 thread1=new Thread3();
        thread1.setName("Thread-A");
        Thread3 thread2=new Thread3();
        thread2.setName("Thread-B");
        thread1.start();
        thread2.start();
	
	//利用Runnable实现
    Runnable runnable=new Runnable() {
        private int ticket=10;
        @Override
        public void run() {
            while(ticket>0){
            //Thread.currentThread()获取当前线程名
                System.out.println(Thread.currentThread()+ "剩余:"+ticket--+"张票");
            }
        }
    };
    new Thread(runnable,"线程一").start();
    new Thread(runnable,"线程二").start();
    }

4.Callable实现多线程

Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候 需要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线程。
Callable中的抽象方法为call(),并且含有返回值,需要通过Future接口的实现类FetureTask类接收Callable对象获得线程执行后的返回值,并且FetureTask类还实现了Runnable接口。
通过Thread类的构造方法接收FetureTask对象启动线程

FetureTask类构造方法 public FutureTask(Callable<V> callable)

超过指定时间则跳过结果(抛异常)继续执行,不会一直等待public V get(long timeout, TimeUnit unit)
获得返回结果public V get()

public class CallableTest implements Callable<String> {
private int tick=10;
    @Override
    public String call() throws Exception {
        while(this.tick>0){
            System.out.println(Thread.currentThread().getName()+"剩余:"+this.tick--+"张票");
        }
        return "票卖完了";
    }

    public static void main(String[] args) {
        Callable<String> callable=new CallableTest();
        FutureTask<String> futureTask=new FutureTask<String>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            //获取任务返回结果(任务结束)
          //  String result=  futureTask.get();

            //超过一秒未返回结果则抛出异常
            String result=  futureTask.get(1,TimeUnit.SECONDS);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        System.out.println("结束");
    }
}

主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的。

线程方法体系图:
在这里插入图片描述

sleep方法

线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。
线程休眠会交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果 当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象
休眠时间使用毫秒作为单位

public static native void sleep(long millis) throws InterruptedException

Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName()+ "执行时间" + LocalDateTime.now());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        new Thread(runnable,"线程2").start();
        new Thread(runnable,"线程3").start();
    }

所有的代码是依次进入到run()方法中的。
真正进入到方法的对象可能是多个,也可能是一个。进入代码的顺序可能有差异,但是总体的执行是并发执行。

线程让步(yield()方法

暂停当前正在执行的线程对象,并执行其他线程,yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield什么时候让是不确定的,因此整个执行过程是随机的。另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时 间,这一点是和sleep方法不一样的。

public static void main(String[] args) {
        //yiled方法 让当前线程回到就绪状态
        Runnable runnable= () -> {
            for(int i=0;i<3;i++){
                Thread.yield();
                System.out.println(Thread.currentThread().getName()+"i="+i);
            }
        };

        new Thread(runnable,"线程一").start();
        new Thread(runnable,"线程三").start();
        new Thread(runnable,"线程二").start();
    }

join()方法

在一个线程中调用其他线程对象的join方法,会阻塞当前线程,去执行其他线程的run方法,执行完毕后再开始执行当前线程。

class  MyRunnable implements Runnable{
    private int tick=1000;

    @Override
    public void run() {
        while(tick>0){
            System.out.println(Thread.currentThread().getName()+"tick="+tick--);
        }
    }
}

public class ThreadMethodJoin {
    public static void main(String[] args) {
    MyRunnable runnable=new MyRunnable();

    //线程
    Thread thread=new Thread(runnable,"thread-A");
    thread.start();

    //在主线程中调用线程的join方法会阻塞主线程,直到调用线程对象的run方法执行完毕,主线程才会继续执行
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("当前线程"+Thread.currentThread().getName());//主线程


    }
}

(面)sleep()方法和wait()方法的异同。

  • 相同点:
  • 执行两个方法都会让出运行权,即让出CPU。
  • 执行两个方法都会让当前线程进入阻塞状态。
  • 不同点:
  • sleep()方法属于Thread类的静态方法,而wait()方法将休眠中的线程唤醒,而执行wait()方法后可以使用notify()方法和notifyAll()方法唤醒等待线程。
  • 若线程T拥有对象O的对象锁,当T执行sleep()方法后,线程T将进入对象O的锁池中,但是不会释放对象O的锁,而执行wait()方法后,线程A将进入对象O的等待权,并释放对象O的锁;而执行wait()方法后,线程A将进入对象O的锁的等待池,并释放O的锁,等待被唤醒后进入O的锁池,载等待获取对象O的锁的运行权。

线程停止

多线程中由三种方法停止线程

1.设置标记位

2.使用stop方法强制使线程退出,但是不安全已被废弃

public class FlagTest {
    public static void main(String[] args) throws InterruptedException {
     //使用标记位方式停止线程
        Runnable runnable=new MyRunnable();
        Thread thread = new Thread(runnable, "标记线程");
        thread.start();
        Thread.sleep(3000);//主线程休眠3秒
      //修改标记位
       ((MyRunnable) runnable).setFlag(false);

        //使用stop强制停止
    //    thread.stop();

    }
}
class MyRunnable implements Runnable{
   private boolean flag=true;
    @Override
    public void run() {
            int i=0;
        while(this.flag){
            try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"第"+ ++i+"次执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

3.使用Thread类的方法interrupt中断线程。

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {

        Runnable runnable=new MyRunnable2();
        Thread thread = new Thread(runnable, "标记线程");
        thread.start();
        Thread.sleep(3000);//主线程休眠3秒
     thread.interrupt();

    }
}

class MyRunnable2 implements Runnable{

    @Override
    public void run() {
            int i=0;
        while(true){
            try {
                //判断线程的中断情况
                boolean interupt=Thread.currentThread().isInterrupted();
             //非阻塞情况
                if(interupt){
                    break;
                }
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"第"+ ++i+"次执行");
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().isInterrupted());
                e.printStackTrace();
                return;
            }
        }
    }


}

interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。
如果,线程的当前状 态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将 中断标志设置为true后,还会有如下三种情况之一的操作:
如果是wait、sleep以及jion三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个 InterruptedException;此时可以通过异常捕获,决定是否退出线程

线程优先级

线程的优先级指的是,线程的优先级越高越有可能 先执行,但仅仅是有可能而已

  1. 高优先级:public final static int MAX_PRIORITY = 10;
  2. 中等优先级:public final static int NORM_PRIORITY = 5; (主线程优先级为5)
  3. 低优先级:public final static int MIN_PRIORITY = 1;

设置优先级:public final void setPriority(int newPriority)

线程是有继承关系的,比如当A线程中启动B线程,那么B和A的优先级将是一样的。

守护线程

java 中有两种线程:用户线程和守护线程。守护线程是一种特殊的线程,它属于是一种陪伴线程。只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当后 一个非守护线程结束时,守护线程才会随着JVM一同停止工作。

主线程main是用户线程,JC垃圾回收器是一种守护线程

设置守护线程public final void setDaemon(boolean on)
设置线程A为守护线程,此语句必须在start方法之前执行

public class ShouHuThread {
    public static void main(String[] args)  {
        //主线程是用户线程
        //System.out.println(Thread.currentThread().isDaemon());//falase

        Thread thread = new Thread(new DeamonRunn(), "线程-A");
        //必须在start前调用
        thread.setDaemon(true);
        thread.start();

        Thread thread1 = new Thread(new DeamonRunn(), "Thread-B");
        thread1.start();
        //主线程休眠
        try {
            Thread.sleep(3000);
            //B是用户线程,还有主线程是用户线程,当所有用户线程都终止守护线程才会终止
            thread1.interrupt();
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //主线程

    }
}
class DeamonRunn implements  Runnable{

    @Override
    public void run() {
        int i=0;
        while(true){
            System.out.println(Thread.currentThread().getName()+"调用第"+ ++i+"次");
            try {
                System.out.println("线程名称:" + Thread.currentThread().getName()
                        + ",i=" + i + ",是否为守护线程:"
                        + Thread.currentThread().isDaemon());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("线程名称:" + Thread.currentThread().getName() + "中断线程 了");
                e.printStackTrace();
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_42962924/article/details/86168345