线程
进程 :正在运行的程序。
进程是系统进行资源分配和调度的独立单位。
每一个进程都要他自己的内存空间和系统资源。
线程 :一个进程中的一个任务或者一条执行路径。
是程序的执行单元和执行路径。
多线程诞生的意义:
提高计算机CPU的使用率。
一、线程的创建
线程的创建两种方法:
1、继承Thread线程类
extends Thread,覆写父类Thread类的run()方法,new线程对象通过JVM调用start()方法来调用run()方法来启动线程
2、实现Runnable接口
implements Runnable , 必须覆写Runnbale接口的run()方法,new线程对象,用Thread类来接收这个对象,再通过JVM调用start()方法来调用run()方法 来启动线程
两种创建线程的方法的优缺点:
一般开发中,我们用的比较多的是实现Runnable接口这种方式
1、Runnable接口更好的解决的Java单继承的局限性,不占继承名额
2、Runnable接口能更好的适合多个线程去对同一个资源进行处理的情况
Thread线程都是单独处理自己的tickets这个共同数据,而Runnable线程是共同处理tickets这一个资源
3、能够把线程同程序代码与数据有效的分离开,体现的程序的健壮性,更好的体现出面向对象的编程思想。
二、线程的控制
1、线程休眠
public static void sleep(long millis)
2、线程加入
public final void jion()
在很多情况下,主线程生成并启动了子线程,如果子线程要进行大量的耗时运算的时候,主线程往往将于子线程结束前结束,此时,如果主线程结束需要用到子线程的运算结果的时候,就需要用到join()方法,等待子线程结束后再结束主线程。
3、线程礼让
public static void yield()
yield()的目的是将正在运行的线程变成可运行的状态,让线程优先级相同的其他线程能够获得运行的机会。因此使用yield()的目的是让具有相同优先级的线程之间能够适当的轮转执行。
4、线程后台(守护线程)
pubic final void setDaemon(boolean on)
5、线程中断
public final void stop()
public void interrupt()
6、线程等待
wait()---->Object类中的方法
7、线程唤醒
notify() notifyAll()------->Object类中的方法
三、线程的同步
线程同步的方法(三种):
1、同步代码块
synchronized(锁对象){
同步代码;
}
一般锁对象是this 或者是类对象 比如String.class System.class 自己定义的类.class
2、同步方法
public synchronized void 方法名(){
同步代码;
}
实现Runnable接口的线程与继承Thread类的线程实现同步方法的差异:
1、在Thread的子类中使用同步方法:
同步方法中,默认的锁对象就是this,但是,在Thread的子类中,无法充当锁对象。
解决方法:
在同步方法上加static修饰
public synchronized static void 方法名(){}
此时,静态修饰的同步方法的锁对象不再是this ,而是当前类的类对象 即 当前类的类名 .class
2、在Runnable的子类中使用同步方法:
两种使用方法:
1、被静态static修饰的同步方法 锁对象---->当前类名 .class
2、普通的同步方法 锁对象---->this
总结:
线程同步中,尽管多个线程加锁的方式不用(同步代码块、同步方法、LOCK),加锁的代码不同,但是只要是锁对象一致,那么同一时间就只能执行一个线程,实现线程同步。
3、Lock锁
Lock锁是JDK1.5版本推出的。同步代码块和同步方法都是我们理解上的加锁和释放锁,但是Lock锁机制能够更加清晰的看出在哪里加锁和在哪里解锁的。
static Lock lock = new ReentrantLock();//实例化一个锁对象
lock.lock();//加锁
lock.unlock();//解锁
Lock锁的弊端:
lock锁相当于是给所有的线程一把锁和仅仅一把钥匙,当前线程使用完资源后才能释放锁,把钥匙交给下一个等待的线程让他进入临界资源。一旦锁起来的同步代码中出现了问题,线程终止,则无法释放锁钥匙,其他线程也无法进入执行。
同步方法和同步代码块的方式实现的同步时,是线程终止自动释放锁的。
为了解决线程锁代码出现问题终止而导致其他线程无法运行,我们必须用try{}finally{}结构,在finally{}中执行释放锁的代码
try{
锁代码;
}finally{
lock.unlock();
}
四、线程的生命周期
创建 就绪 运行 阻塞 死亡
五、线程通信
生产者与消费者问题
线程之间是根据锁对象调用wait() notify() / notifyAll()方法实现通信过程的。
问题:
1、为什么是锁对象调用wait()方法?
2、wait() notify()方法是定义在哪个类里的?为什么?
回答:
1、两个线程之间的唯一联系就是锁对象,只有锁对象调用wait()方法,让当线程阻塞,变成就绪状态,而共享这个锁对象的其他线程仍然可以运行。
2、wait() notify()方法是定义在Object类中的。因为wait() notify()方法的调用时锁对象,而锁对象可以是任意类型的,所以wait() notify() 需要定义在所有类的父类Object里,保证所有对象都能调用。
注意:wait()方法是从哪里等待,就会被从哪里唤醒,紧接着执行下面的代码。
线程休眠的两种方法的区别:
能够让线程休眠的方法----->wait()等待方法 sleep()休眠方法
wait() 处于等待 ,只能被其他线程唤醒
wait(long timeout) 处于等待,超过一定毫秒值 自动被唤醒
wait(long timeout)与sleep(long timeout)都可让线程休眠一段时间
区别:
wait()方法被调用时,当前线程会自动释放锁,当前线程阻塞,然后进入就绪状态,等待被唤醒运行,被唤醒前其他进程可以执行
sleep()方法被调用时,不会自动释放锁 ,其他线程无法执行
五、线程池
对线程的统一管理 创建 分发 回收 再利用 预留
JDK5后 内置线程
public static ExecutorService newCachedThread() ------->带缓存的线程池
public static ExecutorService newFixedThread(int nThreads) ------>带初始容量的线程池
public static ExecutorService newSingleThreadExecutor() --------->带一个线程的线程池
获取一个线程池对象的方式:
ExecutorService pool=Executors.newFixedThread(3);
添加线程
pool.submit();
关闭线程池
pool.shutdown();
六、创建线程的第三种方式
实现Callable接口
只能在线程池中使用,不能独立的做为一个线程启动,只能借助线程池启动
实现Callable接口的线程类的作用:
Callable接口只有一个call()方法,子类实现接口一定要覆写call()方法,此方法就是相当于run()方法
call()方法是有返回值的,返回的是Future的对象 此对象可以调用get()方法,获得计算的返回值