程序,进程,线程
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间 并行执行多个线程,就是支持多线程的
线程作为调度和执行的基本单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间
它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来 安全的隐患
一个Java应用程序java.exe 至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程;
并行与并发:
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
对于多线程:
- 以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法) 反而比用多个线程来完成用的时间更短。 ---> 线程之间的切换需要时间
多线程程序的优点:
提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
提高计算机系统CPU的利用率
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改何时需要多线程:
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
需要一些后台运行的程序时
方式一:继承Thread类
创建一个继承于Thread类的子类
重写Thread类的run( ) --> 将此线程执行的操作声明在run( )中
创建Thread类的子类的对象
通过此对象调用start( )---> a.启动当前线程 b.调用当前线程的run( )
//1.创建一个继承于Thread类的类 class MyThread extends Thread{ //2.重写Thread类的run() pbulic void run(){ sout("这个线程要执行的操作"); } } //main--主线程 public void main(){ //3.创建Thread类的子类的对象 MyThread t1 = new MyThread(); //4.通过此对象调用start() t1.start(); new Thread(){ public void run(){ sout("多线程--Thread匿名子类"); } }.start(); }注意:
- 要想启动多线程,必须调用start( ),调用run()并不会启动多线程
- 一个线程对象只能调用一次start()方法启动
方式二:实现Runnable接口
创建一个实现 Runnable接口的类 --实现类
实现类实现接口中的 抽象方法:run( )
创建实现类的对象
将此对象作为参数 传递到Thread类的构造器中,创建Thread类对象 --代理类
通过Thread类的对象 调用start( )
//1.创建一个实现了 Runnable接口的类,并实现run()方法 class MyThread implements Runnable{ pbulic void run(){ sout("此线程要执行的操作") }; } public void main(){ //2.创建实现类对象 MyThread w = new MyThread(); //3.将实现类对象作为参数 传递到Thread类的构造器中,创建Thread类对象 Thread t1 = new Thread(w); Thread t2 = new Thread(w); //4.通过Thread类对象 掉哟start() t1.start(); t2.start(); }
小结:
开发中,优先选择 实现Runnable接口的方式:
没有单继承的局限性
多个线程可以共享实现类中的数据。联系:public class Thread implements Runnable
相同点:两种方式都需要重写run( ),将线程要执行的逻辑声明在run( )中。
Thread中的常用方法:
start( ) : 启动当前线程,调用当前线程的run( )
run( ) : 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在run( )中
currentThread( ):静态方法,返回当前执行代码的线程
getName( ):获取当前线程的名字
setName( ):设置当前线程的名字 【也可以在 构造器中设置线程名字
new MyThread("线程1")
】yield( ):释放当前cpu的执行权
join( ):在线程a中调用线程b的join( ),此时线程a进入阻塞,直到b执行完毕。
stop( ):强制结束当前线程 (已过时)
sleep(long millitime):让当前线程“睡眠”指定 毫秒,睡眠期间此线程 阻塞。
isAlive( ):判断当前线程是否存活。
线程的优先级:
MAX_PRIORITY: 10
MIN_PRIORITY: 1
NORM_PRIORITY:5 -->默认优先级getPriority( ):获取当前线程优先级
setPriority( ):设置线程的优先级
--- 优先级只是提高被cpu执行的概率
线程通信
wait( ),notify( ),notifyAll( ):此三个方法定义在Object类中
Java中的线程分为两类:守护线程 和 用户线程
虚拟机JVM 必须确保 用户线程执行完毕才能离开 ,若JVM中都是守护线程,当前JVM退出。
用户线程完毕--JVM关闭--守护线程完毕
守护线程是用来服务用户线程的,通过在start( )前调用
thread.setDaemon(true)
可以设置为守护线程守护线程: 后台记录操作日志,监控内存,垃圾回收等待
NEW
,就绪WAITING
,运行RUNNABLE
,阻塞BLOCKED
,死亡TERMINATED
) 线程同步的问题:当线程a操作某共享数据的过程中(还没操作完),进程b参与进来也操作该共享数据,就会造成共享数据的错误。
解决: 当一个线程a在操作共享数据时,其他线程不能参与进来,直到线程a操作完毕。
问题:同步后,该同步片段变为 单线程;同步片段过多可能使程序出现错误 --被某一线程全部占用。
方式一:同步代码块
synchronized(同步监视器){ //需要被同步的代码 }说明:a.操作共享数据的代码,即为需要被同步的代码。
b.共享数据:多个线程共同操作的变量
c.同步监视器,俗称 锁。任何一个类的对象 都可以充当锁
要求:多个线程必须要共用同一把锁。--Runnable方式中可以用 this
--Thread方式中可以用 xx.class【类对象】
方式二:同步方法
若操作共享数据的代码完整的声明在一个方法中,可以考虑使用同步方法
public synchronized void method(){ //被同步的代码(实现Runnabe方式)-- this } public static synchronized void method(){ //继承Thread方式-- 类.class }总结:同步方法仍然涉及到同步监视器,只是不再需要显示声明。(默认)
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
方式三:Lock锁
JDK5.0新增,手动上锁,解锁
//1.实例化ReentrantLock privete ReentrantLock lock = new ReentrantLock(); //ReentrantLock(true)-公平锁 //2.调用上锁方法 lock() lock.lock(); ---------------------要同步的代码片段 //3.调用解锁方法 unlock() lock.unlock();
synchronized 与 Lock 的对比
面试题:synchronized 与 Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的代码片段后,自动释放同步监视器
Lock需要手动的启动同步
lock()
,手动的结束同步unlock()
优先顺序:Lock---> 同步代码块 ---> 同步方法
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
synchronized(this){ notify(); //线程a 释放(一个)被阻塞的线程b /* 同步代码块 */ wait(); //当前线程(a)进入阻塞,并释放同步监视器 --- 交替到线程b }
线程通信涉及到的三个方法:
wait( ): 当前线程进入阻塞状态,并释放同步监视器
notify( ): 唤醒被wait的一个线程 (若有多个线程被wait 则唤醒优先级高的那个)
notifyAll( ):唤醒所有被wait的线程说明(对于wait, notify, notifyAll三个方法):
必须在 同步代码块或同步方法中使用。
调用者必须是同步代码块或同步方法中的 同步监视器。
都是 定义在java.lang.Object类中的。
面试题:sleep( ) 和 wait( ) 的异同?
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点: 1.两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2.调用的要求不同:sleep()可以在任何场景下调用,wait()必须使用在同步代码块/同步方法中。
3.是否释放锁:若两个方法都在同步代码块/同步方法中,sleep()不会释放锁,wait()会释放锁。
生产者消费者模式:
管程法 (生产者---缓冲区---消费者)
信号灯法 (标志位)
生产者消费者 共用同一把锁
public synchronized void produce(){ if(goods < 20){ goods++; sout("生产产品"); nofity(); //唤醒消费者,通知消费 }else{ wait(); //等待消费 } } public synchronized void consum(){ if(goods > 0){ goods--; sout("消费产品"); nofity(); //唤醒生产者,通知生产 }else{ wait(); //等待生产 } }
JDK5.0新增了两种创建线程的方式 ---(共四种创建线程的方式)
线程创建方式三:实现Callable接口
1.创建一个实现 Callable接口 的实现类
2.实现接口中的call( ) --> 将此线程执行的操作声明在call( )中,可以有返回值
3.创建实现类对象
4.创建FutureTask对象,将实现类对象 传入 FutureTask构造器中 ---(FutureTask 实现了Runnale接口)
5.创建Thread对象,将FutureTask对象 传入 Thread构造器中
6.通过Thread对象 调用 start( )
---> futureTask.get( )可以获得 call( )的返回值//1.创建一个实现 Callable接口 的实现类,实现call() class myThread implements Callable{ public Object call() throws Exception{ sout("线程要执行的操作"); return null; //线程的返回值 } } public void main(){ //2.创建实现类对象 myThread w = new myThread(); //3.创建FutureTask对象,将实现类对象 传入 FutureTask构造器中 FutureTask futureTask = new FutureTask(w); //4.创建Thread对象,将FutureTask对象 传入 Thread构造器中,调用start() new Thread(futureTask).start(); Object ob = futureTask.get();//5.futureTask.get()可以获得 call()的返回值 }实现Callable接口的方式 比 实现Runnable接口方式 创建多线程更强大?
call( )可以有返回值
call( )可以抛出异常,被外面的操作捕获,获取异常的信息
Callable是支持泛型的
线程创建方式四:线程池
提前创建好多个线程,放入线程池中,使用时直接获取,用完放回。
- 提高相应速度,降低资源消耗(避免重复的创建销毁,实现重复利用)
- 便于线程管理:
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池的创建:
1.提供指定线程数量的线程池
2.执行指定的线程的操作,需要提供 实现Runnable接口/Callable接口的实现类对象
execute
(Runnable实现类对象) /submit
(Callable实现类对象)3.关闭连接池
shutdown()
class MyRunThread implements Runnable{ public void run(){}; } class MyCalThread implements Callable{ public Object cal(){}; } public void main(){ //1.提供指定数量的线程池 ExecutorService service = Executors.newFixedThreadPool(8); //2.执行指定线程的操作--管理线程 service.execute(new MyRunThread() ); //适合 Runnable service.submit(new MyCalThread() ); //适合 Callable service.setXXX; //线程管理 //3.关闭连接池 service.shutdown(); }