您的位置:首页 > 编程语言 > Java开发

Java多线程笔记

2016-05-30 21:51 239 查看
多线程优点
资源利用率更好(在发生IO等待时,利用处理器做其他事情)

程序设计在某些情况下更简单

程序响应更快
 
 
多线程代价

设计复杂(线程交互复杂,错误难以发现,重现和修复)

上下文切换开销

增加资源消耗(需要内存维持线程的本地堆栈)
 
 
创建和运行Java线程

创建Thread的子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyThread extends Thread {

    public void run(){

         System.out.println("MyThread running");

    }

}

MyThread myThread = new MyThread();

myTread.start();

 
 
Thread thread = new Thread(){

   public void run(){

     System.out.println("Thread Running");

   }

};

thread.start();

  
实现Runable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyRunnable implements Runnable {

    public void run(){

        System.out.println("MyRunnable running");

    }

}

Thread thread = new Thread(new MyRunnable());

thread.start();

 
 
Runnable myRunnable = new Runnable(){

   public void run(){

     System.out.println("Runnable running");

   }

}

Thread thread = new Thread(myRunnable);

thread.start();

  
线程名

1
2
3
4
MyRunnable runnable = new MyRunnable();

Thread thread = new Thread(runnable, "New Thread");

thread.start();

System.out.println(thread.getName());

  
线程安全和共享资源
局部变量
局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是线程安全的。

局部的对象引用
对象的局部引用和基础类型的局部变量不太一样。尽管引用本身没有被共享,但引用所指的对象并没有存储在线程的栈内。所有的对象都存在共享堆中。 如果在某个方法中创建的对象不会逃逸出(即该对象不会被其它方法获得,也不会被非局部变量引用到)该方法,那么它就是线程安全的。 实际上,哪怕将这个对象作为参数传给其它方法,只要别的线程获取不到这个对象,那它仍是线程安全的。

对象成员
对象成员存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。

线程控制逃逸规则
1.如果一个资源的创建,使用,销毁都在同一个线程内完成,且永远不会脱离该线程的控制,则该资源的使用就是线程安全的。
2.即使对象本身线程安全,但如果该对象中包含其他资源(文件,数据库连接),整个应用也许就不再是线程安全的了。 比如2个线程都创建了各自的数据库连接,每个连接自身是线程安全的,但它们所连接到的同一个数据库也许不是线程安全的。
 
 
 
 
线程安全及不可变性

创建不可变的共享对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public
class ImmutableValue{

    private
int value = 0;         //没有set方法

 
 
    public ImmutableValue(int value){

        this.value = value;

    }

 
 
    public
int getValue(){

        return
this.value;

    }

     
 
    /*

    **构造新对象返回

    */

    public ImmutableValue add(int valueToAdd){

        return
new ImmutableValue(this.value + valueToAdd);

    }

}

  
ImmutableValue类是线程安全的,但使用它的类未必安全,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public
void Calculator{

    private ImmutableValue currentValue = null;

 
 
    public ImmutableValue getValue(){

        return currentValue;

    }

 
 
    public
void setValue(ImmutableValue newValue){

        this.currentValue = newValue;

    }

 
 
    public
void add(int newValue){

        this.currentValue = this.currentValue.add(newValue);

    }

}
要使Calculator类实现线程安全,将getValue()、setValue()和add()方法都声明为同步方法即可
 
 Java同步块
实例方法同步(锁定实例对象,一个实例一个线程)

1
2
3
public
synchronized
void add(int value){

    this.count += value;

}

  
静态方法同步(锁定Class对象,一个类一个线程)

1
2
3
public
static
synchronized
void add(int value){

    count += value;

}

  
实例方法中的同步块(锁定this对象,一个this对象一个线程)

1
2
3
4
5
public
void add(int value){

    synchronized(this){

       this.count += value;

    }

}

  
静态方法中的同步块(锁定Class对象,一个类一个线程)

1
2
3
4
5
6
7
8
public
class MyClass {

    public
static
void log2(String msg1, String msg2){

       synchronized(MyClass.class){

          log.writeln(msg1);

          log.writeln(msg2);

       }

    }

}

 
 
  
线程通信
通过共享对象通信

1
2
3
4
5
6
7
8
9
10
11
12
public
class MySignal{

 
 
    protected
boolean hasDataToProcess = false;

 
 
    public
synchronized
boolean hasDataToProcess(){

        return
this.hasDataToProcess;

    }

 
 
