线程

多线程

并行和并发的区别

  • 并行是指两个或者多个事件在同一时刻发生;并发是指两个或多个事件在同一时间间隔发生。
  • 并行是在不同实体上的多个事件(在多台服务器上同时处理多个任务,如Hadoop分布式集群),并发是在同一实体上的多个事件(在一台服务器上“同时”处理多个任务)。
  • 并发编程的目标是充分利用处理器的每一个核,以达到最高的处理性能。

线程和进程的区别

  • 进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。
  • 线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程可以并发执行。
  • 进程在执行中拥有独立的内存单元,而多个线程共享内存资源。

创建

三种方法:

  • 继承Thread
  • 实现Runnable接口
  • 实现Callable接口

本质上都是一种方式,后两种还是需要通过创建Thread对象来执行。

//1.继承Thread
public static void main(String[] args){
	MyThread thread=new Mythread();
	thread.start();
}
class MyThread extends Thread{
	public void run(){
		System.out.println(Thread.currentThread().getName()+"running...");
	}
}

上面的实例中有三个线程:main主线程、开启的thread线程、垃圾回收线程(后台线程)

//实现Runnable方法
public static void main(String[] args){
	Mytask task=new MyTask();
	new Thread(Task).start();
}
//本质是一个可执行的任务
public MyTask implements Runnable{
	public void run(){
		System.out.println(Thread.currentThread().getName()+"running...");
	}
}
//实现Callable接口
public static void main(String[] args){
	FutureTask futureTask=new FutureTask(new MyTask2());
	new Thread(FutureTask).start();
	System.out.println(FutureTask.get());//get方法得到call方法的返回值
}
//这里的泛型与返回类型对应
public MyTask2 implements Callable<T>{
	//该方法是有返回值的
	public Object call() throws Exception{
		System.out.println(Thread.currentThread().getName()+"running...");
	}
}

runnable和callable方法的区别

  • run()没有返回值,call()有返回值
  • run()不能抛出异常,call()可以

线程的生命周期

在这里插入图片描述
线程共有6种状态:new,runnable,blocked,waiting,timed waiting,terminated
要调用一个线程的状态,使用getState方法。

  • new:使用new新建立的线程,该线程还没有开始运行其中的代码,此时状态为new。
  • runnable:一旦调用start方法,线程处于runnable状态。一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间。
  • blocked:当一个线程试图获取一个内部的对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。当所有其他线程释放该锁,并且调度器允许本线程持有它的时候,该线程将变成非阻塞状态。
  • waiting:当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。例如Object.wait方法或者Thread.join方法。当调用notify()或notifyAll()时,或者join的线程执行结束后,会进入runnable状态
  • timed waiting:有几个方法有一个超时参数调用它们导致线程进入timed waiting状态。如sleep(time),wait(time)。当休眠时间结束后,或者调用notify()或notifyAll()重新进入runnable状态。
  • terminated:程序执行结束后,或者出现没有捕获的异常,线程进入terminated状态

sleep/wait/join/yield

  • sleep方法的作用是让当前线程暂停指定的时间(毫秒)。其与wait方法的区别:
    1.sleep定义在Thread上,wait定义在Object上
    2.sleep不会释放锁,wait会释放锁
    3.sleep可以使用在任何代码块,wait必须在同步方法或同步代码块执行
  • t.join()方法会使当前运行线程(或者说调用t.join()的线程)进入等待池并等待 t 线程执行完毕后才会被唤醒。
  • yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其
    他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

为什么wait要定义在Object中,而不定义在Thread中?
因为Java的锁是对象级别的,而不是线程级别的。

为什么wait必须写在同步代码块中?
原因是避免cpu切换到其他线程,而其他线程又提前执行了notify方法,这样就达不到我们的预期(先wait再由其他线程来唤醒),所以需要一个同步锁来保护。

线程属性

线程优先级
在java程序设计语言中,每一个线程有一个优先级,默认情况下,一个线程继承它的父线程的优先级,可以用setPriority方法设置优先级,从MIN_PRIORITY(1)到MAX_PRIORITY(10),NORM_PRIORITY定义为5。
每当线程调度器有机会选择新线程时,它首先选择具有较高优先级的线程。注意线程的优先级是高度依赖于系统的,java线程的优先级被映射到宿主机平台的优先级上,优先级的个数也许更多,也许更少。因此,不要将程序构建为功能正确性依赖于优先级。
void yield()导致当前线程处于让步状态。如果有其他的可运行线程与此线程同样高的优先级,那么这些线程接下来会被调度。
守护线程
可以通过调用t.setDaemon(true)将线程转换为守护线程。守护线程的唯一用途就是为其他线程提供服务。当只剩下守护线程时,虚拟机就退出了。守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

