线程学习笔记
进程与线程
进程
- 是指一个内存中运行的应用程序,每个进程都一个独立的内存空间
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
- 线程实际上是在进程基础上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分为若干个线程
线程又分为守护线程和用户线程
- 用户线程:当一个进程不包含任何存活的用户线程时,进程结束
- 守护线程:守护用户线程的,当最后一个用户线程死亡时,所有守护线程自动死亡,创建线程时调用setDaemon(true)
线程的创建
-
继承Thread
public static void main(String[] args){ MyThread m=new MyThread(); m.start(); } public class MyThread extends Thread{ /** *run方法就是线程要执行的任务方法 */ @Override public void run(){ //这里的代码就是一条新的执行路径 //这个执行路径的触发方式,不是调用run方法,而是通过thread对象的start()来启动任务 } }
-
实现Runnable
public static void main(String[] args){ //实现Runnable //1.创建一个任务对象 MyRunnable mr=new MyRunnable(); //2.创建一个线程,并为其分配一个任务 Thread t=new Thread(mr); //3.执行这个线程 t.start(); } public class MyRunnable implements Runnable{ @Override public void run(){ //线程的任务 } }
-
实现Runnable与继承Thread相比有如下优势:
- 1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
- 2.可以避免单继承带来的局限性
- 3.任务与线程本身是分离的,提高了程序的健壮性
-
带返回值的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();
获取返回值
- Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
-
Runnable与Callable比较
Runnable 与 Callable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
Runnable 与 Callable的不同点
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;Runnable的run()不能抛出
保证多线程安全的方式
-
同步代码块
synchronized(obj) { //需要被同步的代码块 }
public static void main(String[] args)throws InterruptedException{ //同步代码块 //格式:synchronized(锁对象) Runnablerun=newTicket(); newThread(run).start(); newThread(run).start(); newThread(run).start(); newThread(run).start(); } static class Ticket implements Runnable{ private int count=10; private Object o=new Object(); @Override public void run(){ 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; } } } } }
-
同步方法
public static void main(String[] args)throws InterruptedException{ //同步方法 Runnablerun=newTicket(); newThread(run).start(); newThread(run).start(); newThread(run).start(); newThread(run).start(); } static class Ticket implements Runnable{ private int count=10; private Object o=new Object(); @Override public void run(){ while(true){ boolean flag=sale(); if(!flag) break; } } public synchronized boolean sale(){ if(count>0){ System.out.println("卖票"); try{ Thread.sleep(1000); }catch(InterruptedExceptione){ e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName()+"余票:"+count); return true; }else{ return false; } } }
-
显式锁Lock
创建一个锁对象:Lock l=new ReentrantLock();
在要想上锁的代码块前一行加:l.lock();
释放锁时结束的后一行加:l.unlock();
锁
-
公平锁和非公平锁
定义:
- 公平锁是指多个线程按照申请锁的顺序来获取锁
- 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,后申请锁的线程有可能先获取锁
创建一个锁对象时添加参数fair为true,直接修改为公平锁:Lock l=new ReentrantLock(true);
-
线程死锁
线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
-
缓存线程池
/** *缓存线程池: *长度无限制 *任务加入后的执行流程: *1.判断线程池是否存在空闲线程 *2.存在则使用 *3.不存在,则创建线程并放入线程池,然后使用 */ public static void main(String[] args){ ExecutorService service=Executors.newCachedThreadPool(); //指挥线程池中执行新的任务 service.execute(new Runnable(){ @Override public void run(){ } }); service.execute(new Runnable(){ @Override public void run(){ } }); service.execute(new Runnable(){ @Override public void run(){ } }); try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } service.execute(new Runnable(){ @Override public void run(){ } }); }
-
定长线程池
/** *定长线程池: *长度是指定的数值 *任务加入后的执行流程: *1.判断线程池是否存在空闲线程 *2.存在则使用 *3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用 *4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 */ public static void main(String[] args){ 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(newRunnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName()); try{ Thread.sleep(3000); }catch(InterruptedExceptione){ e.printStackTrace(); } } }); service.execute(newRunnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } }); }
-
单线程线程池
/** *单线程线程池: *执行流程: *1.判断线程池的那个线程是否空闲 *2.空闲则使用 *3.不空闲,则等待池中的单个线程空闲后使用 */ public static void main(String[] args){ ExecutorService service=Executors.newSingleThreadExecutor(); service.execute(newRunnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName()); } }); service.execute(newRunnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName()); } }); }
-
周期定长线程池
/**
*周期任务定长线程池:
*执行流程:
*1.判断线程池中是否存在空闲线程
*2.存在则使用
*3.不存在空闲线程,且线程池未满的情况下,则创建线程,并放入线程池,然后使用
*4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
*周期性任务执行时:
*定时执行,当某个时机触发时,自动执行某任务
*/
public static void main(String[] args){
ScheduledExecutorService service=Executors.newScheduledThreadPool(2);
/**
*1.定时执行一次
*参数1:定时执行的任务
*参数2:时长数字
*参数3:时长数字的时间单位,TimeUnit的常量指定
*/
/*service.schedule(new Runnable(){
@Override
public void run(){
System.out.println("");
}
},5,TimeUnit.SECONDS);*/
/**
*周期性执行任务
*参数1:任务
*参数2:延迟时长数字(第一次执行在什么时间以后)
*参数3:周期时长数字(每隔多久执行一次)
*参数4:时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable(){
@Override
public void run(){
System.out.println();
}
},5,1,TimeUnit.SECONDS);
}