    public
synchronized
void setHasDataToProcess(boolean hasData){

        this.hasDataToProcess = hasData;

    }

}

  
忙等待

1
2
3
4
5
6
7
protected MySignal sharedSignal = ...

 
 
...

 
 
while(!sharedSignal.hasDataToProcess()){

  //do nothing... busy waiting

}

  
wait(),notify()和notifyAll()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public
class MonitorObject{

}

 
 
public
class MyWaitNotify{

 
 
    MonitorObject myMonitorObject = new MonitorObject();

 
 
    public
void doWait(){

      synchronized(myMonitorObject){

        try{

          myMonitorObject.wait();

        } catch(InterruptedException e){...}

      }

    }

 
 
    public
void doNotify(){

      synchronized(myMonitorObject){

        myMonitorObject.notify();

      }

    }

}

  
丢失的信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public
class MyWaitNotify2{

 
 
  MonitorObject myMonitorObject = new MonitorObject();

  boolean wasSignalled = false;

 
 
  public
void doWait(){

    synchronized(myMonitorObject){

      if(!wasSignalled){          //防止notify之后才wait,再也无法醒来

        try{

          myMonitorObject.wait();

         } catch(InterruptedException e){...}

      }

      //clear signal and continue running.

      wasSignalled = false;

    }

  }

 
 
  public
void doNotify(){

    synchronized(myMonitorObject){

      wasSignalled = true;

      myMonitorObject.notify();

    }

  }

}

  
假唤醒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public
class MyWaitNotify3{

 
 
  MonitorObject myMonitorObject = new MonitorObject();

  boolean wasSignalled = false;

 
 
  public
void doWait(){

    synchronized(myMonitorObject){

      while(!wasSignalled){          //如果发生假唤醒,wasSignalled为false,将进入循环再次睡眠

        try{

          myMonitorObject.wait();

         } catch(InterruptedException e){...}

      }

      //clear signal and continue running.

      wasSignalled = false;

    }

  }

 
 
  public
void doNotify(){

    synchronized(myMonitorObject){

      wasSignalled = true;

      myMonitorObject.notify();

    }

  }

}

  
多个线程等待相同信号 如果你有多个线程在等待,被notifyAll()唤醒,但只有一个被允许继续执行,使用while循环也是个好方法。 每次只有一个线程可以获得监视器对象锁,意味着只有一个线程可以退出wait()调用并清除wasSignalled标志(设为false)。 一旦这个线程退出doWait()的同步块,其他线程退出wait()调用,并在while循环里检查wasSignalled变量值。 但是,这个标志已经被第一个唤醒的线程清除了,所以其余醒来的线程将回到等待状态,直到下次信号到来。
 
 

不要在字符串常量或全局对象中调用wait()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public
class MyWaitNotify{

 
 
  String myMonitorObject = "";

  boolean wasSignalled = false;

 
 
  public
void doWait(){

    synchronized(myMonitorObject){

      while(!wasSignalled){

        try{

          myMonitorObject.wait();

         } catch(InterruptedException e){...}

      }

      //clear signal and continue running.

      wasSignalled = false;

    }

  }

 
 
  public
void doNotify(){

    synchronized(myMonitorObject){

      wasSignalled = true;

      myMonitorObject.notify();

    }

  }

}
在空字符串作为锁的同步块(或者其他常量字符串)里调用wait()和notify()产生的问题是,JVM/编译器内部会把常量字符串转换成同一个对象。 这意味着,即使你有2个不同的MyWaitNotify实例,它们都引用了相同的空字符串实例。 同时也意味着存在这样的风险:在第一个MyWaitNotify实例上调用doWait()的线程会被在第二个MyWaitNotify实例上调用doNotify()的线程唤醒。 



 
  
 死锁
简单的死锁
Thread 1 locks A, waits for B
Thread 2 locks B, waits for A
 
 更复杂的死锁
Thread 1 locks A, waits for B
Thread 2 locks B, waits for C
Thread 3 locks C, waits for D
Thread 4 locks D, waits for A
 
 数据库的死锁
Transaction 1, request 1, locks record 1 for update
Transaction 2, request 1, locks record 2 for update
Transaction 1, request 2, tries to lock record 2 for update.
Transaction 2, request 2, tries to lock record 1 for update.
 
 避免死锁
加锁顺序

加锁时限

死锁检测

加锁顺序(按照相同的顺序获得锁)
Thread 1

lock A

