Java多线程
下面的代码如果没有出现线程冲突结果应该是200,但实际的结果在100到200之间:
1 class Foo{ 2 public String x = ""; 3 public void add(){ 4 x+="1"; 5 } 6 } 7 public class Main extends Thread{ 8 public boolean flag; 9 public Foo foo; 10 public Main(boolean flag){ 11 this.flag = flag; 12 } 13 public void run(){ 14 for(int i = 0; i < 100; i++){ 15 foo.add(); 16 } 17 } 18 public static void main(String[] arg){ 19 Foo foo = new Foo(); 20 Main a = new Main(true); 21 Main b = new Main(false); 22 a.foo = b.foo = foo; 23 a.start(); 24 b.start(); 25 try { 26 a.join(); 27 b.join(); 28 } catch (Exception e) {} 29 System.out.println(foo.x.length()); 30 } 31 }
在Foo中加入同步的代码之后,不管重复多少次结果都是200:
1 class Foo{ 2 public String x = ""; 3 public void add(){ 4 synchronized(this){ 5 x+="1"; 6 } 7 } 8 }
可以使用Object的wait和notify来等待对象/通知等待的线程:
1 class Foo extends Thread{ 2 public int total; 3 public void run(){ 4 synchronized (this) { 5 for(int i = total = 0; i < 100; i++){ 6 total = total + 1; 7 } 8 this.notify(); 9 } 10 } 11 } 12 public class Main extends Thread{ 13 public static void main(String[] arg){ 14 Foo foo = new Foo(); 15 foo.start(); 16 synchronized(foo){ 17 try { 18 foo.wait(); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 System.out.println("计算结果为:"+foo.total); 23 } 24 } 25 }
从上面的代码中看到每个对象都可以当做一个锁来用,Object中也提供了同步的方法:wait、notify、notifyAll。
发现如果notifyAll不再synchronized中,就会出现异常:通知调用的是本地方法,可能有用公共的东西?
如果没有调用notifyAll,那么线程就会一直等待下去而不会结束。
1 class Calculator extends Thread { 2 int total; 3 public void run() { 4 synchronized (this) { 5 System.out.println("Calculator 正在运行"); 6 for (int i = 0; i < 10000; i++) { 7 try { 8 Thread.sleep(1); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 total += i; 13 } 14 notifyAll(); 15 } 16 } 17 } 18 public class Main extends Thread { 19 Calculator c; 20 public Main(Calculator c) { 21 this.c = c; 22 } 23 public void run() { 24 synchronized (c) { 25 try { 26 System.out.println(Thread.currentThread() + "等待计算结果。。。"); 27 c.wait(); 28 } catch (InterruptedException e) { 29 } 30 System.out.println(Thread.currentThread() + "计算结果为:" + c.total); 31 } 32 } 33 public static void main(String[] args) { 34 Calculator calculator = new Calculator(); 35 new Main(calculator).start(); 36 new Main(calculator).start(); 37 new Main(calculator).start(); 38 calculator.start(); 39 } 40 }
java中的join和C编程中的join差不多,可以发现不管怎么执行,只要执行过join方法之后,主线程都会等线程Worker执行完成后再继续执行:
1 class Worker extends Thread{ 2 public void run(){ 3 for(int i = 0; i < 10; i++){ 4 System.out.println("线程第"+i+"次执行"); 5 } 6 } 7 } 8 public class Main{ 9 public static void main(String[] arg){ 10 Worker a = new Worker(); 11 a.start(); 12 for(int i = 0; i < 20; i++){ 13 System.out.println("主线程第"+i+"次执行"); 14 if(i > 2){ 15 try{ 16 a.join(); 17 }catch(Exception e){ 18 e.printStackTrace(); 19 } 20 } 21 } 22 } 23 }
守护线程和用户线程最关键的区别:Java虚拟机何时离开。只要在虚拟机上还有非守护线程在运行,那么虚拟机就不会离开。
如果只剩下守护线程了,那守护线程是为应用程序来服务的,这个时候当然也没什么必要存在了。从下面的代码中可以看到区别:
1 class Worker extends Thread{ 2 public void run(){ 3 for(int i = 0; i < 1000000; i++){ 4 System.out.println("线程第"+i+"次执行"); 5 } 6 } 7 } 8 public class Main{ 9 public static void main(String[] arg){ 10 Worker a = new Worker(); 11 a.setDaemon(true); 12 a.start(); 13 for(int i = 0; i < 20; i++){ 14 System.out.println("主线程第"+i+"次执行"); 15 } 16 } 17 }
同步的时候,不只是可以把一个对象当做是锁来使用,而且可以把一个方法声明为同步的,其实这样相当于把方法中的代码放入由该类型对应的Class对象来保护的。使用方法如下:
1 class Foo{ 2 public String a = ""; 3 public synchronized void add(){ 4 a = a+"1"; 5 } 6 } 7 class Worker extends Thread{ 8 private Foo foo; 9 public Worker(Foo foo){ 10 this.foo = foo; 11 } 12 public void run(){ 13 for(int i = 0; i < 100; i++){ 14 foo.add(); 15 } 16 } 17 } 18 public class Main{ 19 public static void main(String[] arg){ 20 Foo foo = new Foo(); 21 Worker a = new Worker(foo); 22 Worker b = new Worker(foo); 23 a.start(); 24 b.start(); 25 try { 26 a.join(); 27 b.join(); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 System.out.println(foo.a.length()); 32 } 33 }
volatile只具有锁的可见性。
只能在有限的一些情况下使用volatile变量代替锁,需要同时满足:对变量的写操作不依赖于当前值,该变量没有包含在有其他变量的不变式中。
在某些情况下,volatile变量同步机制的性能要优于锁。
它的特点是:只要值发生了改变,那么观察的线程都能读取到最新的值。
一个利用volatile的读取效率高和synchronized原子性的例子:
1 class CheesCounter{ 2 private volatile int value; 3 public int getValue() { 4 return value; 5 } 6 public synchronized void setValue(int value) { 7 this.value = value; 8 } 9 }
线程池的最简单最简单的例子,注意一下输出的线程的名称:
1 public class Main { 2 public static void main(String[] args) { 3 ExecutorService pool = Executors.newFixedThreadPool(2); 4 Thread t1 = new MyThread(); 5 Thread t2 = new MyThread(); 6 Thread t3 = new MyThread(); 7 Thread t4 = new MyThread(); 8 Thread t5 = new MyThread(); 9 pool.execute(t1); 10 pool.execute(t2); 11 pool.execute(t3); 12 pool.execute(t4); 13 pool.execute(t5); 14 pool.shutdown(); 15 } 16 } 17 class MyThread extends Thread{ 18 public void run() { 19 System.out.println(Thread.currentThread().getName()+"正在执行。。。"); 20 } 21 }
线程池的延迟执行的功能:
1 public class Main { 2 public static void main(String[] args) { 3 ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); 4 Thread t1 = new MyThread(); 5 Thread t2 = new MyThread(); 6 Thread t3 = new MyThread(); 7 Thread t4 = new MyThread(); 8 Thread t5 = new MyThread(); 9 pool.execute(t1); 10 pool.execute(t2); 11 pool.execute(t3); 12 pool.schedule(t4, 10000, TimeUnit.MILLISECONDS); 13 pool.schedule(t5, 10000, TimeUnit.MILLISECONDS); 14 15 pool.shutdown(); 16 } 17 } 18 class MyThread extends Thread { 19 public void run() { 20 System.out.println(Thread.currentThread().getName() + "正在执行。。。"); 21 } 22 }
有返回值的线程:
1 import java.util.concurrent.*; 2 public class Main { 3 public static void main(String[] args) throws ExecutionException, InterruptedException { 4 ExecutorService pool = Executors.newFixedThreadPool(2); 5 Callable c1 = new MyCallable("A"); 6 Callable c2 = new MyCallable("B"); 7 Future f1 = pool.submit(c1); 8 Future f2 = pool.submit(c2); 9 System.out.println(">>>"+f1.get().toString()); 10 System.out.println(">>>"+f2.get().toString()); 11 pool.shutdown(); 12 } 13 } 14 class MyCallable implements Callable{ 15 private String oid; 16 MyCallable(String oid) { 17 this.oid = oid; 18 } 19 public Object call() throws Exception { 20 return oid+"任务返回的内容"; 21 } 22 }
Lock用法的一个最简单的例子:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Foo{ String str = ""; } class Worker extends Thread{ private Lock lock; private Foo foo; public Worker(Lock lock, Foo foo){ this.lock = lock; this.foo = foo; } public void run() { for(int i = 0; i < 1000; i++) { lock.lock(); foo.str = foo.str + "1"; lock.unlock(); } } } public class Main{ public static void main(String[] arg){ Lock lock = new ReentrantLock(); Foo foo = new Foo(); Worker a = new Worker(lock, foo); Worker b = new Worker(lock, foo); a.start(); b.start(); try { a.join(); b.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(foo.str.length()); } }
下面是用ReadWriteLock来处理读者写者问题,封装之后相当简单了,但是没有C写的有意思:
1 import java.util.concurrent.locks.ReadWriteLock; 2 import java.util.concurrent.locks.ReentrantReadWriteLock; 3 class Foo{ 4 String str = ""; 5 } 6 class Reader extends Thread{ 7 Foo foo; 8 ReadWriteLock lock; 9 public Reader(Foo foo, ReadWriteLock lock){ 10 this.foo = foo; 11 this.lock = lock; 12 } 13 public void run(){ 14 lock.readLock().lock(); 15 System.out.println("读者线程正在执行:"+Thread.currentThread().getName()+" 当前长度为:"+foo.str.length()); 16 try { 17 Thread.sleep(1000); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println("读者线程完成执行:"+Thread.currentThread().getName()); 22 lock.readLock().unlock(); 23 } 24 } 25 class Writer extends Thread{ 26 Foo foo; 27 ReadWriteLock lock; 28 public Writer(Foo foo, ReadWriteLock lock){ 29 this.foo = foo; 30 this.lock = lock; 31 } 32 public void run(){ 33 lock.writeLock().lock(); 34 foo.str = foo.str + "0"; 35 System.out.println("写者线程正在执行:"+Thread.currentThread().getName()+" 当前长度为:"+foo.str.length()); 36 try { 37 Thread.sleep(1000); 38 } catch (InterruptedException e) { 39 e.printStackTrace(); 40 } 41 System.out.println("写者线程完成执行:"+Thread.currentThread().getName()); 42 lock.writeLock().unlock(); 43 } 44 } 45 public class Main{ 46 public static void main(String[] arg){ 47 ReadWriteLock lock = new ReentrantReadWriteLock(false); 48 Foo foo = new Foo(); 49 for(int i = 0; i < 20; i++){ 50 if(i%4 == 0){ 51 Writer a = new Writer(foo, lock); 52 a.start(); 53 }else{ 54 Reader a = new Reader(foo, lock); 55 a.start(); 56 } 57 } 58 } 59 }
初始化信号量的时候赋值为1,这样信号量就相当于一个锁了,下面是信号量最简单的例子:
1 import java.util.concurrent.Semaphore; 2 class Worker extends Thread{ 3 int x; 4 Semaphore sp; 5 public Worker(int x, Semaphore sp){ 6 this.x = x; 7 this.sp = sp; 8 } 9 public void run(){ 10 try { 11 sp.acquire(x); 12 System.out.println(Thread.currentThread().getName()+"成功获取"+x+"个信号量。"); 13 14 Thread.sleep(1000*x); 15 synchronized(Worker.class){ 16 sp.release(x); 17 System.out.println(Thread.currentThread().getName()+"成功释放"+x+"个信号量。"); 18 } 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 } 23 } 24 public class Main{ 25 public static void main(String[] arg){ 26 Semaphore sp = new Semaphore(20); 27 for(int i = 0; i < 20; i++){ 28 Worker a = new Worker(i%3+1, sp); 29 a.start(); 30 } 31 } 32 }
BlockingQueue是阻塞队列,如果队列中满的时候put会将线程阻塞。
BlockingDeque是阻塞栈,如果栈中满的时候putFirst的时候会将线程阻塞。
条件变量Condition就是表示条件的一种变量,Java中的条件变量只能和锁配合使用来控制并发程序访问竞争资源的安全。
条件变量的出现是为了更精细地控制线程的等待和唤醒:一个锁可以有多个条件,每个条件上可以有多个线程等待,通过调用await方法可以让线程在该条件上等待,当调用signalAll方法,又可以唤醒该条件下的等待的线程。
下面是用法的最简单的一个例子,还没想到怎么演示与notify的实现的区别,以后再来补上:
1 class Worker extends Thread{ 2 Condition condition; 3 Lock lock; 4 public Worker(Condition condition, Lock lock){ 5 this.condition = condition; 6 this.lock = lock; 7 } 8 public void run(){ 9 lock.lock(); 10 System.out.println(Thread.currentThread().getName()+"正在运行。"); 11 condition.signalAll(); 12 lock.unlock(); 13 } 14 } 15 public class Main{ 16 public static void main(String[] arg){ 17 Lock lock = new ReentrantLock(); 18 Condition condition = lock.newCondition(); 19 ExecutorService pool = Executors.newFixedThreadPool(2); 20 for(int i = 0; i < 10; i++){ 21 Worker a = new Worker(condition, lock); 22 pool.execute(a); 23 } 24 pool.shutdown(); 25 } 26 }
Java中也提供了就像C中的原子变量的类型,比如long对应的AtomicLong,其实也只是在单次的操作中保证不会因为多个线程并发地读写而造成值的错误,下面是一个测试的例子:
1 class Foo{ 2 AtomicLong along = new AtomicLong(0); 3 } 4 class Worker extends Thread{ 5 Foo foo; 6 public Worker(Foo foo){ 7 this.foo = foo; 8 } 9 public void run(){ 10 foo.along.addAndGet(1); 11 } 12 } 13 public class Main{ 14 public static void main(String[] arg){ 15 Foo foo = new Foo(); 16 for(int i = 0; i < 10000; i++){ 17 Worker a = new Worker(foo); 18 a.start(); 19 try { 20 a.join(); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 } 25 System.out.println(foo.along.get()); 26 } 27 }
有时候一个大的任务,常常需要分配给多个子任务区执行,只有当所有的子任务都执行完成的时候才能执行主任务,一个简单的例子,感觉和join有点像:
1 public class Main { 2 public static void main(String[] args) { 3 CyclicBarrier cb = new CyclicBarrier (10, new MainTask()); 4 for(int i = 0; i < 10; i++){ 5 new SubTask(cb).start(); 6 } 7 } 8 } 9 class MainTask implements Runnable { 10 public void run() { 11 System.out.println("主线程"); 12 } 13 } 14 class SubTask extends Thread { 15 private CyclicBarrier cb; 16 SubTask(CyclicBarrier cb) { 17 this.cb = cb; 18 } 19 public void run() { 20 try { 21 Thread.sleep(1000); 22 System.out.println("[子任务" + Thread.currentThread().getName() + "]完成,并通知障碍器已经完成!"); 23 cb.await(); 24 } catch (Exception e) { 25 e.printStackTrace(); 26 } 27 } 28 }
可以看到是子线程都执行了await之后MainTask才会执行。如果没有给定数目的子任务,那么主线程就永远不会运行了。
上面是看到网上的例子,自己实践了一下多线程的用法,都很浅显,就是笔记。关于深入的理解会在后面的博客中给出。
----------------------
欢迎拍砖。