黑马程序员——java基础 多线程(复习)

来源:互联网 发布:ios11app下载不了软件 编辑:程序博客网 时间:2024/05/21 05:36

-----------android培训、java培训、java学习型技术博客、期待与您交流!------------

一、理解多线程

1.什么线程?线程和我们通常讲的进程有什么区别?

进程:进程可以理解为一个正在执行的程序,每个程序执行,都有一个执行顺序,该执行顺序就是一个执行路径,或是一个控制单元。
线程:线程就是进程中一个独立的控制单元。线程在控制着进程的执行,一个进程中至少包含了一个线程。

     jvm启动时,也会创建一个进程,在windows中就是java.exe  该进程中至少有一个线程负责java程序的执行,而且该线程的执行代码存在于main方法中,这个线程也被称为主线程。


二、在java中,如何自定义一个线程?

根据java的面向对象思想,java将线程这类事物也进行了描述,并进行了封装,这就是Thread类。

关于创建线程,java中提供了两种方法:

第一种:1.将类继承Thread类,然后覆写Thread类中的run方法。

2.当我们创建了一个该类的对象,就意味着创建了一个线程。

3.调用start方法开启该线程。

第二种:1.将类实现Runnable接口,并覆写run方法。

2.创建该类对象(因为该类没有直接继承Thread类,所以创建的该对象不是一个线程)

3.创建一个Thread类对象,并将自定义类的对象作为参数传入,由于Thread类认识Runnable接口,这样在Thread类对象初始化的时候,就意味着创建了一个自定义类的线程。

4.调用start方法开启该线程。

我们发现,不管是哪种方法,都要覆盖run方法,那么到底为什么要覆盖run方法呢?

Thread类用于描述线程,该类就定义了一个功能,用于存储需要被运行的代码。该存储功能就是run方法。我们自定义线程时,就要对其进行覆写。


两种创建线程方法的代码示例:

