java—学习笔记之多线程(线程与进程、三种创建线程方式、线程安全问题、四种线程池)

一、什么是进程,什么是线程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程,线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

二、线程调度

分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)。
Java使用的为抢占式调度。CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,因为不论线程有多少,CPU都只能一个一个的去执行,但多线程程序能够提高程序运行效率,让CPU的使用率更高。

三、同步与异步

同步:同时执行 , 效率高但是数据不安全(可能发生哄抢)。
异步:排队执行 , 效率低但是安全(不会发生哄抢)。

四、并发与并行

并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。

五、JAVA多线程的三种实现方式(面试)

(1)、继承Thread类(无返回值)。通过继承Thread类来创建并启动多线程的一般步骤如下:
1)、定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2)、创建Thread子类的实例,也就是创建了线程对象
3)、启动线程,即调用线程的start()方法

public class MyThread extends Thread {
    //run方法就是线程要执行的任务方法
    @Override
    public void run() {
        //这里的代码就是一条新的执行路径
        //这个执行路径是触发方式,不是调用run方法,而是通过thread对象的start方法来启动任务
        for (int i = 0; i < 10; i++) {
            System.out.println("锄禾日当午"+i);
        }
    }
public class Main {

  public static void main(String[] args){
    new MyThread().start();//创建并启动线程
  }
  
}

其常用方法如下:
1)、thread.start() 启动线程;
2)、stop() 停止方法一停用,此方法会导致线程不安全(例如可能内部还没有完成清理垃圾,却被外部用stop()结束了)。
3)、sleep(long millis) 休眠millis毫秒;
4)、Thread.currentThread() 获取当前线程;
5)、Thread.currentThread().getName() 获取当前线程名称。

(2)、实现Runnable接口(无返回值)。通过实现Runnable接口创建并启动线程一般步骤如下:
1)、定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体。
2)、创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象。
3)、通过调用线程对象的start()方法来启动线程。代码示例:

public class MyRunnable2 implements Runnable{//实现Runnable接口
    @Override
    public void run() {
        //线程的任务
        for (int i = 0; i < 10; i++) {
            System.out.println("锄禾日当午"+i);
    }
}
public class Main {

  public static void main(String[] args){//创建并启动线程
    MyThread2 myThread=new MyThread2();
    Thread thread=new Thread(myThread);

    thread().start();//或者    new Thread(new MyThread2()).start();

  }

}

(3)、带返回值的线程Callable。可以主线程与支线程并发执行,也可以等支线程执行后再执行主线程。返回的执行结果需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。Callable使用步骤如下:
1)、 编写类实现Callable接口 , 实现call方法

class XXX implements Callable<T> {
@Override
     public <T> call() throws Exception {
       return T;
     }
}

2).、创建FutureTask对象 , 并传入第一步编写的Callable类对象。

FutureTask<Integer> future = new FutureTask<>(callable);

3).、通过Thread,启动线程

new Thread(future).start();

(4)、实现Runnable与继承Thread相比的优势
1)、通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况;
2)、可以避免单继承所带来的局限性;
3)、任务与线程本身是分离的,提高了线程的健壮性;
4)、后续学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的任务。

(5)、Runnable 与 Callable
1)、Runnable 与 Callable的相同点:都是接口;都可以编写多线程程序;都采用Thread.start()启动线程。
2)、Runnable 与 Callable的不同点:Runnable没有返回值;Callable可以返回执行结果;Callable接口的call()允许抛出异常;Runnable的run()不能抛出。

六、线程安全与通信问题

(1)、线程阻塞
所谓线程阻塞就是所有比较消耗时间的操作。

(2)、线程的中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定,而不是外界强行结束。

(3)、如何安全的结束线程
解决方法:给线程加上中断标记(thread.interrupt()加标记),线程发现中断标记后会自动跳到try_catch块中的catch块,所以只需在catch块中加上“return;”,由线程程序自行判断后自杀。代码实例:

