因为线程是进程的一部分,所以了解线程之前有必要了解进程。
进程:
进程是正在进行的程序,QQ,迅雷,在运行的时候必然会占用一个进程,看似是同时在执行N多程序,其实并不是这样,CPU在快速切换而各个程序都执行(切换时间大约20ms),一个进程至少有一个线程,所以当两个线程同时进行时,他们可能来回交替着抢夺CPU。另外线程不可能不依赖进程而独立存在。在使用javac 和java命令的时候会任务管理器中短暂的出现这两个进程。实际上CPU在执行进程中的线程
线程:
线程是程序控制的单元,线程在控制着进程的执行,多线程是同时多个线程同时执行,当然也是CPU快速切换来执行不同 的线程,线程执行过程中大约占用1M的内存空间。线程运行在java的main方法中的线程叫做主线程,其他线程名字从Thread-0开始递增。在某一时刻只能有一个线程在运行(多核除外),同一个实例只能start()一次(start第二次将抛出java.lang.IllegalThreadStateException)。
为什么要使用线程:
有时需要一边下载,单线程只能处理一个任务,所以下载时运行程序会很卡,如果单独开一个任务工作线程就能解决该问题。线程用于存储要运行的代码,存储地方就是run方法,同理,主线程main要执行存储的代码,线程都需要存储位置才能运行
使用线程:
java API中创建新执行线程有两种方法。一种方法是将类声明为 Thread
的子类。该子类应重写 Thread
类的run
方法。接下来可以分配并启动该子类的实例
使用分为以下几步:
方式一:
1.创建Therad的子类
2.覆盖run方法
3.启动线程start();
方式二:
该方式代码存放在Runnable接口的Run方法中,当把该子类对象传递给new Thread(Runnable)的时候根据多态调用Runnable的Run,如果这是Therad也重写了Run方法,结果可想而知,必然调用Therad的,那么实现Runnable接口方法则无效。1.实现Runnable
2.覆盖run方法
3.将Runnable实例传给new Thread(Runnable runnable);
注意:如果主线程中直接调用run方法,只是单纯的调用run方法,并不会新建线程,而start()是将已经创建好的线程执行。
线程的5种状态:
1.创建:
new Thread();
2.运行:
创建好的线程start();
3.冻结:
sleep(time):睡着时候占着CPU
wait():等待时候释放CPU资源,需要notify()唤醒
4.阻塞:
同时执行多个线程时,只能执行一个,另外的线程处于临时状态
5.消亡:
程序退出和调用stop()
以上总结为:没有执行资格的状态是冻结,有执行资格但未执行的阻塞,两者都有是运行状态
线程同步:synchronized(对象)
线程不同步,也就是异步的情况下会有安全隐患,例如火车站售票,异步的话有可能会把一张票卖给多个人,所以才有了同步锁的说法,同步是指:在某个方法或者代码块里只能执行一个线程,其他线程在外面等待,
前提:必须要两个以上的线程(包括两个)并且必须使用同一把锁
优点:解决了多线程安全问题
缺点:多个线程进入之前需要判断,较为消耗资源
线程安全问题:
不容易发现,就算测试很多次也未必能发现问题,所以这需要在编译时就要考虑安全性问题,那么如何看是否安全呢,只要明确三点:
1.明确哪些代码是多线程运行代码
2.明确共享数据
3.明确多线程运行代码中那些语句是操作共享数据的
上面是解决方法体中的安全性问题的,如果需要同步整个方法,那么只需要将synchronized放在修饰符后就可以。同步方法用的锁是this,static修饰后的 方法锁是类名.class。 类型是Class。 synchronized和Lock中的锁基本相同,只是换了一种思维方式。另外把锁显示化,变成可见了。
注意:如果加了同步锁依然会有线程安全问题,必然是线程同步前提未满足。
死锁:
死锁是指两个线程各占用一个锁,都希望得到下一个锁,但谁也不愿意先放开当前锁导致的一直永远阻塞状态。
产生原因:同步中嵌套同步。
产生前提:
1,互斥条件:线程使用的资源必须至少有一个是不能共享的;
2,请求与保持条件:至少有一个线程必须持有一个资源并且正在等待获取一个当前被其它线程持有的资源;
3,非剥夺条件:分配资源不能从相应的线程中被强制剥夺;
4,循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。
防止死锁只需要破坏其中一个即可 。
等待唤醒机制:
当两个线程访问非共享数据可以不加限制的访问,但如果操作共享数据的话会出现一个线程执行多次,另外的才抢到CPU资源,这种情况大多数都不适合解决问题,最好的方式是 线程A执行完一段,线程B在执行。B执行完成叫醒A,A在执行,A执行完叫醒B。这就是等待唤醒机制。
Object中的wait(),notify(),notifyAll();
都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁,
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法操作同步线程时,都必须要标识它们所操作 线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
生产消费者模型:
核心关键点两个 1.while(true) 2.notifyAll();
为什么要定义while判断标记?让被唤醒的线程再一次判断标记。
为什么要定义notifyAll?因为许需要唤醒对象线程,因为只用notify,容易出现只唤醒本方线程的情况,导致程序中所有线程都等待。
线程 的停止:
API中stop已经过时,并未提供具体方法停止,所以只能程序员停止,停止的唯一原理,run方法结束。正常情况下都可以停止,但当线程是冻结状态时则无效,这时需要interrupt() 让线程重新回到运行状态在停止。interrupt该线程还会收到一个ClosedByInterruptException。
守护线程:
守护线程和前台线程无区别,只是在结束的时候守护线程后结束,并且当全部守护线程结束时JVM退出。
线程中的方法:
join():申请主线程的执行权。申请成功主线程处于冻结状态,当join执行完后主线程才活过来。
yield();释放当前线程执行权,让其他线程执行。
总结:
在需要同时执行的时候使用多线程。