线程

线程

进程 在操作系统上(OS),一个独立运行的任务被称为进程 进程是可以并发执行(即多个进程可以同时运行)

线程是进程中 ,多个并发执行的任务逻辑。线程是进程的组成单位,一个进程至少要有一个线程 原因是进程任务实际的执行者是线程 类比 进程–小组 线程–小组成员 任务是分配给小组的 但实际执行小组任务的是小组成员

一个进程的任务实际上是又(1~n)个线程来完成的,对于java来说,JVM相当于操作系统上运行的进程,JVM一定会包含一个线程被称为主线程,而main函数就是由主线程来执行的

多线程在宏观上是并行,微观上是串行

一.线程得组成

1.CPU:一个线程进行执行时 需要使用CPU CPU具有时间片 哪个线程获得时间片哪个线程去使用CPU 使用CPU得时间由时间片决定	时间片的分发和管理由操作系统负责
2.数据:
    每个线程拥有自己得JVM栈 栈空间独立:每个线程执行逻辑中方法得调用都是独立得
    所有线程共享同一个堆空间 堆空间共享:每个线程中产生得数据如果是放置在堆空间中的 那么的都是共享的 比如创建得对象

二.线程的创建 非常重要

对于其他线程的创建与开启,一般要依赖于某个线程(主线程)所以创建线程开启线程的代码,往往要放置在主函数里

1.先创建任务对象 在创建线程对象
a.定义任务类 Runnable接口的实现类就是任务类
class MyTask implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		//任务代码
		for(char c='A';c<='Z';c++){
			System.out.println(c);
		}
	}
	
}
b.创建任务对象
MyTask task1 = new MyTask();

前两步操作也可以使用Lambda与匿名内部类来完成

c.创建线程对象 并将任务提交给线程
Thread t1 = new Thread(task1);

d.开启线程 调用线程的start方法
t1.start();
2.创建一个自带任务的线程
a.自定义一个线程类 写一个类 继承Thread 重写父类的run方法  run方法中写的就是该自定义线程类 自带的任务
class MyThread extends Thread{
	@Override
	public void run(){
		//自带的任务代码
		for(char c='A';c<='Z';c++){
			System.out.println(c);
		}
	}
}
b.创建自定义线程类对象 并开启线程
MyThread t1 = new MyThread();
t1.start();

这两步 可以使用匿名内部类直接搞定 但是不能用Lambda

三.线程的状态 面试常问

在这里插入图片描述

1.线程的官方状态
1.NEW
创建一个线程 而没有启动时
2.RUNNABLE
可以获得时间片 或 正在执行时 这两种状态都是RUNNABLE可运行状态
3.BLOCKED
阻塞状态,进入到阻塞状态线程会放弃时间片,且不再参与时间片的争夺
4.WAITING
无限期等待,当线程被其他线程加队时(执行了其他线程对象的join方法时) 会进入到该状态
5.TIMED_WAITING
有限期的等待 当线程执行了Thread.sleep方法时 会让当前线程进入到有限期等待
6.TERMINATED
当线程任务执行完毕时 进入到终止状态
2.改变线程状态的方法
a.Thread.sleep(毫秒数);必须掌握
    如果某个线程在执行时 执行了该方法 那么该线程就会进入到睡眠,放弃自己的时间片,在有限时间中不在参与时间片的争夺 不会放弃锁标记

b.线程对象.join();可以不会
    在一个线程任务中,让某个线程对象调用join时 会让调用该方法的线程加队到当前线程之前,当前线程进入到无限期等待,并放弃时间片,不在参与时间片的获取
    例:比如线程A的任务中 执行 了B.join() 此时 A会主动放弃时间片,不参与到时间片的争夺,进入到无限期等待,什么时候B的任务执行完 什么时候A从无限期等待恢复到RUNNABLE状态

四.Java中的线程池

线程池 线程池存放了一定数量的线程 这些线程可以重复的被利用 不必频繁的创建与销毁

好处:不用频繁的创建和销毁线程,节省系统资源,提高效率

