多线程——基础

1.程序、进程、线程

程序:用语言编写的一组指令的集合,指一段静态的代码

进程:程序的一次执行过程,或是正在运行的一个程序

线程:一个程序内部的一条执行路径

一个进程可以有多个线程

 

2.实现多线程的方式

继承Thread类

 public class TestThread1 extends Thread{
     @Override
     public void run() {
         for (int i = 0; i < 200; i++) {
             System.out.println("我在看代码——" + i);
        }
    }
 
     public static void main(String[] args) {
         TestThread1 thread1 = new TestThread1();
         thread1.start();
 
         for (int i = 0; i < 1000; i++) {
             System.out.println("我在学习——"+i);
        }
    }
 }

Thread实现了Runnable接口

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

 

实现Runnable接口

 package com.yl.demo;
 
 public class TestThread2 implements Runnable{
 
     private int id;
 
     public TestThread2() {
    }
 
     public TestThread2(int id) {
         this.id = id;
    }
 
     public int getId() {
         return id;
    }
 
     public void setId(int id) {
         this.id = id;
    }
 
     @Override
     public void run() {
         for (int i = 0; i < 200; i++) {
             System.out.println("我在看代码 "+id+" ——" + i);
        }
    }
 
     public static void main(String[] args) {
         TestThread2 t1 = new TestThread2(1);
         TestThread2 t2 = new TestThread2(2);
         new Thread(t1).start();
         new Thread(t2).start();
 
         for (int i = 0; i < 1000; i++) {
             System.out.println("我在学习——" + i);
        }
    }
 }

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

 

对比thread和runnable

相同点:都要重写run方法

不同点:Thread不适合资源共享,Runnable适合,使用Runnable,可以避免java中的单继承限制,代码可以被多个线程共享,代码和数据独立;线程池只能仿佛实现Runnable或Callable类线程,不能直接放入继承Thread的类

 

实现Callable接口

  • 需要返回值类型

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

  • 创建目标对象

  • 创建执行服务

  • 提交执行

  • 获取结果

  • 关闭服务

 package com.yl.demo;
 
 import java.util.concurrent.*;
 
 public class TestThread3 implements Callable {
     private int id;
 
     public TestThread3(int id){
         this.id = id;
    }
 
     @Override
     public Boolean call(){
         for (int i = 0; i < 200; i++) {
             System.out.println("我在学习" + id + "——" + i);
        }
         return true;
    }
 
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         TestThread3 t1 = new TestThread3(1);
         TestThread3 t2 = new TestThread3(2);
 
         //创建执行服务
         ExecutorService ser = Executors.newFixedThreadPool(1);
         //提交执行
         Future f1 = ser.submit(t1);
         Future f2 = ser.submit(t2);
         //获取结果
         boolean rs1 = (boolean) f1.get();
         boolean rs2 = (boolean) f2.get();
         //关闭服务
         ser.shutdownNow();
    }
 }

好处:可以定义返回值、可以抛出异常

 

使用线程池

 

3.对比Thread和Runnable创建方式:

相同点:都要重写run方法

不同点:Thread不适合资源共享,Runnable适合,使用Runnable,可以避免java中的单继承限制,代码可以被多个线程共享,代码和数据独立;线程池只能仿佛实现Runnable或Callable类线程,不能直接放入继承Thread的类

 

4.线程状态

初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 阻塞(BLOCKED):表示线程阻塞于锁。 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。 终止(TERMINATED):表示该线程已经执行完毕。

这6种状态定义在Thread类的State枚举中,可查看源码进行一一对应。

img

 

5.线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数

  • sleep存在异常InterruptedException

  • sleep时间达到后线程进入就绪状态

  • sleep可以模拟网络延时倒计时

  • 每一个对象都有锁,线程不会释放锁

 

sleep()和wait()

sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。wait声明在同步代码块或同步方法中

 

6.线程礼让

  • 让当前正在执行的线程暂停,但不阻塞

  • 将线程从运行状态转为就绪状态

  • 让cpu重新调度,礼让不一定成功,取决于cpu

 

7.线程停止

  • 建议线程正常停止——>利用次数,不建议死循环

  • 建议使用标志位——>设置一个标志位boolean

  • 不要用stop或destroy

 

8.Join

  • join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

  • 可以想象成插队

 

9.线程优先级

  • 优先级范围1-10

  • 优先级越高,分配的资源越多

  • setPriority(int xxx)

  • getPriority()

 

10.守护线程

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

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

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

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

 Thread thread = new Thread(god);
 thread.setDaemon(true); //默认是false

 

11.线程同步

多个线程操作同一个资源

并发:同一个对象被多个线程同时操作

 

加入锁机制synchronized

关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。

synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行就独占该锁,直到该方法返回才释放锁。

 

当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在一下问题:

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

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

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

 

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  • 方法里需要修改的内容才需要锁,锁太多会浪费资源

  1. 普通同步方法(实例方法),锁是当前实例对象this ,进入同步代码前要获得当前实例的锁

  2. 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁

  3. 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

  • 同步方法仍然涉及到同步监视器(锁),只是不需要我们显示的声明

 

synchronized(同步监视器) {

需要同步运行的代码片段

}

同步监视器是java中任意的一个对象,只要保证多个线程看到的该对象是”同一个“,即可保证同步块中的代码是并发安全的

 

可重入实现:

每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。

 

Lock

  • 通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当

  • ReentranLock(可重入锁)实现了Lock,可以显示加锁、释放锁

 

synchronized和Lock的对比

  • Lock是显示锁,synchronized是隐式锁,出了作用域自动释放

  • Lock只有代码块锁,synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,具有更好的扩展性(提供更多的子类)

  • 优先使用顺序:

    • Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(方法体之外)

  • Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  • Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

 

线程池

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用

好处:

  • 提高响应速度

  • 降低资源消耗

  • 便于线程管理:

    • corePoolSize:核心池的大小

    • maxPoolSize:最大线程数

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

线程池相关API: ExecutorService和Executors

  • ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor

  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

 package com.yl.demo;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 public class MyThreadPool {
     public static void main(String[] args) {
         //1.创建服务,创建线程池
         //newFixedThreadPool:参数为线程池大小
         ExecutorService service = Executors.newFixedThreadPool(10);
 
         //2.执行
         service.execute(new myThread());
         service.execute(new myThread());
         service.execute(new myThread());
 
         //3.关闭连接
         service.shutdown();
    }
 
 }
 
 class myThread implements Runnable{
 
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName());
    }
 }

 

 

 

 

 

 

 

posted @ 2020-09-17 19:21  Fabulo  阅读(252)  评论(0)    收藏  举报