Day08=线程与进程+线程调度+线程启动方法+线程安全问题+同步&锁+线程状态+多线程通信+线程池

线程与进程的区别(面试常考)

  • 本质
    • 进程是内存中运行的应用程序
    • 线程是进程的执行路径,每个进程必须至少一个线程
  • 内存空间
    • 进程拥有独立内存空间
    • 线程共享进程的内存空间,线程私有程序计数器,虚拟机栈,本地方法栈
  • 切换
    • 进程切换要保存、还原上下文,比较慢
    • 线程切换较快

线程调度

有以下几种方法

  • 分时调度
    • 所有线程轮流获得CPU使用权,平均分配每个线程占用CPU的时间(时间片)
  • 抢占式调度
    • 优先级高的线程使用CPU,如果优先级相同就随机,java使用的也是抢占式调度。
    • CPU使用抢占式调度,在多线程间高速切换,看上去就是同一时刻运行多个。能让CPU使用率更高。
      多线程并不能提高程序运行速度,但能提高运行效率

同步与异步

  • 同步 排队执行 效率低但安全
  • 异步 同时执行 效率高但不安全

并发与并行(面试常考)

  • 并发:多个时间同一时间段发生,不管是同时发生还是排队执行
  • 并行:确实在同一时刻发生,同时执行。

多线程实现方法(面试常考)

如何创建多线程?

  1. 继承Thread类,覆写run()方法,创建Thread对象,通过Thread对象的start()来启动
  2. 实现Runnable接口,覆写run()方法,但还是要创建Thread对象,把Runnable对象传给Thread对象(为线程分配任务)。所以跟1差不多。(比1有好处)

1.继承Thread覆写run方法,而且需要用Tread对象的start()来启动任务,在线程里调用run方法

public static void main(String[] args){
        MyThread my = new MyThread();
        my.start();
        for(int i=0;i<5;i++){
            System.out.println("b"+i);
        }
    }
public class MyThread extends Thread{
    @Override
    public void run(){
        //新的执行路径-线程,触发方式不是直接调用run,而是通过thread对象的start()方法来调用
        for(int i=0;i<5;i++){
            System.out.println("a"+i);
        }
    }
}

a和b并发执行,每次输出结果都不一样。
执行流程:

  • main线程开启->main方法执行
    • 开启子线程m(main方法执行时)
      内存区域分配:
  • 每个线程拥有自己的栈空间,共用一份堆内存

2.实现Runnable接口,也是覆写run

public class MyRunnable implements Runnable{
    @Override
    public void run(){
        for(int i=0;i<5;i++){
            System.out.println("a"+i);
        }
    }
}

public static void main(String[] args){
        //1.    创建任务对象Runnable
        MyRunnable r = new MyRunnable();
        //2.    创建线程thread并为其分配任务r
        Thread t = new Thread(r);
        //3.    执行线程
        t.start();
        for(int i=0;i<5;i++){
            System.out.println("b"+i);
        }
    }

与继承Thread相比有优势:

  • 可以多实现,比单继承更灵活
  • 创建任务和线程分配来实现,更适合多个线程同时执行相同任务的情况
  • 任务和线程是分离的,提升健壮性
  • 线程池技术,只接收Runnable类型的任务而不接收Thread类型的线程

通过匿名内部类开启线程

 new Thread(){
            @Override
            public void run(){
                for(int i=0;i<5;i++){
                    System.out.println("a"+i);
                }
            }
        }.start();

守护线程

守护用户线程的线程。

  • 用户线程:一个进程不包含任何存活用户线程,进程结束
  • 守护线程:当最后一个用户线程结束时,守护线程自动死亡
    设置t1为守护线程,main方法结束就结束了
    t1.setDaemon(true);

线程名称

new Thread(new MyRunnable(),"name").start(); 设置线程名称.
Thread.currentThread().getName());获取当前执行线程的名称

线程的6种状态(面试常考)

ENUM Thread.State

  • NEW 尚未启动的线程
  • Runnable 正在执行的线程
  • Blocked 被阻塞 等待监视器锁定
  • Waiting 无限期等待另一个线程执行特定操作 休眠
  • TimedWaiting 等待另一个线程执行最多指定等待时间的操作?
  • Terminated 已退出的线程

线程休眠

Thread.sleep(millis:1000);

线程阻塞

比如要读用户输入,线程会等着,耗时操作,等待用户输入完成。

线程中断(打标记,记得释放资源)

