JAVA 基础之 多线程
关键:多线程同步,有两种角度。
1.从JVM的角度来看,instance = new instance () 不是一个原子操作,在jvm被分成三步,分配内存,设置intance引用指向实例,初始化,JVM乱序执行。需要考虑线陈并发处理问题。可以通过volatile控制语句的原子性。
2.从多条语句之间的角度来看: if(instance =null) {instance = new instance ()} 这样两句话,如何保证同步。使用sychornized 关键字。通过锁,保证同步代码块 。
1. 主线程
Static Thread currentThread(); //currentThread() 是Thread类的共有静态方法
Thread t = Thread.currentThread(); //获得主线程
Static void Sleep(); //Thread 类的 静态方法,是当前线程睡眠 , 单位毫秒,可修改单位
2. 线程的实现 :
1) Runnable 接口
2)扩展Thread类
class MyThread extends Thread{ public int x = 0; public void run(){ System.out.println(++x); } } class R implements Runnable{ private int x = 0; public void run(){ System.out.println(++x); } } public class Test { public static void main(String[] args) throws Exception{ for(int i=0;i<10;i++){ Thread t = new MyThread(); t.start(); } Thread.sleep(10000);//让上面的线程运行完成 R r = new R(); for(int i=0;i<10;i++){ Thread t = new Thread(r); t.start(); } } }
总结:Thread和Runnable是实现java多线程的2种方式,runable是接口,thread是类,建议使用runable实现java多线程,不管如何,最终都需要通过thread.start()来使线程处于可运行状态。
认识Thread的start和run
1) start:
用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到spu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
2) run:
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
sleep & yield
sleep()方法让线程睡眠一段时间,时间过后,线程才会去争取cpu时间片。
Java1.5提供了一个新的TimeUnit枚举,让sleep的可读性更好。
yield()方法让线程主动让出cpu,让cpu重新调度以决定哪个线程可以使用cpu。
3. 线程同步:
http://www.cnblogs.com/yyyyy5101/archive/2011/07/20/2112157.html
1) 同步方法 : synchronize关键字修饰 需要保证同步的方法(临界区)
2) 同步语句 : synchronized块
在synchronized关键字后面,要传一个对象参数,任何线程要进入临界区时必须先要获得该对象的锁,退出临界区时要释放该对象的锁,这样别的线程才有机会进入临界区。
临界区和synchronized方法,其原理都是一样的,都是通过在对象上加锁来实现的,只不过临界区来得更加灵活,因为它不光可以对this对象加锁,也可以对任何别的对象加锁。
在使用Java多线程中会需要使用Synchronized块同步方法。我们不仅可以通过synchronized块来同步一个对象变量。也可以使用synchronized块来同步类中的静态方法和非静态方法。
synchronized关键字有两种用法。第一种就是在《使用Synchronized关键字同步类方法》一文中所介绍的直接用在方法的定义中。另外一种就是synchronized块。我们不仅可以通过synchronized块来同步一个对象变量。也可以使用synchronized块来同步类中的静态方法和非静态方法。
synchronized块的语法如下:
- public void method()
- {
- … …
- synchronized(表达式)
- {
- … …
- }
- }
一、非静态类方法的同步
从《使用Synchronized关键字同步类方法》一文中我们知道使用synchronized关键字来定义方法就会锁定类中所有使用synchronzied关键字定义的静态方法或非静态方法,但这并不好理解。而如果使用synchronized块来达到同样的效果,就不难理解为什么会产生这种效果了。如果想使用synchronized块来锁定类中所有的同步非静态方法,需要使用this做为synchronized块的参数传入synchronized块国,代码如下:
通过synchronized块同步非静态方法
- public class SyncBlock
- {
- public void method1()
- {
- synchronized(this) // 相当于对method1方法使用synchronized关键字
- {
- … …
- }
- }
- public void method2()
- {
- synchronized(this) // 相当于对method2方法使用synchronized关键字
- {
- … …
- }
- }
- public synchronized void method3()
- {
- … …
- }
- }
在上面的代码中的method1和method2方法中使用了synchronized块。而第017行的method3方法仍然使用synchronized关键字来定义方法。在使用同一个SyncBlock类实例时,这三个方法只要有一个正在执行,其他两个方法就会因未获得同步锁而被阻塞。在使用synchronized块时要想达到和synchronized关键字同样的效果,必须将所有的代码都写在synchronized块中,否则,将无法使当前方法中的所有代码和其他的方法同步。
除了使用this做为synchronized块的参数外,还可以使用SyncBlock.this作为synchronized块的参数来达到同样的效果。
在内类(InnerClass)的方法中使用synchronized块来时,this只表示内类,和外类(OuterClass)没有关系。但内类的非静态方法可以和外类的非静态方法同步。如在内类InnerClass中加一个method4方法,并使method4方法和SyncBlock的三个方法同步,代码如下:
使内类的非静态方法和外类的非静态方法同步
- public class SyncBlock
- {
- … …
- class InnerClass
- {
- public void method4()
- {
- synchronized(SyncBlock.this)
- {
- … …
- }
- }
- }
- … …
- }
在上面SyncBlock类的新版本中,InnerClass类的method4方法和SyncBlock类的其他三个方法同步,因此,method1、method2、method3和method4四个方法在同一时间只能有一个方法执行。
Synchronized块不管是正常执行完,还是因为程序出错而异常退出synchronized块,当前的synchronized块所持有的同步锁都会自动释放。因此,在使用synchronized块时不必担心同步锁的释放问题。
二、静态类方法的同步
由于在调用静态方法时,对象实例不一定被创建。因此,就不能使用this来同步静态方法,而必须使用Class对象来同步静态方法。代码如下:
通过synchronized块同步静态方法
- public class StaticSyncBlock
- {
- public static void method1()
- {
- synchronized(StaticSyncBlock.class)
- {
- … …
- }
- }
- public static synchronized void method2()
- {
- … …
- }
- }
在同步静态方法时可以使用类的静态字段class来得到Class对象。在上例中method1和method2方法同时只能有一个方法执行。除了使用class字段得到Class对象外,还可以使用实例的getClass方法来得到Class对象。上例中的代码可以修改如下:
使用getClass方法得到Class对象(每一个加载过的类在JVM中都有Class对象,包括基本数据类型和void关键字)
- public class StaticSyncBlock
- {
- public static StaticSyncBlock instance;
- public StaticSyncBlock()
- {
- instance = this;
- }
- public static void method1()
- {
- synchronized(instance.getClass())
- {
- }
- }
- }
在上面代码中通过一个public的静态instance得到一个StaticSyncBlock类的实例,并通过这个实例的getClass方法得到了Class对象(一个类的所有实例通过getClass方法得到的都是同一个Class对象,因此,调用任何一个实例的getClass方法都可以)。我们还可以通过Class对象使不同类的静态方法同步,如Test类的静态方法method和StaticSyncBlock类的两个静态方法同步,代码如下:
Test类的method方法和StaticSyncBlock类的method1、method2方法同步
- public class Test
- {
- public static void method()
- {
- synchronized(StaticSyncBlock.class)
- {
- }
- }
- }
注意:在使用synchronized块同步类方法时,非静态方法可以使用this来同步,而静态方法必须使用Class对象来同步。它们互不影响。当然,也可以在非静态方法中使用Class对象来同步静态方法。但在静态方法中不能使用this来同步非静态方法。这一点在使用synchronized块同步类方法时应注意。
对象锁
package com.zj.lock; import java.util.concurrent.TimeUnit; public class Resource1 { public void f() { // other operations should not be locked... System.out.println(Thread.currentThread().getName() + ":not synchronized in f()"); synchronized (this) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + ":synchronized in f()"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void g() { // other operations should not be locked... System.out.println(Thread.currentThread().getName() + ":not synchronized in g()"); synchronized (this) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + ":synchronized in g()"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void h() { // other operations should not be locked... System.out.println(Thread.currentThread().getName() + ":not synchronized in h()"); synchronized (this) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + ":synchronized in h()"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { final Resource1 rs = new Resource1(); new Thread() { public void run() { rs.f(); } }.start(); new Thread() { public void run() { rs.g(); } }.start(); rs.h(); } }//匿名内部类
匿名类是不能有名称的类,所以没办法引用它们。必须在创建时,作为new语句的一部分来声明它们。这就要采用另一种形式的new语句,如下所示: new <类或接口> <类的主体> 这种形式的new语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。它还创建那个类的一个新实例,并把它作为语句的结果而返回。要扩展的类和要实现的接口是new语句的操作数,后跟匿名类的主体。如果匿名类对另一个类进行扩展,它的主体可以访问类的成员、覆盖它的方法等等,这和其他任何标准的类都是一样的。如果匿名类实现了一个接口,它的主体必须实现接口的方法。
附带问题:为什么局部内部类(即:定义在方法中的内部,包括匿名内部类),使用的参数必须为final?
答 :局部变量的生命周期与局部内部类的对象的生命周期的不一致性!
假设:
1. 我们在方法内定义了一个 匿名的 Thread 子类,他使用了方法的局部参数,然后我让这个线程运行去,因为是不同的线程,那么当我这个方法的启动线程
的语句执行过了,而且我修改了这个参数或局部变量,那么那个线程启动执行的时候是不是会出现莫名其妙的问题:运行时刻能访问到的变量太难以捉摸了,我是该
复制一份过去给新线程运行时使用还是到时候再来取呢(再来取时已经物是人非了)?
2. 设方法f被调用,从而在它的调用栈中生成了变量i,此时产生了一个局部内部类对象inner_object,它访问了该局部变量i .当方法f()运行结束后,局部变量i就已死亡了,不存在了.但:局部内部类对象inner_object还可能 一直存在(只能没有人再引用该对象时,它才会死亡),它不会随着方法f()运行结束死亡.这时:出现了一个"荒唐"结果:局部内部类对象 inner_object要访问一个已不存在的局部变量i!
Java 为了消除这个编程中可能出现的歧义,使用方法内的内部类时如果访问了方法的参数或局部变量,那么它应该是 final 的。
final 原理,当变量是final时,通过将final局部变量"复制"一份
另详解: http://feiyeguohai.iteye.com/blog/1500108
如果满足下面的一些条件,使用匿名内部类是比较合适的:
?只用到类的一个实例。 ?类在定义后马上用到。
?类非常小(SUN推荐是在4行代码以下)
?给类命名并不会导致你的代码更容易被理解。
在使用匿名内部类时,要记住以下几个原则:
?匿名内部类不能有构造方法。
?匿名内部类不能定义任何静态成员、方法和类。
?匿名内部类不能是public,protected,private,static。
?只能创建匿名内部类的一个实例。
?一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
?因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
4.线程间通信 :
notify() wait() notifyAll();
Java中如何实现线程同步
在Java中,线程的同步涉及到synchronized和三个方法wait()、notify()和notifyAll()。
synchronized在上篇中已经讲过了,这里就不再重复了。
wait()方法与Thread类的sleep()和yield()方法类似,都是让当线程睡眠,或者说是暂停执行;与之不同的是wait()方法会释放掉当前对象的锁,也因为此wait()方法必须在synchronized块里才能被调用。
notify()和notifyAll()方法用于唤醒之前调用wait()方法睡眠的线程,与wait()方法一样,notify()和notifyAll()也必须在synchronized块里才能被调用。
注意wait()、notify()和notifyAll()三个方法都是Object类的,而不是Thread类的。因为这三个方法都涉及的锁的操作,而锁的操作适用于所有的对象。