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

启动多线程有两种方式:(都是在主线程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

时间: 2024-12-20 10:56:39

启动多线程的两种情况比较的相关文章

Java实现多线程的两种方式

实现多线程的两种方式: 方式1: 继承Thread类 A: 自定义MyThread类继承Thread类 B: 在MyThread类中重写run() C: 创建MyThread类的对象 D: 启动线程对象. 问题: a. 为什么要重写run方法? run()方法里封装的是被线程执行的代码 b. 启动线程对象用的是哪个方法? start()方法 c. run()和start()方法的区别? 直接调用run方法只是普通的方法调用 调用start方法先会启动线程,再由jvm调用run()方法 方式2:

【Java多线程】两种基本实现框架

Java多线程学习1——两种基本实现框架 一.前言 当一个Java程序启动的时候,一个线程就立刻启动,改程序通常也被我们称作程序的主线程.其他所有的子线程都是由主线程产生的.主线程是程序开始就执行的,并且程序最终是以主线程的结束而结束的. Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的.每用Java命令启动一个Java应用程序,就会启动一个JVM进程.在同一个JVM进程中,有且只有一个进程,就是它自己.在这个JVM环境中,所有程序代码的运行都

实现多线程的两种方法:继承Thread类或实现Runnable接口

实现多线程的两种方法:继承Thread类或实现Runnable接口 Java中实现多线程有两种方法:继承Thread类和实现Runnable接口,在程序开发中只要是多线程,我们一般都是实现Runnable接口,原因归结为一点:实现接口比继承类要好. 多线程的第一种实现方式:继承Thread类 步骤如下 创建一个继承Thread的类(假定为A),并重写Thread的run方法 构造一个A类对象,假定为aa 调用aa的start方法.(start方法是从Thread继承过来的) 具体例子如下 pac

Java多线程的两种实现方式:继承Thread类 &amp; 实现Runable接口

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! 创建和启动线程的两种传统方式: Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象.每个Thread对象描述了一个单独的线程.要产生一个线程,有两种方法: ◆需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法: ◆实现Runnalbe接口,重载Runnalbe接口中的run()方法.

java 实现多线程的两种方式

一.问题引入 说到这两个方法就不得不说多线程,说到多线程就不得不提实现多线程的两种方式继承Thread类和实现Runable接口,下面先看这两种方式的区别. 二. Java中实现多线程的两种方式 1.  继承Thread类 /** * 使用Thread类模拟4个售票窗口共同卖100张火车票的程序,实际上是各卖100张 */ public class ThreadTest { public static void main(String[] args){ new MyThread().start(

用sql取出来的list需要处理成map的两种情况

1. 原生sql: select a.id,a.name from a SQLQuery sqlQuery=this.getSession().createSQLQuery(sb.toString()); List list = sqlQuery.list(); 在action处理成map: 2.hql: select new map(a.id as id,a.name as name) from a this.getHibernateTemplate().find(sb.toString())

Nginx访问PHP文件的File not found错误处理,两种情况

Nginx访问PHP文件的File not found错误处理,两种情况 这个错误很常见,原有有下面两种几种 1. php-fpm找不到SCRIPT_FILENAME里执行的php文件 2. php-fpm不能访问所执行的php,也就是权限问题 第一种情况 可以在你的location php 里面添加当文件不存在时返回404而不是交给php-fpm进行处理 location ~ \.php${ ... #文件不存在转404 try_files $uri = 404; ...} 然后,在你的配置文

在两种情况下设备与驱动会发生匹配

在两种情况下设备与驱动会发生匹配:(基于linux内核3.0) 当设备插入系统时,设备挂接到总线上,与总线上的所有驱动进行匹配(bus_type.match进行匹配), 如果匹配成功,则调用bus_type.probe或者driver.probe初始化该设备,挂接到总线上,如果匹配失败,则只是将该设备挂接到总线上. 当驱动注册到系统时,驱动挂接到总线上,与总线上的所有设备进行匹配(用bus_type.match进行匹配),如果匹配成功,则调用bus_type.probe或者driver.prob

java项目打jar包的两种情况

链接地址:http://jingyan.baidu.com/article/6b97984d8a6ddc1ca2b0bfa0.html 本文介绍一下java项目打jar包时的两种情况各怎么操作 方法/步骤 一.java项目没有导入第三方jar包 这时候打包就比较简单: 1. 首先在Eclipse中打开项目, 右键点击项目,选择“Export”:2. 选择Java/JAR file,Next:3. Select the resources to export中可以选择你想要包含的项目文件夹,一些不