1.线程池的类型
a.线程池的顶级接口:Executor
b.线程池的常用类型:ExecutorService
c.线程池官方实现类:ThreadPoolExecutor  
不推荐使用该类创建线程池实例  如果有公司要求那么按照公司规范进行创建
线程池构造的7个参数 需要在出去面试前 背会
2.创建线程池对象
a.获取java中接口类型的对象:
    1.使用官方提供的实现类创建对象
    2.自己书写实现类创建对象
    3.调用官方的工厂方法,直接获取接口类型的对象,而不去自己手动创建

b.什么是工厂方法?
    工厂方法的实现,是使用某种手段创建一个接口类型的对象,该方法会给调用者返回值一个接口类型实现类的对象,而让调用者忽略创建实现类对象的过程
    
c.获取线程池对象的两个常用线程池方法
    线程池相关的工厂方法 都被放置在Executors的工具类中
    
    static ExecutorService newCachedThreadPool() 
    会返回一个缓存机制的线程池:线程池在创建时不包含任何的线程,当有新任务提交时,会验证是否有闲置的线程,如果有就把任务给闲置线程,如果没有将新创建一个线程接收任务,一个线程在完成任务后,会等待60秒,当60等待期间没有新任务时,线程会被销毁	不会有任何任务等待
   
    static ExecutorService newFixedThreadPool(int nThreads)  
    会返回固定线程数量的线程池:参数就是规定线程池在创建时,会创建几个线程,当有新任务提交时,会先查看是否有闲置线程,如果有就提交给闲置线程,如果没有不会创建新的线程,而是任务等待闲置线程  存在任务等待的情况
3.如何给线程池提交任务
1.创建一个线程池
2.使用线程池的submit方法 提交任务(提交任务对象)
    	ExecutorService pool1 = Executors.newCachedThreadPool();
		//ExecutorService pool2 = Executors.newFixedThreadPool(2);
		Runnable task1 = ()->{
			for (int i = 1; i < 26; i++) {
				System.out.println(i);
			}
		};
		Runnable task2 = ()->{
			for (char c = 'A' ; c < 'Z'; c++) {
				System.out.println(c);
			}
		};
		pool1.submit(task1);
		pool1.submit(task2);
4.Callable接口 了解
Runnable接口的run方法 存在问题 不能给任务的发布者返回计算的结果,也不能抛出异常

JDK1.5推出了新的任务类型 Callable类型 Callable类型只能与线程池搭配不能与手动创建的线程搭配使用

创建Callable类型的任务类
class 类名 implements Callable<泛型>{
    public 泛型 call()throws 异常....{
        //任务代码
        return 给任务发布者返回的数据;
    }
}
a.如何获取Callable类型任务的 任务结果?
    1.创建Callable类型的任务类
    class MyTask2 implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		int sum = 0;
		for(int i=1;i<=500;i++){
			sum += i;
		}
		return sum;
	}
	
}
class MyTask3 implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		int sum = 0;
		for(int i=501;i<=1000;i++){
			sum += i;
		}
		return sum;
	}
	
}
	2.创建Callable类型的任务对象
        MyTask2 task2 = new MyTask2();
        MyTask3 task3 = new MyTask3();
	3.将Callable类型的任务对象提交给线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        Future<Integer> f1 = pool.submit(task2);
        Future<Integer> f2 = pool.submit(task3);	
	4.将任务提交给线程池后,会得到Future的未来对象,因为计算结果产生在未来
    5.通过Future的get方法可以获取Callable类型的任务对象的计算结果
    	System.out.println("主线程在干其他事情..");
		System.out.println("主线程需要其他线程的计算结果,等待中.....");
		Integer integer = f1.get();
		Integer integer2 = f2.get();
		Integer result = integer+integer2;
		System.out.println(result);
        注意:当Callable类型的任务对象还未将结果计算完成时,未来对象的get方法会使得当前线程进入阻塞状态,等到获取到结果时 才能继续执行

五.线程安全问题

