1.1 继承Thread类
public class MyThread extends Thread { private String name; public MyThread() { } public MyThread(String pName) { this.name = pName; } @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("线程" + name + "正在运行:" + i); } } public static void main(String[] args) { MyThread myThreadA = new MyThread("A"); MyThread myThreadB = new MyThread("B"); myThreadA.start(); myThreadB.start(); } }运行结果:
第1次运行: 线程B正在运行:0 线程B正在运行:1 线程B正在运行:2 线程B正在运行:3 线程B正在运行:4 线程A正在运行:0 线程A正在运行:1 线程A正在运行:2 线程A正在运行:3 线程A正在运行:4 第2次运行: 线程A正在运行:0 线程A正在运行:1 线程A正在运行:2 线程A正在运行:3 线程B正在运行:0 线程A正在运行:4 线程B正在运行:1 线程B正在运行:2 线程B正在运行:3 线程B正在运行:4 第3次运行: 线程B正在运行:0 线程A正在运行:0 线程A正在运行:1 线程B正在运行:1 线程A正在运行:2 线程A正在运行:3 线程A正在运行:4 线程B正在运行:2 线程B正在运行:3
因为要用到CPU资源,所以每次的运行结果都不一样。
1.2 实现Runnable接口
public class MyRunnable implements Runnable { private String name; public MyRunnable() { } public MyRunnable(String pName) { this.name = pName; } @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("线程" + name + "正在运行:" + i); } } public static void main(String[] args) { Thread myThreadA = new Thread(new MyRunnable("A")); Thread myThreadB = new Thread(new MyRunnable("B")); myThreadA.start(); myThreadB.start(); } }
运行结果同上一个例子一样。
补充:
1.在Java中,程序运行至少启动两个线程:main线程和垃圾收集线程。
2.主线程也有可能在子线程结束之前就已经结束。
线程总是存在优先级,优先级范围是1~10。JVM对线程的调度是基于优先级的抢先调度机制。在通常情况下,当前运行的线程的优先级大于或等于其他线程,但是这不是绝对的。
所以,在多线程编程的时候,不要依赖与优先级,因为优先级是没有保障的。
让步使用yield()方法。让步并不是一定就能够让其他线程执行,让步仅仅是让当前线程从新回到就绪状态,这时候线程调度器开始重新调度,也许线程调度器又选择了这个线程,也许选择了其他线程,这个是随机的。
public class YieldingThread extends Thread { private int countDown = 5; @Override public void run() { System.out.println("#" + Thread.currentThread().getName() + " start"); super.run(); while (true) { System.out.println("#" + Thread.currentThread().getName() + ": " + countDown); if (--countDown == 0) { return; } yield(); } } public static void main(String[] args) { System.out.println("#" + Thread.currentThread().getName() + " start"); for (int i = 0; i < 5; i++) { new YieldingThread().start(); } } }
可以看到运行结果并不是平均分配的,而是随机的。所以,yield()使用的机会并不多,如果相对程序作认真调整的会,就不能依赖于yield()。
休眠使用sleep()方法。sleep()的这段时间叫作该线程被挂起了。
public class SleepingThread extends Thread { private int countDown = 5; @Override public void run() { System.out.println("#" + Thread.currentThread().getName() + " start"); super.run(); while (true) { System.out.println("#" + Thread.currentThread().getName() + ": " + countDown); if (--countDown == 0) { return; } try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { System.out.println("#" + Thread.currentThread().getName() + " start"); for (int i = 0; i < 5; i++) { SleepingThread sleepingThread = new SleepingThread(); try { sleepingThread.start(); sleepingThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
可以发现,线程隔1秒打印一次,而且,下一个线程必须等上一个线程执行完毕才能开始,这是因为使用了join()方法,下面会讲到。
加入到某个线程使用join()方法。在Thread A中使用了Thread B的join()方法,其效果相当于Thread A必须等待Thread B执行完成才能继续执行,等待的这段时间叫作Thread A被挂起了(即Thread A的isAlive()为false)。另外,可以为join()方法设置参数(单位可以是毫秒或者毫秒加纳秒),如果在指定时间内Thread B还没有执行完,也将继续执行Thread A。
例子见上面一个例子。
线程的等待使用wait()方法。当一个线程使用了wait()方法,这个线程就被挂起,并释放锁。当线程再次收到notify()或者notifyAll()的时候,重新获得锁,继续执行。另外,可以为wait()使用参数(单位毫秒),如果这指定时间内没有收到notify()或者notifyAll(),那么也将获得锁继续执行。
看一个例子:一个餐馆,有一个厨师和一个服务员。服务员等待厨师的食物。当厨师准备好一份食物时,通知服务员。这时生产者 - 消费者的关系。
/** * 订单类(一个简单的能自己计数的类) */ class Order { private static int i = 0; private int count = i++; public Order() { // 当订单数累积到10,退出程序 if (count == 10) { System.out.println("Out of food, closing"); System.exit(0); } } public String toString() { return "Order " + count; } } /** * 服务员类 */ class WaitPerson extends Thread { private Restaurant restaurant; public WaitPerson(Restaurant r) { restaurant = r; } @Override public void run() { while (true) { while (restaurant.order == null) synchronized (this) { try { // 获得锁(本类的实例对象),然后挂起,又释放锁,重新等待该锁 wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("Waitperson got " + restaurant.order); restaurant.order = null; } } } /** * 厨师类 */ class Chef extends Thread { private Restaurant restaurant; private WaitPerson waitPerson; public Chef(Restaurant r, WaitPerson w) { restaurant = r; waitPerson = w; } @Override public void run() { while (true) { if (restaurant.order == null) { restaurant.order = new Order(); System.out.print("Order up! "); // 因为wait()会释放锁,所以这边能够获得锁。 synchronized (waitPerson) { // 唤醒等待该锁的线程(这边只有一个) waitPerson.notify(); } } try { sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } /** * 餐馆类 */ public class Restaurant { Order order; public static void main(String[] args) { Restaurant restaurant = new Restaurant(); WaitPerson waitPerson = new WaitPerson(restaurant); waitPerson.start(); Chef chef = new Chef(restaurant, waitPerson); chef.start(); } }
问题1:为什么使用wait和notify一定要在同步方法或同步代码块中。答:有wait,notify的地方必有synchronized。这是因为wait和notify不是属于线程类,它们是Object这个基类的方法。而且,这两个方法都和对象锁有关,有锁的地方,必有synchronized。如果不加synchronized,会报异常。
问题2:为什么使用wait一定要用while,不能用if
答:如果用if,当被notify的时候,不管条件满不满足,都继续执行if条件后面的代码。如果使用while,当被notify的时候,会再判断一次条件,如果不满足条件,再次进入wait。
interrupt不能中断正在运行的线程,只能中断被阻塞的线程,线程阻塞有三种情况:sleep()、join()、wait()。
threadObj.interrupt 方法并不直接中断线程或者抛出InterruptedException,而是设置 interrupted 标志位。Object.wait, Thread.sleep方法,会不断的轮询监听 interrupted 标志位,发现其设置为true后,会停止阻塞并抛出 InterruptedException异常。
所谓“后台”(daemon)线程,是指程序运行的时候,在后台提供一种通用服务的线程,并且这种服务并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束,程序也就终止了。反过来说,只要有任何非后台线程还在运行,程序就不会终止。比如,执行main( )的就是一个非后台线程。
看一个例子:
public class SimpleDaemons extends Thread { @Override public void run() { super.run(); while (true) { try { sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(this); } } public static void main(String[] args) { for (int i = 0; i < 10; i++) { SimpleDaemons simpleDaemons = new SimpleDaemons(); simpleDaemons.setDaemon(true); simpleDaemons.start(); } // try { // Thread.sleep(3000); // } catch (InterruptedException e) { // e.printStackTrace(); // } } }
程序什么也没有打印。因为程序只有一个非后台线程,即main。main很快就执行完了,程序很快就结束了。
如果是一个后台线程,那么它创建的任何线程将被自动设置成后台线程,如下例所示:
public class SimpleDaemons extends Thread { @Override public void run() { super.run(); while (true) { try { sleep(100); SimpleThread simpleThread = new SimpleThread(); simpleThread.start(); System.out.println("simpleThread.isDaemon:" + simpleThread.isDaemon()); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(this); } } public static void main(String[] args) { for (int i = 0; i < 10; i++) { SimpleDaemons simpleDaemons = new SimpleDaemons(); simpleDaemons.setDaemon(true); simpleDaemons.start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } class SimpleThread extends Thread { @Override public void run() { super.run(); } }
package com.tianjf; public class Test { // 零长度的byte数组对象创建起来将比任何对象都经济 // 查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码 private byte[] lock = new byte[0]; /** * 普通的非同步方法 */ public void methodA() { System.out.println("methodA"); } /** * 1.同步方法,获得本对象的锁。和methodC的效果一样 */ public synchronized void methodB() { System.out.println("methodB"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 2.同步代码块,获得本对象的锁。和methodB的效果一样 */ public void methodC() { synchronized (this) { System.out.println("methodC"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 3.同步代码块,获得lock这个对象的锁 */ public void methodD() { synchronized (lock) { System.out.println("methodD"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Test test1 = new Test(); Test test2 = new Test(); // // 1.第一个线程进入methodB方法,获得test1的锁。 // // 2.第二个线程试图进入methodB方法,但是它要获得test1的锁才能进入methodB方法,所以第二个线程只能等待test1的锁 // // 3.第一个线程运行结束,释放test1的锁 // // 4.第二个线程获得test1的锁,进入methodB方法 // new Thread(new MyRunnableB(test1)).start(); // new Thread(new MyRunnableB(test1)).start(); // // 和上面效果一样。因为methodB和methodC是一样的 // new Thread(new MyRunnableB(test1)).start(); // new Thread(new MyRunnableC(test1)).start(); // // 两个线程不冲突。因为methodA不是同步方法,不需要获得锁才执行 // new Thread(new MyRunnableB(test1)).start(); // new Thread(new MyRunnableA(test1)).start(); // // 两个线程不冲突。因为methodB获取的是本对象的锁,而methodD获取的是lock这个对象的锁 // new Thread(new MyRunnableB(test1)).start(); // new Thread(new MyRunnableD(test1)).start(); // 两个线程不冲突。因为锁是两个不同的对象 new Thread(new MyRunnableB(test1)).start(); new Thread(new MyRunnableB(test2)).start(); } } /** * 用来调用methodA的线程 */ class MyRunnableA implements Runnable { private Test test; public MyRunnableA(Test pTest) { this.test = pTest; } @Override public void run() { test.methodA(); } } /** * 用来调用methodB的线程 */ class MyRunnableB implements Runnable { private Test test; public MyRunnableB(Test pTest) { this.test = pTest; } @Override public void run() { test.methodB(); } } /** * 用来调用methodC的线程 */ class MyRunnableC implements Runnable { private Test test; public MyRunnableC(Test pTest) { this.test = pTest; } @Override public void run() { test.methodC(); } } /** * 用来调用methodD的线程 */ class MyRunnableD implements Runnable { private Test test; public MyRunnableD(Test pTest) { this.test = pTest; } @Override public void run() { test.methodD(); } }
上面的例子是锁对象,同一个对象能实现同步。那么怎么让所有对象实现同步呢?那就要用到类锁。看下面的例子:
package com.tianjf; public class Test { public void methodA() { System.out.println("methodA"); } public static synchronized void methodB() { System.out.println("methodB"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } public void methodC() { synchronized (Test.class) { System.out.println("methodC"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Test test1 = new Test(); Test test2 = new Test(); // new Thread(new MyRunnableB()).start(); // new Thread(new MyRunnableB()).start(); new Thread(new MyRunnableC(test1)).start(); new Thread(new MyRunnableC(test2)).start(); } } class MyRunnableA implements Runnable { private Test test; public MyRunnableA(Test pTest) { this.test = pTest; } @Override public void run() { test.methodA(); } } class MyRunnableB implements Runnable { @Override public void run() { Test.methodB(); } } class MyRunnableC implements Runnable { private Test test; public MyRunnableC(Test pTest) { this.test = pTest; } @Override public void run() { test.methodC(); } }
通过static方法和.class可以实现类锁。
在将原子操作之前,先看两个例子:
例子1:
package com.tianjf; /** * EvenGenerator类,它的next()方法用来生成偶数 */ class EvenGenerator { private int currentValue = 0; private boolean cancled = false; public int next() { ++currentValue; // 危险! ++currentValue; return currentValue; } public boolean isCancled() { return cancled; } public void cancle() { cancled = true; } } /** * EvenChecker类,用来不断地检验EvenGenerator的next()方法产生的是不是一个偶数,它实现了Runnable接口 */ class EvenChecker implements Runnable { private EvenGenerator generator; public EvenChecker(EvenGenerator generator) { this.generator = generator; } @Override public void run() { int nextValue; while (!generator.isCancled()) { nextValue = generator.next(); if (nextValue % 2 != 0) { System.out.println(nextValue + "不是一个偶数!"); generator.cancle(); } } } } /** * 创建两个EvenChecker来并发地对同一个EvenGenerator对象产生的数字进行检验 */ public class Test { public static void main(String[] args) { EvenGenerator generator = new EvenGenerator(); Thread t1 = new Thread(new EvenChecker(generator)); Thread t2 = new Thread(new EvenChecker(generator)); t1.start(); t2.start(); } }
运行结果:1753不是一个偶数! 1751不是一个偶数!
明明next()方法++currentValue了两次,为什么还有不是偶数的情况呢?问题出来有“危险”注释的那一行。因为很可能某个线程在执行完这一行只进行了一次递增之后,CPU时间片被另外一个线程夺去,于是就生产出了奇数。解决的办法,就是给EvenGenerator的next()方法加上 synchronized关键字
例子2:
因为日本地震海啸以及核爆炸的缘故,有人造谣说,咱国内已经受到了核污染,吃含碘的东西能够减轻核辐射带来的影响。于是就有投机的人在淘宝上开了一家网店,专卖碘片,一块钱一片。生意十分火爆。有很多个买家不断地在买碘片,一直到把钱给用光。买家买碘片的这些钱都打到了卖家的同一个银行账号里。所以,结果就是,买家所有的钱最后都到了卖家的银行账户里,卖家银行账号里的总额就是所有买家在买碘片之前的现金总计。
package com.tianjf; /** * 银行账户类 */ class BankAccount { private int total = 0; public void add(int n) { total += n; } public int getTotal() { return total; } } /** * 买家类 */ class Customer implements Runnable { private BankAccount account; private int cash; public Customer(int cash, BankAccount account) { this.cash = cash; this.account = account; } public void cost(int n) { cash -= n; account.add(n); } @Override public void run() { while (cash > 0) { // 直至将钱用光 cost(1); } System.out.println("total: " + account.getTotal()); // 打印出银行账户的总计金额 } } /** * 测试类 */ public class Test { public static void main(String[] args) { BankAccount account = new BankAccount(); for (int i = 0; i < 100; i++) { new Thread(new Customer(100000, account)).start(); } } }
运行结果:(前面省略N行) total: 7841199 total: 7744832 total: 7569631 total: 7641199
卖家理应得到100*100000 = 10000000,但是为什么无故少了这么多钱呢?问题出在哪儿呢?BankAccount的add()方法不是只有一句话么,难道这一句话也能被打断?回答就是:这一句话确实能够被打断,因为这样的操作不具有 原子性(atomicity)。解决办法同样是用synchronized控制
好了下面进入正文:原子性
具有原子性的操作被称为原子操作。原子操作在操作完毕之前不会线程调度器中断。
Java中的原子操作是:除了long和double之外的基本类型的简单操作都具有原子性。简单操作就是赋值操作或return操作。
例如:
a = 1;和return a; 具有原子性
a += b;不具有原子性
JVM中a += b:操作不是原子的,要经过三个步骤:
- 取出a和b
- 计算a+b
- 将计算结果写入内存
如果有两个线程t1,t2在进行这样的操作。t1在第二步做完之后还没来得及把数据写回内存就被线程调度器中断了,于是t2开始执行,t2执行完毕后t1又把没有完成的第三步做完。这个时候就出现了错误,相当于t2的计算结果被无视掉了。所以上面的买碘片例子在同步add方法之前,实际结果总是小于预期结果的,因为很多操作都被无视掉了。
Java SE引入了原子类,比如AtomicInterger,AtomicLong等等。
上面提到了,对于简单操作,long和double也不具备原子性。但是,一旦给这两个类型的属性加上volatile修饰符,对它们的简单操作就会具有原子性
对于非原子的操作(即非简单操作)的变量加上volatile,并不能提供原子性。
比如在碘片例子中,将BankAccount类写成这样:
class BankAccount { private volatile int total = 0; public void add(int n) { total += n; } public int getTotal() { return total; } }
这样也不能提供原子性,因为total += n;不是原子操作。如果给一个变量加上volatile修饰符,就相当于:每一个线程中一旦这个值发生了变化就马上刷新回主存,使得各个线程取出的值相同。编译器不要对这个变量的读、写操作做优化。
关于volatile的详细解释,参照:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
public class Test { public static void main(String[] args) { MyThreadLocal myThreadLocal = new MyThreadLocal(); Thread t1 = new MyThread(myThreadLocal); Thread t2 = new MyThread(myThreadLocal); Thread t3 = new MyThread(myThreadLocal); Thread t4 = new MyThread(myThreadLocal); t1.start(); t2.start(); t3.start(); t4.start(); } } class MyThreadLocal { // 定义了一个ThreadLocal变量,用来保存int或Integer数据 // 如果不覆盖initialValue,第一次get返回null,故需要初始化一个 private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { protected Integer initialValue() { return 0; }; }; public Integer getNextNum() { threadLocal.set(threadLocal.get() + 1); return threadLocal.get(); } } class MyThread extends Thread { private MyThreadLocal myThreadLocal = null; public MyThread(MyThreadLocal pMyThreadLocal) { this.myThreadLocal = pMyThreadLocal; } @Override public void run() { super.run(); for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "\t" + myThreadLocal.getNextNum()); } } }
运行之后发现每个线程中的ThreadLocal都不互相影响。