class Demo extends Thread{public void run(){for(int x=0; x<60; x++)System.out.println("demo run----"+x);}}class ThreadDemo {public static void main(String[] args) {Demo d = new Demo();//创建好一个线程。d.start();//开启线程并执行该线程的run方法。}}

class Ticket implements Runnable{private  int tick = 100;public void run(){while(true){if(tick>0){System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);}}}}class  TicketDemo{public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);//创建了一个线程;Thread t2 = new Thread(t);//创建了一个线程;Thread t3 = new Thread(t);//创建了一个线程;Thread t4 = new Thread(t);//创建了一个线程;t1.start();t2.start();t3.start();t4.start();}}

三、线程同步

1.为什么要进行线程同步?

在此之前,我们先要了解一下线程的状态。线程的状态分为:被创建,运行,冻结,临时状态和消亡,共5种。

线程专题如下图:


线程状态上可以了解到,线程状态需要两个因素:1.具备运行资格。2.具备CPU的试用权。

那么由于这两个因素共同决定了线程在某一时刻是否在运行,也就是说当多个线程同时在操作同一个共享数据时,就会出现安全问题。

比如:正常情况:线程一进去写入数据,完毕退出后,线程二才进去获取这个数据。但是由于线程是 有可能冻结的,也就是说线程一进去后还没来得及写入数据就sleep了,之后线程二抢到了cpu的使用权,进去获取数据,这样就出现了问题。

那么如何解决这个问题?

方法就是线程同步,简单说就是上锁。线程一进去后就锁上了,除非它执行完毕,并解锁,不然其他线程就算有cpu使用权,也进不来,因为没有持有锁。

2.线程同步的方法

第一种:同步代码块

synchronized 关键字。同步代码块。

例:synchronized( 对象 ){

需要被执行的代码;

}    // 该参数 传入的是一个对象,这个对象就是锁,随便什么都可以,一般使用Object即可。

如何判断哪些代码需要同步?

就看那些语句是在操作共享数据。

第二种:同步函数

synchronized 可以作为修饰符,来修饰函数。被其修饰的函数被称为同步函数。

在synchronized中,一般同步函数使用的是this锁。但当函数被static修饰后,这个函数就会优先于对象加载,那么这时同步函数使用的就是字节码文件锁。

3.同步的前提

同步的前提有两个:

必须要有两个及两个以上的线程。

多个线程必须要使用同一个锁。

注意:如果发现加了同步,还是出现了安全问题,一定是这两个前提没有同时满足。

4.多线程之间的通信

线程间的通信:其实就是多个线程在操作同一资源,但操作的方式不同。

线程中的等待、唤醒机制:wait();  、 notify();

wait 和 sleep 的区别:

wait : 释放资源,释放锁。   该方法会出现异常,一定要try,catch

sleep :释放资源,不释放锁。

wait 、 notify 、 notifyAll 定义在了Object 中,因为这些方法是监视器方法,监视器就是锁,锁是任意对象。只有同步才需要锁。他们只能用在同步中。

每个线程在wait 、notify 、 notifyAll时必须标示锁。

多线程通信的方法:

(1)在资源类中加入一个判断条件, boolean  flag = false;我们使线程A对应false才会执行, 线程B对应true才会执行。

(2)当线程A进入判断标记为false,进去执行后,将判断标记改为true,然后notify线程B,最后自己wait

(3)此时线程B 加入进来,判断标记为true ,表示线程B 可以执行,执行后又将标记改为false ,然后notify线程A,最后自己wait

这样就实现了线程间的通信。

等待的线程会在线程池中,当notify时,通常唤醒,最早进入线程池等待的线程,这时,提供了notifyAll的方法,一次性唤醒所有的线程,但是notifyAll有一个问题,它唤醒了所有的线程(包括唤醒了己方线程)。这样己方线程和对方线程就会抢夺cpu的使用权。

5.Lock接口

在JDK 1.5后,将同步和锁封装成了对象。

Lock接口的出现,替代了同步代码块和同步函数,并将同步的隐式操作变成了显式操作。更加灵活。

lock()  :获取锁

unlock() :释放锁,通常会放在finally 中。

newCondition( )  :将我们自定义的锁返回给Condition实例。

只要子类实现了lock接口,就可以自定义,加锁和解锁的方法。

Condition 接口,替代了Object中的监视器方法。Condition接口中,提拱了await()等待、signal()唤醒、signalAll()唤醒所有,这些方法。

记得:await( )  :该方法会出现异常,一定要try,catch

我们知道接口是不能定义对象的,那么Lock接口如何来使用呢?

ReentrantLock类:该类实现了Lock接口,我们用这个类来定义锁。

生产者和消费者的示例:

class Resource{private String name;private int count = 1;private boolean flag = false;//  t1    t2private Lock lock = new ReentrantLock();private Condition condition_pro = lock.newCondition();private Condition condition_con = lock.newCondition();public  void set(String name)throws InterruptedException{lock.lock();try{while(flag)condition_pro.await();//t1,t2this.name = name+"--"+count++;System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);flag = true;condition_con.signal();}finally{lock.unlock();//释放锁的动作一定要执行。}}//  t3   t4  public  void out()throws InterruptedException{lock.lock();try{while(!flag)condition_con.await();System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);flag = false;condition_pro.signal();}finally{lock.unlock();}}}class Producer implements Runnable{private Resource res;Producer(Resource res){this.res = res;}public void run(){while(true){try{res.set("+商品+");}catch (InterruptedException e){}}}}class Consumer implements Runnable{private Resource res;Consumer(Resource res){this.res = res;}public void run(){while(true){try{res.out();}catch (InterruptedException e){}}}}

总结:

(1)Lock接口用来定义锁(通过创建子类ReentrantLock对象),然后该对象调用newCondition( ) 方法来返回定义的锁给Condition来使用。可以使用该锁的方法有三个:await(); 、 signal(); 、 signalAll() ;

(2)Condition的好处,一个锁可以定义多个对象。

6.停止线程

停止线程的方法只有一个,那就是让run方法结束。开启多线程时,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是停止线程。控制循环的方法通常是使用定义标记的方式开完成的。

但有个特殊情况,当线程处于冻结状态的时候,有时无法读取标记,这是线程就不能停止,这时必须使用一个方法来强制清除冻结状态,该方法就是:interrupt( );

interrupt( ) :强制清除线程的冻结状态。该方法会抛出InterruptedExecption异常,一定要记得处理。

join( ) 方法:当A线程执行到B线程的join方法时,A线程就会释放cpu执行权给B线程,当B线程执行完毕后,再唤醒A线程继续执行。

0 0
原创粉丝点击