1.名词解释 必须记住 非常重要
a.临界资源:在多线程并发下,多个线程共享的某个数据 被称为临界资源
b.原子操作:多步操作被视为一个整体,在执行顺序上不可以被打破
c.线程不同步/不安全:在多线程并发下,原子操作被破坏,临界资源数据出现问题
d.线程的同步/安全:在多线程并发下,保证原子操作不被破坏,从而保证临界资源的数据安全
e.死锁:两个线程相互等待对方释放所有占据的互斥锁标记,从而使得两个线程都会进入到阻塞状态,使得程序无法向下执行
2.如何保证线程的同步 重要
Java的每一个对象都会有一个 互斥锁标记

a.使用同步代码块保证线程同步
    synchronized(临界资源对象){
    	//原子操作
	}
	当一个线程,是第一个访问同步代码块的线程 此时该线程获取到临界资源的互斥锁标记 当原子操作执行完毕时会归还互斥锁标记
    当一个线程想要执行原子操作 会先试图获取互斥锁标记,获取成功则执行原子操作,如果获取失败则进入阻塞状态
    阻塞状态状态的线程会放弃时间片 不参与时间片的争夺
    当一个线程释放了互斥锁标记时,JVM会通知所有处在阻塞状态并等待该互斥锁标记的所有线程,此时这些线程会等待互斥所标记的随机分配,哪个线程获取到该互斥锁标记,哪个线程回归Runnable状态 参与时间片争夺

b.使用同步方法来保证线程的同步
   	如果一个方法的内部全部都是原子操作 并且临界资源使用的是当前对象,那么我们可以直接把该方法声明为一个同步方法
     语法:
        修饰符  synchronized 方法返回值 方法名(参数表){
        	//原子操作
    	}
	  例:
        public void push(String s) {
            synchronized(this) {
                str[size] = s;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                size++;
            }
        }
		转为同步方法:
        public synchronized void push(String s) {
			str[size] = s;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			size++;
		}
	关于同步代码块与同步方法的使用位置:
        同步代码块一般使用在任务代码中
        同步方法,往往使用在临界资源的方法上
3.线程的状态图

6.对于集合是否线程安全,可变长字符串是否线程安全的解读
线程安全的实现类:往往内部方法都了同步操作
线程不安全的实现类:往往内部的方法没有做线程的同步操作

例如:
    Vector为什么线程安全?ArrayList为什么线程不安全?
   Vector中的方法都是同步方法,被synchronized修饰
   ArrayList所有的方法都不是同步方法
4.线程间通讯 不是很重要
void wait() 
    当线程执行到 临界资源.wait()时,当前线程会立刻放弃时间片,放弃所有的互斥锁标记,进入到无限期等待
void notify()
    当线程执行到 临界资源.notify(), JVM会去随机唤醒一个处于无限期等待且正在等待该临界资源的线程,唤醒后该线程会从 无限期等待进入到阻塞状态 从而等待JVM通知临界资源被释放然后准备争抢
void notifyAll() 
    当线程执行到 临界资源.notify(), JVM会去随机唤醒所有处于无限期等待且正在等待该临界资源的线程,唤醒后该线程会从 无限期等待进入到阻塞状态 从而等待JVM通知临界资源被释放然后准备争抢
5.lock锁
同步代码块以临界资源的互斥锁标记作为占据临界资源的标志
lock锁以自身为一个标记,先进行加锁的线程就占据lock锁,当解锁时释放lock锁

使用lock锁的步骤:
    1.创建一个lock锁对象---使用实现类ReentrantLock获取lock锁对象
    Lock lock = new ReentrantLock(); 
    2.对原子操作的开始进行加锁, 在原子操作结束后进行解锁
	public void push(String s) {
		synchronized(this){
            str[size] = s;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            size++;
        }
	}
	使用lock锁替换
    public void push(String s) {
		lock.lock();
		str[size] = s;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		size++;
		lock.unlock();
	}

lock锁常用方法:
    1.lock()上锁
    2.unlock()解锁
    3.tryLock() 引用加锁 尝试获取lock对象如果lock被占据返回false  如果lock对象没有被占据则返回true并等效于lock()
    
lock锁的好处:使得锁具有更具的表现,代码可读性高,可以提高代码的灵活度,提高程序效率
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读