首页 > 安全资讯 >

Notes: Java的多线程创建、sleep和wait的区别以及同步(sychonized)

16-10-17

job指的是具体一项工作的内容,譬如,搅拌水泥这件事情。可以找很多个worker来做。在Java当中,job就是runnable类,而thread类则是worker。

理解Java多线程的关键是理解好job-worker模式。

job指的是具体一项工作的内容,譬如,搅拌水泥这件事情。可以找很多个worker来做。在Java当中,job就是runnable类,而thread类则是worker。

线程的创建:

所以,比较提倡的创建多线程的方法是:

(1) 首先是创建一个job的内容(Runnable类)

public MyRunnable implements Runnable{
  /*可以有类成员,通过是其它类的引用,方便调用其它类的方法*/
  public MyRunnable(){
    
  }//或者创建其它的构造器,为了初始化类成员
  @Override
  public void run(){
     /*需要做的Job在这里定义,可以调用类成员引用所指向的类的方法*/
  }
}

Runnable是一个接口,所以事先这个接口需要为这个接口的唯一一个抽象方法赋予实际内容。

(2)然后,再通过这个runnable的实现类去创建不同的线程(worker):

Thread thread_one = new Thread(new MyRunnable());
Thread thread_two = new Thread(new MyRunnable());
thread_one.start();
thread_two.start();

刚刚说了上述方法是比较提倡的方法,那么说还有不提倡的方法咯?

是的,因为Thread类实际上是Runnable的实现类。所以,它也有自己的一个run方法。我们可以通过继承Thread来改写其run()方法来定义Thread具体的内容。

public MyThread extends Thread{
  /*只能自己定义类成员,不能再通过继承其它的类获得类成员*/
  public MyThread(){
    /*初始化类成员,定义其它形式的构造器可以传入其它类的对象*/
 }
  @Override
  public void run(){
    /*定义job*/
  }
}

为什么不提倡呢?虽然上述的方法省掉了一个runnable的具体类对象,但是它没办法通过继承其它类获得对应的类成员。而且,job-worker的模式在runnable/ thread的定义方法中完美体现出来,而在这里却被混为一体了。当我们为同一个job创建多个线程的时候,还是runnable、thread这种定义比较方便。

Race Condition:

这里需要注意一点,MyThread.start()、thread_1.start()(如果直接调用Thread类的run或者Runnable的run则还是在同一个线程执行)只是告诉JVM把我这个线程加入调度队列当中,并不代表该线程会马上执行。事实上,在贯穿整个多线程编程当中,需要谨记住一点:You never know a given may or may not be running at anytime。

因为这个原因,某个线程可能因为JVM线程调度的原因,就停在了job中间的某句话执行之后。而另一个线程就开始执行了。而因为这样的现象存在,所以对于多个线程共享同一个对象的堆空间的时候,很有可能出现race condition。

举个例子【1】:
 

上述代码如果在两个线程同时执行的,可能存在这么一种情况:

一开始,Thread1首先执行完if,判断条件成立后,进入if体,准备执行添加语句时,被强制停止了。然后Thread 2开始执行,这时候集合还是没有元素的,所以thread2成功执行了所有语句后,thread1又继续执行,这时候,thread1因为已经进入了if语句后,直接又往集合添加元素。因为这样的原因,使得代码的执行效果和我们设计逻辑不符。

这种情况有个专业的名称,称为race condition。

Synchronized:

为了避免这种的情况,Java提供了同步的机制来保证代码的逻辑。同步锁的机制,是用来保护某个类(或者对象)的堆空间内存的内容在同一时刻只能被一个线程访问、修改。通过synchronized修改堆空间的代码块或者方法来实现。

打个比方来说,一个类就是一个大澡堂,澡堂的每个洗澡的隔间是类的方法或者代码块,线程都是排队使用隔间的人,同步锁是打开隔间门的钥匙。一把同步锁可以打开所有的隔间门,但是只有一个同步锁。并且一个进去了,其它所有的隔间也会被锁住。同步锁和隔间也分两种。一种对象级别的锁和隔间,一种是类级别的锁和隔间。对应着以下的情况:

对象级别:

