1、进程:正在执行的程序称作一个进程。进程负责了内存空间的划分。
Windows号称是多任务的操作系统,那么Windows是同时运行多个应用程序吗?
从宏观角度:Windows确实是在同时运行多个应用程序。
从微观角度:cpu是做了一个快速切换执行的动作,由于速度太快,所以我们感觉不到切换而已。
线程:线程在一个进程中负责了代码执行。
多线程:在一个进程中有多个线程同时在执行不同的任务。
2、java中的线程:
疑问?线程负责了代码的执行,我们之前没有写过线程,为什么代码可以执行呢?
答:运行任何一个java程序,jvm在运行的时候都会创建一个主线程执行main方法中所有代码。
疑问?一个java应用程序至少有几个线程?
答:至少有两个线程,一个是主线程负责main方法代码执行;一个是垃圾回收器线程,负责了回收垃圾
3、多线程的好处
1、多线程最大好处在于可以同时并发执行多个任务;
2、多线程可以最大限度地减低CPU的闲置时间,从而提高CPU的利用率。
多线程的弊端
1、增加了cpu负担
2、降低了一个进程中线程的执行效率
3、引发了线程安全问题
4、出现了死锁现象
4、Java创建线程的两种方法:继承Thread类的方式和实现Runnable接口的方式
(1)继承Java.lang.Thread类,并重写run()方法。
定义:
class MyThread extends Thread {
//把自定义线程的任务代码写在run方法中
public void run( ) {
/* 覆盖该方法*/
} }
调用:
MyThread thread = new MyThread();
thread.start();
run():把自定义线程的任务代码写在run方法中
start():启动线程
getName():返回此线程的名称
疑问:重写run方法的目的是什么?
每个线程都有自己的任务代码,jvm创建的主线程的任务代码就是main方法中的所有代码,自定义线程中任务代码就是写在run方法中,自定义线程负责了run方法中所有代码。
注意:1、一个线程一旦开启,那么线程就会执行run方法中的代码,run方法千万不能直接调用,如果直接调用就相当于调用了一个普通方法而已,并没有开启线程。
2、启动线程,线程将等待调度(到底什么时候被调度,不一定要看当前的操作系统分配资源的情况);调度后,自动调用其run方法,run方法是等着被自动调用的。
(2)实现Java.lang.Runnable接口,并重写run() 方法;
定义:
class MyThread implements Runnable{
public void run( ) {
//实现该方法
}
}
调用:MyThread t = new MyThread();
Thread th = new Thread(t);
th.start();
static Thread currentThread():返回对当前正在执行的线程对象的引用。
Thread(Runnable target, String name) 分配一个新的 Thread对象,并且赋值线程名称
注意:Runnable接口的存在主要是为了解决Java中不允许多继承的问题。
(3)两种线程创建方式的比较:
继承Thread类:
优势:Thread类已实现了Runnable接口,故使用更简单
劣势:无法继承其它父类
实现Runnable接口:
优势:可以继承其它类
劣势:编程方式稍微复杂,多写一行代码
5、线程状态:
创建:新创建了一个线程对象。
可运行:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取cpu的执行权。
运行:就绪状态的线程获取了CPU执行权,执行程序代码。
阻塞状态: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
死亡:线程执行完它的任务时。
6、Thread类中常用的构造方法
Thread() 创建一个新的线程
Thread(String name) 创建一个指定名称的线程
Thread(Runnable target) 利用Runnable对象创建一个线程,启动时将执行该对象的run方法
Thread(Runnable target, String name) 利用Runnable对象创建一个线程,并指定该线程的名称
7、thread类中的常用方法:
void start() 启动线程
final void setName(String name) 设置线程的名称
final String getName() 返回线程的名称
final void setPriority(int newPriority) 设置线程的优先级
final int getPriority() 返回线程的优先级
final void join() throws InterruptedException 等待该线程终止。(强行介入)
final boolean isAlive() 判断线程是否处于活动状态
void interrupt() 中断线程
8、thread类中的常用静态方法:
static Thread currentThread() 返回对当前正在执行的线程对象的引用
static void sleep(long millis) throws InterruptedException 使当前正在执行的线程休眠(暂停执行)
static void yield() 让出时间片
static boolean interrupted() 判断当前线程是否已经中断
9、常用方法的解析
优先级:
注意:1、多个线程处于可运行状态时,将对cpu时间片进行竞争,优先级高的,获取的可能性则高
2、 通过setPriority和getPriority方法来设置或返回优先级;
3、线程优先级用1~10 表示,10的优先级最高,默认值是5,1的优先级最低
sleep():使线程停止运行一段时间,此时将处于阻塞状态。阻塞的时间由指定的毫秒数决定
注意:1、线程睡眠是帮助所有线程获得运行机会的最好方法
2、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。
yield():出让时间片,但不会阻塞线程,转入可运行状态
注意:1、如果调用了yield方法之后,如果没有其他等待执行的线程, 这个时候当前线程就会马上恢复执行!
sleep()和yield()方法对比: sleep() yield()
1、暂停后的状态 进入被阻塞的状态,直到经过指定时间后,才进入可运行状态 直接将当前线程转入可运行状态
2、没有其他等待运行的线程 当前线程会等待指定的时间后执行 当前线程有可能会马上恢复执行
3、等待线程的优先级别 优先级相同或更高的线程运行
join():阻塞当前线程,强行介入执行
final void setDaemon(boolean on) 将该线程设置为后台线程
final boolean isDaemon() 判断该线程是否为后台线程
我们自己创建的线程,叫前台线程也叫用户线程。后台线程创建的都叫后台线程 。
10、线程同步
需求:模拟三个窗口同时在售50张票
问题1:为什么50张票被卖出了150次?
出现原因:因为num是非静态的,非静态的成员变量数据是在每个对象中都会维护一份数据的,三个线程对象就会有三份。
解决方案:把num票数共享出来给三个对象使用。使用static修饰。
问题2:刚刚的问题是解决了,但是为什么会出现两个窗口卖同一张票呢?--出现了线程安全问题
解决方案:一个时间只能由一个线程操作内容---线程同步机制
出现安全的根本原因:
1、存在两个或者两个以上的线程对象,而且线程之间共享着一个资源
2、有多个语句操作了共享资源
同步机制的方法
方式一:同步代码块
语法:synchronized(共享对象名){被同步的代码段}
同步代码块要注意的事项:
1、任意一个对象都可以作为锁对象。
2、在同步代码块中调用sleep方法并不会释放锁对象的。
3、只有真正存在线程安全问题的时候才使用同步代码块,否则会降低效率。
4、多线程操作的锁对象必须是唯一共享的,否则无效。eg:Static Object o = new Object()--是唯一 "锁"--可以是唯一,存储在常量池 new String("锁")--不可以存在堆
方式二:同步方法
语法:访问修饰符 synchronized 数据返回类型 方法名(){ … }
同步方法要注意:
1、如果是一个非静态的同步方法的锁,对象是this对象;如果是静态的同步方法的锁对象是当前函数所属的类的字节码文件(Class对象)。
2、同步方法的锁对象是固定的,不能由你来指定。
推荐使用:同步代码块
原因:1、同步代码块的锁对象可以由我们随意指定;同步方法的锁对象是固定的,不能指定。
2、同步代码块可以很方便的需要被同步的范围,同步方法必须是整个方法的所有代码都被同步了。
线程同步的特点:
1、线程同步的优点:解决了线程安全问题、对公共资源的使用有序化
2、线程同步的缺点:性能下降;会带来死锁
11、死锁 :线程死锁指的两个线程互相持有对方依赖的共享资源,造成无限阻塞。
案例电池和遥控器(张三拿电池,李四拿遥控器,谁也不肯放手) 张三等着李四的电池,李四等着张三的遥控器,无限等待中
死锁出现的根本原因: 1、存在两个或者两个以上的线程
2、存在两个或者两个以上的共享资源
死锁解决方案:没有方案。只能尽量避免发生(或者线程通信)
12、线程通讯:一个线程完成了自己的任务的时候,要通知另外一个线程去完成另外一个任务
如何实现线程通讯:Java代码中基于对共享数据进行“wait()、notify()、notifyAll()"来实现多个线程的通讯。
案例生产者和消费者
问题一:出现了线程安全问题。 价格错乱了...
解决方案:加锁
问题二:生成一大片,消费一大片,要做成什么样呢?生产者生产完等待消费者消费,然后再去生产;消费者每消费完,那么要等待生产者去生产,然后再消费。生产一个消费一个。 解决问题:通信方法
线程通讯常用方法:
wait(): 等待--如果线程执行了wait方法,那么该线程会进入等待的状态,等待状态下的线程必须要被其他线程调用notify方法才能唤醒。
notify(): 唤醒--唤醒线程池等待线程其中的一个。
notifyAll() : 唤醒线程池所有等待的线程。
wait与notify方法要注意的事项:
1. wait方法与notify方法是属于Object对象 的。
2. wait方法与notify方法必须要在同步代码块或者是同步方法中才能使用。
3. wait方法与notify方法必需要由锁对象调用。
线程通讯原理:
wait():一个线程如果执行了wait方法,那么该线程就会进去一个以锁对象为标识的线程池中等待。
notify():如果一个线程执行了notify方法,那么就会唤醒以锁对象为标识符的线程中等待线程中的其中一个。
sleep() wait()
归属 Thread的静态方法 Object的方法
作用 让本线程进入睡眠状态 让本线程进入“等待”状态。
是否释放同步锁 不会释放同步锁 会释放同步锁
13、线程的生命周期:参照ppt p62
(1)新建状态(New):使用new关键字创建线程对象,仅仅被分配了内存;
(2)可运行状态(Runnable):线程具备获得CPU时间片的能力。线程进入可运行状态的情况如下: 线程start()方法被调用; 当前线程sleep()、其它线程join()结束、等待用户输入完毕; 某个线程取得对象锁; 当前线程时间片用完了,调用当前线程的yield()方法。
(3)运行状态(Running):执行run方法,此时线程获得CPU的时间片;
(4)阻塞状态(Blocked):线程由于某些事件放弃CPU使用权,暂停运行。直到线程重新进入可运行状态,才有机会转到运行状态。阻塞状态分为如下几种: 同步阻塞 – 线程获得同步锁,若被别的线程占用,线程放入锁池队列中。 等待阻塞 – 线程执行wait()方法,线程放入等待队列中。 其它阻塞 – 线程执行sleep()或join()或发出I/O请求。
(5)死亡状态(Dead):run、main() 方法执行结束,或者因异常退出了run()方法,线程进入死亡状态,不可再次复生。