黑马程序员_多线程回顾

来源:互联网 发布:ubuntu更新软件源命令 编辑:程序博客网 时间:2024/05/17 09:32
--------------Android培训、java培训、期待与您交流!--------------

概念:

线程是进程中一个负责程序执行的控制单元,或者说执行路径。一个进程至少有一个线程,开启多个线程是为了同时运行多个代码。
线程的任务:创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。而运行的指定代码就是这个执行路径的任务。
多线程的好处:解决了多部分同时运行的问题。(就像电脑可以同时开启多个程序一样,总没人希望一个程序一个程序慢慢运行)
多线程也有弊端:开启的线程太多会导致效率的降低。(电脑开多了程序会卡的,运行的程序每个程序也会相对慢下来)


创建多线程的两种方式:

方式一:继承Thread类。
具体步骤:1、定义类继承Thread类;
2、覆盖Thread类中的run方法;
3、直接创建Thread子类对象创建线程;
4、调用start方法开启线程并调用线程的任务run方法执行。
不足:这种方法将任务和运行任务的机制混在了一起。而且,若一个类已经有了父类,也不能再继承Thread类,不好扩展。

方式二:实现Runnable接口。
具体步骤:1、定义类实现Runnable接口;
2、覆盖接口中的run方法,将线程的任务代码封装到run方法中;
3、通过Thread类创建线程对象,并Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。(因为线程的任务都封装在Runnable接口的子类对象的run方法中,所以在线程对象创建时就必须明确要运行的任务);
4、调用线程对象的start方法开启线程。
好处1、将线程的任务从线程的子类中分离出来,进行单独的封装,按照面向对象的思想将任务封装成对象;
2、避免了java单继承的局限性。
因此,实现Runnable接口的方式较经常使用。

一些小细节:1、调用 Thread.currentThread(); 获取当前正在执行线程对象的引用。
2、一个线程发生异常,则该线程结束,但其他线程不受影响。
3、任务中的run方法指明如何完成这个任务,直接调用run方法只是在同一个线程中执行该方法,而没有新线程被启动。run方法就是封装自定义线程运行任务的函数。

线程的状态(按毕向东老师的划分):

1、被创建:start() ;
2、临时阻塞状态:具备cpu执行资格,但不具备cpu执行权,等待执行权中;
3、运行:同时具备cpu执行资格和执行权;
4、冻结:同时释放cpu执行资格和执行权,wait()、sleep();
5、消亡:run方法结束或被stop方法执行。

多线程虽然可使多条代码同时运行,但也产生了问题。当一个线程在执行操作共享数据的多条代码的过程中,其他线程参与了运算,就会导致线程安全问题的产生。因而,产生线程安全问题的要素是:
1、多个线程在操作共享的数据;
2、操作共享数据的代码有多条。

于是针对以上两个要素,得出解决线程安全问题的思路
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码时,其他线程不可以参与运算。必须要当前线程吧这些代码都执行完毕后,其他线程才可以进入参与运算。这就是同步。
在消除了安全问题的同时,也产生了弊端,也就是同步的弊端:相对降低了效率,且同步外的线程都会判断同步锁。(个人感觉同步时,和单线程执行效率基本相同,也就是效率降低了。)

同步的关键字:synchronized。
同步代码块:synchronized{
code......
}
同步函数:public synchronized void method(){
code......
}

同步代码块的锁是任意对象,同步函数的锁是固定的调用者this,而同步静态函数的锁是函数所属字节码文件对象。同步代码块的灵活性更高。

同步也会产生问题——死锁。例,

Object  obj1 = new Object();Object  obj2 = new Object();while(true){synchronized(obj1){}//当线程1进入此处,持有obj1锁,需要obj2锁synchronized(obj2){}}while(true){synchronized(obj2){}//当线程2进入此处,持有obj2锁,需要obj1锁synchronized(obj1){        }}



这样便会产生两线程各持不容锁而进入下一同步内容时,无法获取对方锁的情况,也就是死锁。要避免此种代码的出现。

等待唤醒机制

wait():让线程处于冻结状态,被wait的线程会被存储到线程池中;
notify():唤醒线程池中任意一个线程;
notifyAll():唤醒线程池中所有线程。
这些方法都要定义在同步中,因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程。这些方法都是Object类中的方法,因为这些方法是监视器的方法,监视器就是锁。锁可以是任意对象,任意的对象调用的方法一定定义在Object类中。

多生产者多消费者问题:
      有多条线程执行“输出”语句,另外多条线程执行“输入”语句。在各自的run方法中,都要有标记,“提示”线程间进行通信协作。在“生产者”达到要求之后,便要停止运行,notify唤醒“消费者”进行消费后,wait方法进入冻结状态;而“消费者”消费达到要求后,也要notify唤醒”生产者“进行再次生产,以此循环。而这里的要求便是我们所要做的标记,”提示“线程该做什么操作。

if判断标记:只有一次判断,会导致不该运行的线程运行了,出现数据错误的情况。
white判断标记:解决了线程被唤醒之后,是否该继续执行的问题。
notify:只能唤醒一个线程,如果唤醒了本方线程,则没有意义,而且和while组合会导致死锁。
notifyAll:解决了本方线程一定要唤醒对方线程的问题。

因此,改用while + notifyAll 组合会比较合适。以下便是简单的代码实现:

<pre name="code" class="java">public class NewResource {private String name;private int count = 1;private boolean flag;public synchronized void set(String name) {// TODO Auto-generated method stubwhile (flag) {//此处需为循环判断,以免一次判断后,线程不该进入执行代码的情况而使数据产生错误的情况发生try {this.wait();} catch (InterruptedException e) {// TODO: handle exception}}this.name = name + count;System.out.println(Thread.currentThread().getName()+".........生产........"+this.name);count++;flag = true;this.notifyAll();}public synchronized void out() {// TODO Auto-generated method stubwhile (!flag) {try {this.wait();} catch (InterruptedException e) {// TODO: handle exception}}System.out.println(Thread.currentThread().getName()+"......................消费........................."+name);flag = false;this.notifyAll();}}


但由于 notifyAll 每次都会唤醒包括本方线程在内的所有线程,如果本方线程得到执行权,则会重复判断while的条件,效率较低。所以在jdk 1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
Lock接口:替代了同步代码块和同步函数,并将同步的隐式锁操作变成显式锁操作。同时更为灵活,可以一个锁上加上多  组监视器。Condition接口:替代了Object类中wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象。可以与任意锁进行组合。
创建一个对象:Lock lock = new ReentrantLock();通过已有的锁获取该锁上的监视器对象:(一个锁可以挂多个监视器对象)Condition con = lock.NewCondition();//Condition con1 = lock.NewCondition();//Condition con2 = lock.NewCondition();......//Condition conN = lock.NewCondition();con.await()con.signal();con.signalAll();代码形式:
lock.lock();获取锁code.......finally{lock.unlock();释放锁}


lock.lock()相当于获取synchronized上的锁,lock.unlock类似于线程执行完同步代码之后将synchronized上的锁从0掷为1(比喻),以便当前线程执行完毕之后让其他线程进入进行运算。

0 0
原创粉丝点击