lock B

Thread 2

wait for A

lock C (when A locked)

Thread 3

wait for A

wait for B

wait for C

加锁时限
Thread 1 locks A

Thread 2 locks B

Thread 1 attempts to lock B but is blocked

Thread 2 attempts to lock A but is blocked

Thread 1's lock attempt on B times out

Thread 1 backs up and releases A as well

Thread 1 waits randomly (e.g. 257 millis) before retrying.

Thread 2's lock attempt on A times out

Thread 2 backs up and releases B as well

Thread 2 waits randomly (e.g. 43 millis) before retrying.

如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。如果只有两个线程,并且重试的超时时间设定为0到500毫秒之间,这种现象可能不会发生,但是如果是10个或20个线程情况就不同了。因为这些线程等待相等的重试时间的概率就高的多(或者非常接近以至于会出现问题)

死锁检测
每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。

当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。

下面是一幅关于四个线程(A,B,C和D)之间锁占有和请求的关系图。像这样的数据结构就可以被用来检测死锁。



当检测出死锁时:

一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁

一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级

 
 
饥饿和公平

1、Java中导致饥饿的原因:

高优先级线程吞噬所有低优先级线程的CPU时间(线程优先级值设置在1到10之间,而这些优先级值所表示行为的准确解释则依赖于你的应用运行平台。对大多数应用来说,你最好是不要改变其优先级值)

线程被永久堵塞在一个等待进入同步块的状态(运气太差,总是得不到运行机会)

线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法,运气太差,总是没有被唤醒)
 
 
2、在Java中实现公平性方案,需要:

使用锁,而不是同步块

公平锁

注意性能方面
公平锁代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public
class FairLock {

    private
boolean isLocked = false;

    private Thread lockingThread = null;

    private List<QueueObject> waitingThreads =

            new ArrayList<QueueObject>();

 
 
    public
void lock() throws InterruptedException{

        QueueObject queueObject           = new QueueObject();

        boolean     isLockedForThisThread = true;

        synchronized(this){

            waitingThreads.add(queueObject);

        }

 
 
        while(isLockedForThisThread){

          synchronized(this){

            isLockedForThisThread =

                isLocked || waitingThreads.get(0) != queueObject;

            if(!isLockedForThisThread){

              isLocked = true;

               waitingThreads.remove(queueObject);

               lockingThread = Thread.currentThread();

               return;

             }

          }

          try{

            queueObject.doWait();

          }catch(InterruptedException e){

            synchronized(this) { waitingThreads.remove(queueObject); }

            throw e;

          }

        }

    }

 
 
    public
synchronized
void unlock(){

      if(this.lockingThread != Thread.currentThread()){

        throw
new IllegalMonitorStateException(

          "Calling thread has not locked this lock");

      }

      isLocked      = false;

      lockingThread = null;

      if(waitingThreads.size() > 0){

        waitingThreads.get(0).doNotify();

      }

    }

}

 
 
 
 
/*****************************************************/

public
class QueueObject {

 
 
    private
boolean isNotified = false;

 
 
    public
synchronized
void doWait() throws InterruptedException {

 
 
        while(!isNotified){

            this.wait();

        }

 
 
        this.isNotified = false;

 
 
    }

 
 
    public
synchronized
void doNotify() {

        this.isNotified = true;

        this.notify();

    }

 
 
    public
boolean equals(Object o) {

        return
this == o;

    }

}
FairLock新创建了一个QueueObject的实例,并对每个调用lock()的线程进行入队列。调用unlock()的线程将从队列头部获取QueueObject,并对其调用doNotify(),以唤醒在该对象上等待的线程。通过这种方式,在同一时间仅有一个等待线程获得唤醒,而不是所有的等待线程。这也是实现FairLock公平性的核心所在。
还需注意到,QueueObject实际是一个semaphore。doWait()和doNotify()方法在QueueObject中保存着信号。这样做以避免一个线程在调用queueObject.doWait()之前被另一个调用unlock()并随之调用queueObject.doNotify()的线程重入,从而导致信号丢失。queueObject.doWait()调用放置在synchronized(this)块之外,以避免被monitor嵌套锁死,所以另外的线程可以解锁,只要当没有线程在lock方法的synchronized(this)块中执行即可。
最后,注意到queueObject.doWait()在try – catch块中是怎样调用的。在InterruptedException抛出的情况下,线程得以离开lock(),并需让它从队列中移除。
 
  
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: