• 启动多线程的两种情况比较


    启动多线程有两种方式:(都是在主线程main线程下)

    1. 使用同一个线程对象来启多个线程

    2. 使用多个线程对象来启多个线程

    这两种方式有什么区别呢?先贴上代码举例说明:

    这是使用线程对象MyRunnable的同一个实例r来启动了两个线程

    MyRunnable r = new MyRunnable();
    Thread ta = new Thread(r,"Thread-A"); 
    Thread tb = new Thread(r,"Thread-B"); 
    ta.start(); 
    tb.start();     

    这是使用线程对象MyRunnable的两个不同的实例r来启动了两个线程

    MyRunnable r1 = new MyRunnable();
    MyRunnable r2 = new MyRunnable();
            
    Thread ta = new Thread(r1,"Thread-A"); 
    Thread tb = new Thread(r2,"Thread-B"); 
            
    ta.start(); 
    tb.start();

    那么使用这两种方式的区别在哪里呢?我们紧接着看下面的代码的运行结果:

    public class MyRunnable implements Runnable {
        private Foo foo =new Foo(); 
    
        public static void main(String[] args) {
            MyRunnable r = new MyRunnable();
            Thread ta = new Thread(r,"Thread-A"); 
            Thread tb = new Thread(r,"Thread-B"); 
            ta.start(); 
            tb.start(); 
            
            /*
            MyRunnable r1 = new MyRunnable();
            MyRunnable r2 = new MyRunnable();
            
            Thread ta = new Thread(r1,"Thread-A"); 
            Thread tb = new Thread(r2,"Thread-B"); 
            
            ta.start(); 
            tb.start();
            */
        } 
    
        public void run() {
                for (int i = 0; i < 3; i++) {
                    this.fix(30);
                    try {
                        Thread.sleep(1); 
                    } catch (InterruptedException e) {
                        e.printStackTrace(); 
                    } 
                    System.out.println(Thread.currentThread().getName() + " :当前foo对象的x值= " + foo.getX());
                } 
        } 
    
        public int fix(int y) {
            return foo.fix(y);
        } 
    }
    
    
    class Foo {
        private int x = 100;
    
        public int getX() {
            return x;
        } 
    
        public int fix(int y) {
            x = x - y; 
            return x;
        } 
    }

    使用同一个线程对象启多个线程的运行结果:

    Thread-B :当前foo对象的x值= 40
    Thread-B :当前foo对象的x值= 10
    Thread-A :当前foo对象的x值= -20
    Thread-B :当前foo对象的x值= -50
    Thread-A :当前foo对象的x值= -50
    Thread-A :当前foo对象的x值= -80

    使用多个线程对象启动多个线程的运行结果:

    Thread-A :当前foo对象的x值= 70
    Thread-B :当前foo对象的x值= 70
    Thread-B :当前foo对象的x值= 40
    Thread-A :当前foo对象的x值= 40
    Thread-A :当前foo对象的x值= 10
    Thread-B :当前foo对象的x值= 10

    我们可以看到在改变值的过程中,值串了。并且线程执行也是串的,两个线程之间在相互争抢执行。
    (线程ta的run方法还没有执行完,tb的run方法争抢到了cpu资源从而执行)

    在改变值的过程中,值改变是对的。线程执行是串的。(值没有串,是因为foo是私有变量,属于ta,tb所各自私有)

    是我们不能允许的,因为值串了。
    都出现的线程之间相互争抢的问题,就看我们的业务实现了。
    使用多线程时,我们有时就是想启用多个线程同时去干不同的事情,这时它们相互争抢执行就是我们想要的。
    有时,在多个线程同时访问一个方法时,我们希望当一个线程执行完这个方法后,再让其他的线程去执行,这时,我们就要避免线程之间相互争抢的问题,也就是使用同步锁机制来控制。

    好,如果我们现在想要run()方法执行完了之后,其他线程才能再次进入run()方法来执行。我们用同步关键字synchronized来实现。如下:

    同步方法:

    synchronized public void run() {
            for (int i = 0; i < 3; i++) {
                this.fix(30);
                try {
                    Thread.sleep(1); 
                } catch (InterruptedException e) {
                    e.printStackTrace(); 
                } 
                System.out.println(Thread.currentThread().getName() + " :当前foo对象的x值= " + foo.getX());
            } 
        } 

    同步块:

    public void run() {
            synchronized(this){
                for (int i = 0; i < 3; i++) {
                    this.fix(30);
                    try {
                        Thread.sleep(1); 
                    } catch (InterruptedException e) {
                        e.printStackTrace(); 
                    } 
                    System.out.println(Thread.currentThread().getName() + " :当前foo对象的x值= " + foo.getX());
                } 
            }
        } 

    对于我们使用上面的同步方法和同步块都能得到如下的输出:

    Thread-A :当前foo对象的x值= 70
    Thread-A :当前foo对象的x值= 40
    Thread-A :当前foo对象的x值= 10
    Thread-B :当前foo对象的x值= -20
    Thread-B :当前foo对象的x值= -50
    Thread-B :当前foo对象的x值= -80

    对于我们使用上面的同步方法和同步块却得到如下的输出:

    Thread-A :当前foo对象的x值= 70
    Thread-B :当前foo对象的x值= 70
    Thread-A :当前foo对象的x值= 40
    Thread-B :当前foo对象的x值= 40
    Thread-A :当前foo对象的x值= 10
    Thread-B :当前foo对象的x值= 10

    的结果与我们预期的是一样的,但是却不如我们的预期,各个线程之间还是在相互争抢执行。

    为什么呢?我们不是都已经使用synchronized同步了吗?

    导致这个问题的根源就是对象锁的问题。

    中使用同步方法时,线程ta,tb对应的对象锁都为MyRunnable的实例对象r,对象锁共享且唯一,所以起到了同步的作用。
    同理,使用同步块时,ta,tb的对象锁也都是MyRunnable的实例对象r,故也能达到效果。

    但对于不同的是,使用方法同步和块同步时,线程ta,tb对应的对象锁分别是各自的线程对象的实例,即ta-->r1,tb-->r2。故线程ta,tb分别持有各自的对象锁,所以达不到同步的效果。

    如果换成如下代码执行

    public void run() {
            synchronized("123"){
                for (int i = 0; i < 3; i++) {
                    this.fix(30);
                    try {
                        Thread.sleep(1); 
                    } catch (InterruptedException e) {
                        e.printStackTrace(); 
                    } 
                    System.out.println(Thread.currentThread().getName() + " :当前foo对象的x值= " + foo.getX());
                } 
            }
        } 

    我们得到如下结果:

    Thread-A :当前foo对象的x值= 70
    Thread-A :当前foo对象的x值= 40
    Thread-A :当前foo对象的x值= 10
    Thread-B :当前foo对象的x值= 70
    Thread-B :当前foo对象的x值= 40
    Thread-B :当前foo对象的x值= 10

    这下就和我们的预期一样了。ta,tb线程都持有字符串"123"作为对象锁,ta,tb线程中的"123"都指向相同的内存地址,故对象锁相同且共享,故能达到同步效果。(为什么ta,tb中的"123"指向相同的内存地址,与String对象本身比较特殊有关,在此不赘述)

    对于文章中的对象锁问题有疑问的,可以参见另一篇博文:http://www.cnblogs.com/kevin-yuan/archive/2013/04/27/3047511.html

  • 相关阅读:
    674. Longest Continuous Increasing Subsequence
    989. Add to Array-Form of Integer
    1018. Binary Prefix Divisible By 5
    53. Maximum Subarray
    1010. Pairs of Songs With Total Durations Divisible by 60
    27. Remove Element
    1089. Duplicate Zeros
    119. Pascal's Triangle II
    830. Positions of Large Groups
    hdu5969最大的位或
  • 原文地址:https://www.cnblogs.com/kevin-yuan/p/4111242.html
Copyright © 2020-2023  润新知