1)如果这个锁上锁的对象是类的对象,则这个类所以的synchronized的实例方法和同样的用对象synchronized代码块都不能允许其它线程执行。也就是说,当一个线程获得了类级别的同步锁,进入了其中一个对象级别的隔间,其它对象级别的隔间都不能被其它线程使用。

2)如果synchronized的是一个static方法,因为这个方法就不是仅属于某个对象而是属于整个类的了,一旦一个线程进入了这个代码块就会将这个类的所有对象的所有synchronized方法或synchronized同步代码块锁定,其他的线程就没有办法访问所有这些对象的synchronized方法和synchronized代码块

类级别的:

如果对于某个代码块用的是synchronized(.class)的类级别的锁,一旦一个线程进入了这个代码块就会将整个类的所有这个synchronized(.class) 同步代码块锁定,其他的线程就没有办法访问这个对象的synchronized(**.class) 代码块,这种锁也是class级别的,但要注意在这种情况下,其他线程仍然是可以访问仅做了synchronized(对象)的代码块或非静态方法的,因为它们仅仅是对当前对象的锁定。【2】
 

其实,本质上而言,synchronized(object)一个代码块和synchronized一个方法是相同的。两者都是对象级别的锁。方法同步锁的本质可以是:

public void func(){
  synchronized(object){
     ...
  }
}
其实就是把函数内的代码块同步了。上述代码等价于:
public synchronized void func(){
    ...

}

线性安全类真的线性安全吗?

有一个很有趣的现象,在多线程的环境当中,优先选择使用线性不安全类。为什么?

线性安全类的意思是,其中所有的方法都是加了对象级同步锁的。然而,光光是方法加了同步锁是远远不够的,举个例子,看看以下代码:

众所周知,Vector是线性安全的,与之相对的,ArrayList是线性不安全的。那为什么我们很少看到用Vector呢?很重要的原因是,因为Vector不遵循于Collection.List的接口,一开始并没有add等众所周知的结果,后来我猜测使用了适配器模式开发了类似的接口。其次,与HashTable不够HashMap普及的原因一致,线性安全的类会更慢,效率更低(同步锁的原因),而且当只有一个线程的时候,就没必要使用线性安全类了。

现在,来说说另外一个原因:当在多线程环境中,也不推荐使用线性安全类。

原因见上图,contains方法是同步的,add方法也是同步的,但是假设有这么个情况,一个进程获得了contains的同步锁,判断返回false后,正准备执行add的时候被JVM切换到了另外一个线程,这时候另外一个线程获得了contains的同步锁,判断同样返回false。然后成功add了。接着刚刚的那个线程轮到它执行的时候,也会add同一个元素。

这是因为两个线程安全的方法之间,并没有任何的方法确保线程同步。所以,就算Vector,你还是需要在if的前面加上synchronized(myVector)的。与其加两次锁,还不如直接使用ArrayList用一次加锁来得直接。

Thread.sleep(int ms)和Object.wait()的区别:

对于thread类,有一个类成员方法是sleep(int ms),让一个线程休眠(阻塞)一段时间,以ms为单位。但其不证明其休眠刚刚好是对应数字的时间,因为当对应的时间结束后,其进入了就绪状态,等待着JVM的调用。

而对应Object.wait()的作用也是让调用它的线程进入休眠。其也可以指定一个时间让它醒过来,也可以不指定,通过别人线程调用notify()或者notifyAll()来唤醒它。但是就算指定一个时间,也能被notify()或者notifyAll()提前唤醒。

那么两个休眠有什么不同呢?

1)前者是没有意义的阻塞,纯粹就是让它安静一会;后者则是大多数为了等待另外一个线程准备好其要资源,再起来继续工作;

2)所以,来到了最大的不同了:sleep的进程不会释放同步锁,所以,如果一个线程进入了一个同步方法或者一个同步代码块,在块内调用sleep()了,则在它休眠的时间内,其它的线程不同进入对象级别的同步的所有代码块或者方法。但是,调用wait()则不同了,线程会释放掉同步锁,让其它进程能进入其它的或者当前的对象级别的代码块或者方法内部继续执行。

Reference:

[1] Terry Lee <Java Development>
[2] https://segmentfault.com/a/1190000003810166
相关文章
最新文章
热点推荐