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