线程是一个独立的执行路径,他的生命周期因其自身决定。否则外部中断可能导致资源没有释放,一直占用。
线程可以打中断标记,它自己会看,如果有就会触发一个异常InterruptedException,try catch来由程序员来决定是否关闭
t1.interrupt();给线程t1添加中断标记。
添加标记之后进入catch块,发现中断标记:

  • wait
  • sleep
  • interrupt
  • interrupted
    可以在异常处理里进行资源释放(交代后事)然后return,就可以让线程自己死亡。

线程安全问题(面试常考)

多个线程同时运行时会发生线程不安全的问题。
解决方法:

  1. 同步代码块 synchronized(锁对象){} 任何对象都可以作为锁存在
  2. 同步方法(1.2.都是隐式锁)
  3. 显式锁Lock

同步代码块(排队执行)

while(true){
     synchronized (o) {
          if (count > 0) {
              System.out.println("卖票");
              try {
                  Thread.sleep(1000);

              } catch (InterruptedException e) {
                   e.printStackTrace();
               }
              count--;
System.out.println(Thread.currentThread().getName() + "剩余:" + count);

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

同步方法

让卖票成功返回true,否则返回false。

public synchronized boolean sale(){
	if(count>0){
		//卖票
		//trycatch sleep1000ms
		count--;
	}else break;
}

显式锁,自己创建锁

private Lock l = new ReentrantLock();//显式锁
@Override
public void run(){
	while(true){
		l.lock();
		if(count>0){
			//卖票
			//trycatch让线程sleep1000ms
		count--;
		}else break;
		l.unlock();//对count改变之后释放锁对象

公平锁和非公平锁

先来先得。

线程死锁

互相等待对方释放资源,占用自己所持资源。

多线程通信

api:

  • notify()唤醒单个线程 notifyall全唤醒

生产者与消费者模型

确保生产者在生产时消费者没有在消费。

  • 生产者生产后,flag=false,先唤醒消费者notifyAll(),再等待wait()
  • 消费者消费后,flag=true,唤醒生产者,等待
  • 用flag控制是否能生产。消费完了才能生产。
  • 生产和消费的方法都需要用synchronized同步
  • wait需要try catch

创建线程的方法->带返回值的线程Callable

功能接口,可以用作lambda表达式或方法引用的赋值目标。主线程可以拿到一个结果。
使用:编写实现Callable接口,实现call方法。

public static void main(String[] args){
	Callable<Integer> c = new MyCallable();
	FutureTask<Integer> task = new FutureTask<>(c);
	new Thread(task).start();
	task.get();//要抛出异常
	//然后执行主线程剩下的代码
}

class MyCallable implements Callable<T>{
	@Override
	public Integer call() throws Exception{
		Thread.sleep(3000);
		return 100;
	}
}
  • task.isDone();//判断子线程是否执行完毕

线程池(池化技术)

线程与任务执行流程:

  • 创建线程
  • 创建任务
  • 执行任务
  • 关闭线程
    如果创建和关闭线程的时间比真正创建和执行任务的时间多得多,会浪费大量时间。频繁创建会降低系统效率。线程池能存储大量线程。
    线程池里的空闲线程可以用来执行任务,变为忙状态。

非定长,缓存线程池

newCachedThreadPool();

  • 判断是否存在空闲线程
  • 存在则使用
  • 不存在 创建并放入线程池,然后使用
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池执行任务
service.execute(new Runnable(){
	@Override...}

定长线程池

newFixedThreadPool(len);

  • 前同
  • 不存在
    • 线程池已满 等待
    • 不满,创建

单线程线程池 长度为1的定长线程池

newSingleThreadExecutor()

  • 空闲则使用,不空闲则等待

周期性任务定长线程池 在某个时机触发时执行

  • 是否空闲,存在则使用
  • 不存在
    • 未满 创建 放入 使用
    • 已满 等待
  • 周期性任务执行时,定时,自动执行
ScheduledExecutorService service = Executors.newScheduledThreadPool(size);
//任务 时长 时长的单位(TimeUnit的常量)
service.schedule(new Runnable(){
	...
	}	
}, delay:5, period:1, TimeUnit.SECONDS);//5s后执行

Lambda表达式

1.8版本引入,属于函数式编程思想,让实现接口的写法更简单
Thread t = new Thread(()->System.out.println(""));
()内就是要传入的参数,(参数)->(方法体);

你可能感兴趣的:(Java基础学习笔记)