线程安全

当多个线程访问一个对象时,如果不用进行额外的同步操作,调用这个对象的行为就可以获得正确的结果,我们就说这个对象是线程安全的。

ThreadLocal

作用:实现在线程的上下文传递对象
为每一个线程创建一个副本

public class ThreadLocalTest{
	private static final ThreadLocal<Long> threadLocal=new ThreadLocal<>();
	public static void main(String[] args){
		Task task=new Task();
		new Thread(task).start();
		Thread.sleep(millis:10);
		new Thread(task).start();
	}
	class Task implements Runnable{
		public void run(){
			Long result=threadLocal.get();
			if(result==null){
				threadLocal.set(System.currentTimeMillis());
				System.out.println(Thread.currentThread().getName()+"->"+threadLocal.get());
			}
		}
	}
}

为什么可以为每个线程创建一个副本?、
分析get的源码:

Long result = threadLocal.get();

public T get() {
 	 //1.获取当前线程
    Thread t = Thread.currentThread();
    //2,获取到当前线程对应的map
    ThreadLocalMap map = getMap(t);
    
    if (map != null) {
        //3.以threadLocal为key,获取到entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            //4.获取对应entry的value,就是我们存放到里面的变量的副本
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

set:

threadLocal.set(System.currentTimeMillis());

public void set(T value) {
    //1.获取到当前线程
    Thread t = Thread.currentThread();
    //2.获取当前线程对应的map
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //3.往map存放一个键值对
        //this ThreadLocal
        //value 保存的副本
        map.set(this, value);
    else
        createMap(t, value);
}

可以得到结论:每个线程都会有对应的map,map来保存键值对。

ThreadLocal在实际开发中解决了什么问题?

public class UserService {
    //省略接口的声明
    private UserDao userDao = new UserDao();
    private LogDao logDao = new LogDao();

    //事务的边界放在业务层
    //JDBC的封装,connection
    public void add(){
        userDao.add();
        logDao.add();
    }
}

public class UserDao {
    public void add(){
        System.out.println("UserDao add。。。");
        //创建connection对象
        //connection.commit();
        //connection.rollback();
    }
}

public class LogDao {
    public void add(){
        System.out.println("LogDao add。。。");
        //创建connection对象
        //connection.commit();
        //connection.rollback();
    }
}

如果代码按上面的方式来管理connection,我们还可以保证service的事务控制吗?
这是不行的,假设第一个dao操作成功了,那么它就提交事务了,而第二个dao操作失败了,它回滚了事务,但不会影响到第一个dao的事务,因为上面这么写是两个独立的事务。
上面的根源就是两个dao操作的是不同的connection,所以,我们保证是同个connection即可:

//事务的边界放在业务层
//JDBC的封装,connection
public void add(){
	Connection connection = new Connection();
	userDao.add(connection);
	logDao.add(connection);
}

上面的方式代码不够优雅

public class ConnectionUtils {

    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    public static Connection getConnection(){
        Connection connection = threadLocal.get();
        if(connection == null){
            connection = new Connection();
            threadLocal.set(connection);
        }
        return connection;
    }
}

public class UserDao {

    public void add(){
        System.out.println("UserDao add。。。");
        //创建connection对象
        //connection.commit();
        //connection.rollback();
        Connection connection = ConnectionUtils.getConnection();
        System.out.println("UserDao->"+connection);
    }
}

线程池

  • 在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。
  • 假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。

组成

一个线程池包括以下四个基本组成部分:

  1. 线程池管理器(ThreadPool):用于创建并管理线程池,包括:创建线程池,销毁线程池,添加新任务;
  2. 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
  3. 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
  4. 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

参数

Java中已经提供了创建线程池的一个类:Executor
而我们创建时,一般使用它的子类:ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler)

corePoolSize:核心线程数量(核心线程即使没在使用也不会被回收)
maximumPoolSize:线程池可容纳最大线程数量
keepAliveTime:非核心线程最长可以保留的时间
unit:即这个时间的单位
workQueue:等待队列。任务可以储存在等待队列中等待被执行,执行先入先出原则。
threadFactory:创建线程的线程工厂。
handler:拒绝策略,可以拒绝执行某些任务

执行流程

在这里插入图片描述
任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务;如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行;如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。

常见线程池

  • CachedTreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为无限大。当有需要时就创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
  • SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务。有核心线程也有非核心线程,非核心线程的数量也为无限大。适用于执行周期性的任务。
  • SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
  • FixedThreadPool:定长的线程池,有核心线程,没有非核心线程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值