java多线程-初探(三)

java多线程-初探(二)

本章主要阐述synchronized同步关键字,以及未做同步将出现的问题。

未做同步引发的问题举例

本文举例:1万个人一起吃1万碗饭,两个人随便吃。吃完为止。

正常想要的结果是:吃到第0碗的时候,程序执行结束,打印出剩余的饭为0碗。

未加同步可能出现的结果是:吃到第0碗的时候,有一个人以为还有饭可以吃,接着吃。结果就把剩余的饭吃出负数来了。

未加同步的问题不是每次执行都能出现的,这个要看cpu的资源执行效果,如果都是很和谐的状态下则不会出现,建议多执行几次。

class Main

package com.thread.three;

public class Main {

    // 一万碗饭
    public static int riceNum = 10000 ;

    /**
     * 吃饭
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("开吃!");
        // 一万个线程同时吃饭
        int peopleNum = 10000 ;
        Thread[] threads = new Thread[peopleNum];
        for (int i=0; i<peopleNum; i++){
            Thread thread = new Thread(){
                public void run(){
                    while (true){
                        if (Main.riceNum <= 0) break;
                        System.out.println("吃了一碗,剩余:" + Main.riceNum--);
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            thread.start();
            threads[i] = thread;
        }
        // 加入主线程
        for (Thread thread : threads){
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("吃完啦,剩余:" + riceNum + "碗饭");
    }
}

执行结果

问题原因

上述试了几遍出现的同步问题,说到底就是有一个线程以为还有饭可以吃,吃的时候却已经没了。

关键性问题代码:

if (Main.riceNum <= 0) break;
System.out.println("吃了一碗,剩余:" + Main.riceNum--);

举例说明

此时饭剩下最后1碗的时候,线程1在第一行判断通过,还未执行第二行减少饭的代码逻辑时,cpu切换到线程2,此时线程2以为饭还有1碗,线程2的判断也通过,当线程2要执行第二行时cpu又切换到线程3。

以此类推,假设有3个线程在第二行等待,线程1执行了第二行代码,现在饭变成0碗了,输出剩余0碗饭。cpu又切换到线程2,此时输出剩余-1碗饭,cpu再次切换到线程3,则输出剩余-2碗饭。

当cpu切换到某一个线程时,从上次切换走的那行继续执行,所以切换到线程2跟线程3时不需要判断饭是不是小于0碗了,因为cpu切走之前已经判断通过了。

解决思路

在线程1执行判断以及减少饭的操作时,其他线程等线程1吃完再吃呢?

我们要把判断是否还有饭以及吃饭的整个过程,不允许其他线程在我们吃的时候也来一起吃。

synchronized关键字

synchronized之内的代码块可以只让一个线程执行,执行完了才能轮到其他线程进入这个代码块,否则其他线程就在synchronized处等待。

synchronized可修饰方法或者方法内的代码块。

修饰方法的锁则是调用该方法的对象,如果同时new多个实例,则不是同一把锁。

synchronized的锁

实现其他线程等待,归根需要一把锁。这个锁建议要是一个唯一不可变的实例对象。

当线程1执行到synchronized时,获取了synchronized的锁,当线程1在执行代码块的时,cpu切换到其他线程,其他线程要获取锁的时候,发现锁已经被线程1占用了,无法获取,其他线程则在synchronized代码块之外等待线程1把锁释放了,才能获取锁从而执行。

大白话解释就是:当一个人拿了一把钥匙开门进房间了,其他人要进来时没有钥匙,只能等里面的人出来把钥匙给其他人,其他人才能进去。

Java中的同步有三种:

  1. 同步代码块。锁是由开发者自己定义
  2. 非静态方法上加同步。锁对象是当前调用方法的那个对象使用this 表示
  3. 静态方法上加同步。锁对象是当前方法所在的class文件,使用类名.class 表示

同步方法的执行:

当任何的线程要进入到当前这个方法时,就必须先判断能不能获取到锁,如果可以获取到,才能进入方法运行,获取不到,只能在方法外面等待。

synchronized代码格式

synchronized( 任意对象(锁) ){

      需要被同步的代码

}

释放锁

上述为例,线程1释放锁有两种方式。

1:synchronized代码块内执行完成。

2:synchronized代码块内发生异常且代码块内无捕获。

添加synchronized锁的关键性代码示例

while (true){
  synchronized (Main.class){
     if (Main.riceNum <= 0) break;
     System.out.println("吃了一碗,剩余:" + Main.riceNum--);
     try {
          Thread.sleep(100);
     } catch (InterruptedException e) {
           e.printStackTrace();
     }
  }
}

上述的锁就是:Main.class

这个class对象在类加载时产生,唯一且不可变。

错误示例

while (true){
  synchronized (new Object()){
     if (Main.riceNum <= 0) break;
     System.out.println("吃了一碗,剩余:" + Main.riceNum--);
     try {
          Thread.sleep(100);
     } catch (InterruptedException e) {
           e.printStackTrace();
     }
  }
}

上文的锁是一个Object对象,造成的结果就是锁直接在门上插着,谁都能进。

没new一个对象都不一样,两个线程的锁不是同一个,则无法实现同步。

线程安全的类

线程安全:效率低。

线程不安全:效率高。

在一个类里,如果所有的方法都被synchronized修饰,则称这个类为线程安全的类。

能保证同一时间内,有且只有一个线程可以进入这个类里修改操作数据。从而保证数据的安全性,避免因为同步而导致出现的脏数据(如上文的剩余-1碗饭)

举例说明:StringBuffer和StringBuilder

StringBuffer:线程安全的类

StringBuilder:线程不安全的类

StringBuffer的方法中,所有方法都被synchronized修饰。因为无法多个线程一起操作,所以效率相对StringBuffer比较低。

举例说明:HashMap和Hashtable

Hashtable:线程安全的类,效率低

HashMap:线程不安全的类,效率高

举例说明:ArrayList和Vector

Vector:线程安全的类,效率低

ArrayList:线程不安全的类,效率高

说明

线程安全的类,表示不可以多个线程同时访问一个线程安全的对象。

如上文的集合,不安全的类,则可以两个线程同时添加数据。安全的类则必须等第一个线程添加完成后第二个线程才能添加数据。(前提是两个线程操作同一个集合对象)

注:线程不安全的集合也可以通过Collections工具类转换成线程安全的。

如:List<Integer> list = Collections.synchronizedList(new ArrayList());

java多线程-初探(四)

猜你喜欢

转载自blog.csdn.net/wkh___/article/details/84818655