package thread;
public class Demo5 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //给线程t1添加中断标记
        t1.interrupt();
    }

    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    System.out.println("发现了中断标记,线程自杀");
                    return;
                }
            }
        }
    }
}

运行结果:
在这里插入图片描述
(4)、线程安全问题(多个线程同时抢夺一个数据导致不安全)。解决方法如下(使用加锁机制):
1)、同步代码块:格式:synchronized(锁对象){代码块 };
2)、同步方法:把需排队的代码弄成一个方法锁,方法如果是static,这个同步锁是当前类级别的,而非静态用的锁是this。
3)、显示锁 Lock 子类 ReentrantLock。同步代码块和同步方法都是属于隐式锁。

(5)、线程死锁问题
什么叫线程死锁?假如A线程等B线程,B线程也在等A线程,程序就卡在这里了,这就叫线程死锁。如何避免:不要在锁中调用另一个锁,避免套上加套,混乱不清。

(6)、多线程通信问题
Java中Object类中给出了一些解决线程间通信问题的方法。
1)、void wait() 使线程休眠等待唤醒。与sleep()不同,sleep()是休眠多久时间自动唤醒,而wait()是由外部线程唤醒。
2)、void notifyAll() 唤醒所有由wait()方法休眠的线程。
3)、void notify() 随机唤醒一个由wait()休眠的线程。

七、四种线程池 Executors(面试)

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。Java中的四种线程池ExecutorService:

(1)、缓存线程池(长度无限制)

//执行流程:1. 判断线程池是否存在空闲线程;
//        2. 存在则使用;
//        3. 不存在,则创建线程 并放入线程池, 然后使用。
//运行效果(线程顺序不定):
        /*pool-1-thread-3锄禾日当午
        pool-1-thread-2锄禾日当午
        pool-1-thread-1锄禾日当午*/
//创建一个缓冲池对象
ExecutorService service = Executors.newCachedThreadPool();
 //向线程池中 加入 新的任务
 //线程一
 service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }

        });
        //线程二
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }

        });
        //线程三
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }

        });

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

(2)、定长线程池(长度是指定的数值)

/*执行流程
        1 判断线程池是否存在空闲线程
        2 存在则使用
        3 不存在空闲线程  且线程池未满的情况下  则创建线程  并放入线程池中  然后使用
        4 不存在空闲线程  且线程池已满的情况下  则等待线程池的空闲线程
    **/
    /*运行结果(线程顺序不定):
    1、先出现pool-1-thread-1锄禾日当午
            pool-1-thread-2锄禾日当午
    2、几秒后出现pool-1-thread-1锄禾日当午
    */
ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });

(3)、单线程线程池(长度为一的线程池)

/*执行流程
        1 判断线程池的那个线程是否空闲
        2 空闲则使用
        3 不空闲则等待它空闲后再使用
    **/
/*
运行结果:
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午
*/
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });

(4)、周期性任务定长线程池

/*
执行流程:
        1 判断线程池是否存在空闲线程
        2 存在则使用
        3 不存在空闲线程  且线程池未满的情况下  则创建线程  并放入线程池中  然后使用
        4 不存在空闲线程  且线程池已满的情况下  则等待线程池的空闲线程
   
执行结果:pool-1-thread-1锄禾日当午
    */
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        //定时执行一次线程
        //参数1:定时执行的任务(run)
        //参数2:时长数字(5)
        //参数3:时长数字的时间单位(TimeUnit.SECONDS)    Timeunit的常量指定
       scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        },5, TimeUnit.SECONDS);      //5秒钟后执行
   /*
    周期性任务执行时:定时执行, 当某个时机触发时, 自动执行某任务。
        周期性执行任务
            参数1:任务
            参数2:延迟时长数字(第一次在执行上面时间以后)
            参数3:周期时长数字(每隔多久执行一次)
            参数4:时长数字的单位
        * **/
       /* scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        },5,1, TimeUnit.SECONDS);*/
    }

注意:线程执行完后程序不会自动停止,它会一直等待停止指令,或者是直到一定的时间后才会自动停止程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值