1.程序,线程,进程之间的关系
(1)程序(program):为完成特定任务、⽤某种语⾔编写的⼀组指令的集合。即指⼀段静态的代码,静态对象。
(2)进程(process):程序的⼀次执⾏过程,或是正在运⾏的⼀个程序。是⼀个动态的过程:有它⾃身的产⽣、 存在和消亡的过程。——⽣命周期
(3)线程(thread),进程可进⼀步细化为线程,是⼀个程序内部的⼀条执⾏路径
2.并行和并发
(1)并⾏:多个CPU同时执⾏多个任务。⽐如:多个⼈同时做不同的事。
(2)并发:⼀个CPU(采⽤时间⽚)同时执⾏多个任务。⽐如:秒杀、多个⼈做同⼀件事。
3.多线程程序的优点
(1)提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
(2)提高计算机系统CPU的利用率
(3)改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
4.何时需要多线程
(1)程序需要同时执⾏两个或多个任务。
(2)程序需要实现⼀些需要等待的任务时,如⽤户输⼊、⽂件读写操作、⽹络操作、搜索等。
(3)需要⼀些后台运⾏的程序时。
5.线程的优先级
(1)thread中有三个优先级别,分别是 MIN_PRIORITY(最小的),NORM_PRIORITY(默认的) MAX_PRIORITY(最高的)
(2)可以通过.setPriority去设置线程的优先级
(3)但是并不是说优先级高的线程就一定会去执行,而是大概率会去执行。
6.创建线程的方式
(1)方式一:继承于Thread类
①创建⼀个继承于Thread类的⼦类
②重写Thread的run()⽅法
③将此线程的⽅法声明在run()中
class MyThread extends Thread{
@Override
public void run() {
System.out.println("我是子线程");
super.run();
}
}
④创建Thread类的⼦对象
MyThread myThread = new MyThread();
⑤通过此对象调⽤start()
myThread.start();
(2)Thread匿名子类创建方式
①方式一:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//拿到每一个线程的名字
System.out.println(Thread.currentThread().getName());
}
});
thread.start();
②方式二:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
(3)方式二:实现Runnable接口
①创建⼀个实现了Runnable接⼝的类
②实现类去实现Runnable中的抽象⽅法:run()
class MyRun implements Runnable{
@Override
public void run() {
System.out.println("我是子线程");
}
}
③创建实现类的对象
MyRun myRun = new MyRun();
④将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread thread = new Thread(myRun);
⑤通过Thread类的对象调⽤start()
thread.start();
(4)方式三:实现Callable接口
①call() 可以有返回值的。
②call()可以抛出异常,被外⾯的操作捕获,获取异常的信息
③Callable是⽀持泛型的
④需要借助FutureTask类,⽐如获取返回结果
⑤实现方式
1)创建⼀个实现Callable的实现类
class MyCallable implements Callable{
}
2)实现call⽅法,将此线程需要执⾏的操作声明在call()中
@Override
public Object call ( ) throws Exception{
return null;
}
3)创建Callable接⼝实现类的对象
MyCallable myCallable = new MyCallable( );
4)将此Callable接⼝实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask ( myCallable );
5)将FutureTask的对象作为参数传递到Thread类的构造器中
Thread thread = new Thread( futureTask );
6)创建Thread对象,并调⽤start()
thread . start( );
7)获取Callable中call⽅法的返回值
try{
Object o = futureTask . get( );
}catch( Exception e ){
Throw new RuntimeException( e );
}
7.通过调⽤run 和start⽅法有什么区别
①start是开启⼀条⼦线程运⾏ (使用start方法可以实现多线程)
②多次调⽤start⽅法会抛异常,判断了⼀下当前线程的状态,如果不为0 那么就抛出异常(IllegalThreadStateException())
③run就是⼀个重写的⽅法 ,是在主线程⾥运⾏(使用run方法就是单纯的调用方法,不会实现多线程)
8.继承Thread和实现Runnable的区别
(1)直接继承于thread ,thread内部也实现了runnable接⼝
(2)在项⽬开发中 ,实现runnable接⼝要优于继承Thread的⽅式。
(3)如果有多条线程,在有共享数据时,如果使⽤的是继承thread的⽅式,那么共享数据需要加静态status去 修饰,否则会创建多个对象。
(4)⽽实现runnable接⼝使⽤的是同⼀个对象,所以不会存在有多个对象的问题
9.Thread常用方法
start 启动当前线程,执行当前线程的run()
run() 通常需要重写Thread类中的此方法,将创建的线程需要执行的操作声明在此方法中
currentThread() 静态方法,返回当前代码执行的线程
getName() 获取当前线程的名字
setName() 设置当前线程的名字
yield() 释放当前CPU的执行权
join() 在线程a 中调用线程b的join(),此时线程a就进入了阻塞状态,知道线程b完全执行完以后,线程a才结束阻塞状态
stop() 已过时。强制结束当前线程
sleep() 让当前线程“睡眠”指定时间的millitime(毫秒),在指定的millitime时间内,当前线程是阻塞的
isAlive() 返回boolean,判断线程是否还活着
10.线程的生命周期
(1)新建——当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
(2)就绪——处于新建状态的线程被start()后,将进去线程队列等待CPU时间片,此时它已具备了运行的条件, 只是没分配到CPU资源
(3)运行——当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
(4)阻塞——在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进 入阻塞状态
(5)死亡——线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
11.线程安全问题
(1)使用多线程时并不一定会出现线程安全问题,但是如果有共享数据的时候,就可能会出现线程安全问题
(2)就好比卖票窗口的案例,尽管使用了static修改,在一开始还是了重票或者错票的问题,尤其是在添加了sleep之后
(3)解决线程安全问题 synchronize ——同步代码块,同步方法
12.Synchronize——线程同步锁——关键字
(1)synchronize 可以用于解决线程安全问题,有同步代码块和同步方法两种方式
(2)同步代码块
①使用方法
synchronize (任意对象,但必须是同一个对象){
操作共享数据的代码
}
②注意点
1)操作共享数据的代码,即为需要被同步的代码,不能包含代码多了,也不能包含代码少了
2)共享数据:多个线程共同操作的变量
3)同步监视器,俗称:锁。任何⼀个类的对象,都可以来充当锁 要求:多个线程必须要共⽤同⼀把锁
4)默认情况锁是打开的,只要有一个线程进去执行代码,锁就会关闭,当线程执行完出来了,锁才会自动打开
③优缺点
1)优:解决了多线程的数据安全问题
2)缺:当线程很多时,因为每个线程都会去判断同步上的锁,很消耗资源,降低程序运行效率
(3)同步方法
①使用方法
修饰符 synchronized 返回值类型 方法名(方法参数){
}
②注意点
1)如果是实现runnable接⼝的线程⽅式,那么对应的对象是相同的,就不需要使⽤static,但如果是继承于Thread 那么在多线程使⽤时,会创建多个对象,同步⽅法就需要将this指向窗⼝类,就要添加static
2)同步方法有默认存在的锁对象
3)对于非静态static,同步锁就是this
4)对于静态static方法,我们使用当前方式所在类的字节码对象
(4)同步代码块和同步方法的区别
①同步代码块可以锁住指定代码,同步方法是锁住方法所有代码
②同步代码块可以指定锁住对象,同步方法不能
13.死锁
(1)出现的原因
①两个线程都持有对方的锁,但是都准备获取另一个锁
(2)解决方法
①将获取锁的对象换成一样的
14.Lock锁——接口
(1)ReentrantLock类实现了Lock ,它拥有与synchronized相同的并发性和内存语义, 在实现线程安全的控制中,⽐较常⽤的是ReentrantLock,可以显式加锁、释放锁。
(2)使用方式
class Window implements Runnable{
private ReentrantLock lock = new ReentrantLock ( ) ;
@Override
public void run( ){
try{
lock.lock( ); //手动调用lock方法
}finally{
lock.unlock( ); //每次结束都要手动去释放
}
}
15.Lock和Synchronize的区别
(1)synchronized是Java语⾔的关键字。Lock是⼀个接⼝。
(2)synchronized不需要⽤户去⼿动释放锁,发⽣异常或者线程结束时⾃动释放锁。Lock则必须要⽤户去⼿动释放锁, 如果没有主动释放锁,就有可能导致出现死锁现象。
(3)lock可以配置公平策略,实现线程按照先后顺序获取锁。 提供了trylock⽅法 可以试图获取锁,获取到或获取不到时,返回不同的返回值 让程序可以灵活处理。 lock()和unlock()可以在不同的⽅法中执⾏,可以实现同⼀个线程在上⼀个⽅法中lock()在后续的其他⽅法中unlock(), ⽐syncronized灵活的多
16.线程之间通讯
(1)方法
wait( ) 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify( ) 一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个
notifyAll( ) 一旦执行此方法,就会唤醒所有被wait的线程
(2)wait(),notify(),notifyAll()三个⽅法必须使⽤在同步代码块或同步⽅法中。
(3)wait(),notify(),notifyAll()三个⽅法的调用者必须是同步代码块或同步方法中的同步监视器
(4)wait(),notify(),notifyAll()三个⽅法是定义在java.lang.Object类中。
17.线程池
(1)使用线程池的好处
①经常创建和销毁、使⽤量特别⼤的资源,⽐如并发情况下的线程,对性能影响很⼤,提前创建好多个线程,放⼊线程池中,使⽤时直接获取,使⽤完放回池中。可以避免频繁创建销毁、实现重复利⽤。
②类似⽣活中的公共交通⼯具。
③提⾼响应速度(减少了创建新线程的时间)
④降低资源消耗(重复利⽤线程池中线程,不需要每次都创建)
⑤便于线程管理
(2)JDK5.0起提供了线程池相关API
(3)常用线程池
①创建并返回不同类型的线程池
Executors . 工具类、线程池的工厂类
②创建一个可根据需要创建新线程的线程池
ExecutorService executorService = Executors . newCachedThreadPool ( );
③创建一个可重用固定线程数的线程池
ExecutorService executorService = Executors . newFixedThreadPool ( n );
④创建一个只有一个线程的线程池
ExecutorService executorService = Executors . newSingleThreadExecutor ( );
⑤创建一个定长的线程池,支持定时及周期性任务执行
ExecutorService executorService = Executors . newScheduledThreadPool ( n );
(4)常用方法
void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task) 执行任务,有返回值,一般又来执行Callable
void shutdown() 关闭连接池
(5)线程池核心参数
corePoolSize 核心线程数目-池中会保留的最多线程数
maximumPoolSize 最大线程数目
keepAliveTime 生存时间-救急线程的生存时间,生存时间内没有新任务,此线程资源会释放。