进程:就是正在运行中的程序,他分为多个线程;是操作系统的概念;
线程:是一个控制单元,也叫一个执行单元,CPU在多个线程中做着快速的切换;是应用程序的概念;
那么为什么要有多线程呢?说实话这个我也不是很清楚,毕老师说是为了提高执行效率,张老师说多线程不能够提高执行效率;
也许是他们基于的知识点不通,目前还是先学会怎么用吧!
线程的五种状态:创建,临时状态(阻塞状态,具有执行资格,但是还没有cpu执行权),启动(获得了cpu执行权),
冻结状态(没有执行资格),消亡状态;
Java中线程的创建方式有两种:
Thread:实现了Runnable接口;
1 :继承Thread类,然后覆盖run方法,将需要被线程执行的代码放入run方法中,调用Thread的start方法启动线程;
2:实现Runnable接口,由于Thread类也实现了Runnable接口,因此和Thread是同级别的,只是Thread调用了系统启动线程的功能;
因此我们可以将需要被多线程执行的代码放入run方法中,然后创建一个Thread对象,将实现Runnable接口的类对象传入Thread的构造方法中
,然后启动线程;
这样做的好处是:
因为Java只支持多实现,不支持多继承。如果一旦继承了Thread类,
那么他就不能在继承其他类的;也就是出现了局限性;
同时,将需要被多线程执行的代码和启动线程的功能进行了分离,也就是说,启动线程专门有Thread负责,而需要被线程执行的代码放在实现了Runnable接口的对象中,体现了封装性;更面向对象一点,也是我们推荐的方式;
这里需要注意的是,启动线程是有操作系统完成的,并不是由java来完成的,java只是调用了底层的功能而已;
Java:中怎么消灭或者说结束一个线程呢?由于stop方法已经过时了,不让使用了,因此只能等着线程执行完run方法中的代码,也就是说让线程自然死亡,而不能认为的去枪毙他,毕老师讲的改变循环条件也就是为了让run方法结束,因此可以这样说一个线程的执行周期,或者说生命周期就是run方法的生命周期;
同时请看下面这段代码:
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
//22222222
System.out.println(Thread.currentThread().getName()
+ "thread ...");
}
}
}) {
public void run() {
while (true) {
//1111
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "haha");
}
}
}.start();
这段代 会执行那部分呢是1111还是222呢,其实这个很好理解;分解这段代 ;
class SubThread extends Thread{
public void run() {
while (true) {
//1111
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "haha");
}
}
class ImplRunnable implements Runnable{
@Override
}
综合一下就是这样:
new SubThread(new ImplRunnable()).start();
也就是直接采用的是继承Thread类来创建线程;
因此是执行的111111处的代码咯;
关于线程安全问题:
产生原因:由于多个线程操作共享数据
一个线程操作该数据还没有完成,就被另一个线程使用了;
解决这个问题的方法是给操作共享数据的代码加上线程锁;只要一个线程完全操作成功了,下一个线程才能操作;
我们判断是否会出现线程安全问题的标准有两个:
1 是否是多线程情况,并访问了共享数据;
2 各线程对共享数据的访问限制所使用的锁是否相同;
即线程的互斥;我们对于线程安全问题是使用了sychronied代码块或者函数封装体对
可能出现问题的代码进行封装,要保证使用的锁是同一个,静态中我们使用的是类的字节码文件对象,非静态中一般用的都是this指针,具体情
况可以参照懒汉式单例模式;为了统一,直接就使用某个类的字节码文件对象保证锁的唯一:
同时在加锁的时候,需要明确一下那些是要被同步的代码,只有那些设计到访问共享数据的代码才需要被同步的,因为同步加锁是需要耗费很大的资源的,即提高了安全性就会降低效率;
对于多线程需要访问和操作的数据,我们用面向对象的思想思考,既然我们要对访问的数据要用到多线程,
即涉及到多线程的访问安全问题,我们对于这个问题把他考虑下,这个可以归结为我们使用到的数据对象,即我们使用到的数据对象他应该考虑到这个问题,这个我们要
对其进行封装,我们只要使用就可以了,即我们用的时候他已经是安全的,不会出现线程安全问题了;
这里我们用到面向对象的思想考虑,即我们的对象要不要被多线程访问?我们考虑到,这个我们必须要对其进行封装,
我们用的时候就可以不要担心线程安全问题,比如StringBuffer和
StringBuilder,前者是线程安全的,就是其内部对多线程进行了处理;我们使用时无需考虑到线程安全问题,
但是线程安全了,其内部使用到了同步代码块,也就是有锁;每次执行都会有判断锁的操作,大大的影响力程序执行效率,
所以我们推荐使用StringBuilder,虽然其内部并没有解决多线程安全问题,但是程序执行效率高,如果我们要保证线程安全,
可以自己添加同步代码块;来解决;
但是我们在开发程序时要有个这样的思想,就是我们设计一个类时,该类就应该有一些功能,
对外提供他应该提供的方法,解决使用者都要解决的问题,就是说我们使用你这个类没有太多的麻烦需要解决;直接就可以用;
这就是面向对象的封装性的又以体现;
等待唤醒机制:线程间通信机制;线程之间的交互称之为线程通信;
一个程序中有多个线程存在,各个线程之间如何通信呢,通过锁对象可以互相进行通信,因为我们知道锁是任意对象,任意对象具有的方法必然是放在Object方法中去。wait和notify方法是用来唤醒和使自己等待的方法通过自己等待然后通知其他线程,或者说唤醒其他线程,做到各个线程之间通信或者说交互执行;
JDK1.5对synchronized代码块进行升级改进:
Lock替代了synchronized,condition替代了锁对象;
一个Lock可以有多个condition对象;
这个专门放到张老师的JDK1.5线程并发库中去写;
线程的同步于互斥:同步是指在多线程环境下,一个线程的执行依赖于其他线程的唤醒;侧重于线程间相互通信;
互斥是指:多个线程对共享数据的访问,同一时刻只能有一个;即对资源的访问是互斥的;