java基础 -- 多线程总结(一)--基本概念

来源:互联网 发布:排八字软件 编辑:程序博客网 时间:2024/04/29 12:47

1.创建线程的三种方式对比

通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明并抛出异常而已。因此也可以将实现Runnable接口和Callable接口归为一种方式。这种方式与继承Thread方式之间的主要差别如下。

采用Runnable/Callable接口的方式创建多线程的优缺点:

  1. 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
  2. 在这种方式下,多线程可以共享一个target对象,所以非常适合多个线程来处理同一份资源的情况,从而可以将cpu、代码和数据分开形成清晰的模型,较好地体现了面向对象的思想。
  3. 劣势是,编程稍稍复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

 

Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,下面来看一个简单的例子:

public class CallableAndFuture {    public static void main(String[] args) {        Callable<Integer> callable = new Callable<Integer>() {            public Integer call() throws Exception {                return new Random().nextInt(100);            }        };        FutureTask<Integer> future = new FutureTask<Integer>(callable);        new Thread(future).start();        try {            Thread.sleep(5000);// 可能做一些事情            System.out.println(future.get());        } catch (InterruptedException e) {            e.printStackTrace();        } catch (ExecutionException e) {            e.printStackTrace();        }    }}

采用继承Thread类的方式创建多线程的优缺点:

  1.  劣势是,因为多线程已经继承了Thread类,所以不能再继承其他父类。
  2. 优势是,编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。


2.启动线程

启动线程使用start()方法,而不是run()方法!当对象调用了start()方法之后,该线程处于就绪状态,,Java虚拟机会为其创建方法栈和程序计数器,处于这个状态的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行取决于JVM里的线程调度器的调度。
永远不要调用线程的run()方法!调用strart()方法来启动线程,系统会把该run()方法当成线程执行体来处理;如果直接调用线程对象的run()方法,则run()方法就会立即执行,而且在run()方法返回之前其他线程无法并发执行--也就是说,如果调用当前线程的run(0方法,系统把线程对象当成一个普通对象,而run()方法也是一个普通方法,而不是线程执行体。

3.sleep()方法和yeild()方法的区别

  1. sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yeild()方法只会给优先级相同,或者优先级更高的线程执行机会。

  2. sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yeild()方法不会将线程转入阻塞状态,他只是强制将当前线程进入就绪状态。因此完全有可能某个线程调用yeild()方法暂停之后,立即在此获得处理器资源被执行。
  3. sleep()方法声明跑出了InterruptedException已成,所以使用sleep()方法时要么捕捉该异常,要么显示声明抛出该异常;而yeild()方法则没有声明抛出任何异常。
  4. sleep()方法比yeild()方法有更好的移植性,通常不建议使用yeild()方法来控制并发线程的执行。

4.synchronized

同步代码块

/*JAVA对于多线程的安全问题提供了专业的解决方式就是同步代码块synchronized(对象)//这个对象可以为任意对象{需要被同步的代码}对象如同锁,持有锁的线程可以在同步中执行没持有锁的线程即使获取CPU的执行权,也进不去同步的前提:1,必须要有两个或者两个以上的线程2,必须是多个线程使用同一个锁必须保证同步中只能有一个线程在运行好处:解决了多线程的安全问题弊端:多个线程需要判断锁,较为消耗资源*/class Tick implements Runnable{private int tick = 50;Object obj = new Object();//申请一个对象public void run(){while(true){synchronized(obj){if(tick > 0){//try{Thread.sleep(40);}catch(Exception e){}System.out.println( Thread.currentThread().getName() + " sail --" + tick--);}}}}}class TickDemo{public static void main(String []args){Tick t = new Tick();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();}}

同步方法

public synchronized  static void method(){    // ....code } /........................................./


对于Synchronized修饰的实例方法(非static方法)而言无需指定同步监视器,同步方法的监视器就是this,也就是调用该方法的对象。


释放同步监视器的锁定

释放:

1.当前线程的同步方法,同步代码块正常运行结束.
2.当前线程在同步代码块,同步方法中遇到break,return什么的,
3发生异常,错误.
4.程序执行了同步监视器对象的wait()方法,线程暂停并释放同步监视器.

不释放:

1.在期内执行了Thread.sleep,thread.yield方法.不会放开.
2.用了suspend挂起,当然不应该用suspend和resume方法.



5.wait()、notify()和notifyAll()

     wait()、notify()和notifyAll()是Object类中的方法
   从这三个方法的文字描述可以知道以下几点信息:
  1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
  2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
  3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象monitor,
     则只能唤醒其中一个线程;
  4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;

有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。
  
上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);
  
notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。
  
同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
  
nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。
  
这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。







0 0