java 线程
一、线程的基本概念
线程理解:线程是一个程序里面不同的执行路径
每一个分支都叫做一个线程,main()叫做主分支,也叫主线程。
程只是一个静态的概念,机器上的一个.class文件,机器上的一个.exe文件,这个叫做一个 进程。程序的执行过程都是这样的:首先把程序的代码放到内存的代码区里面,代码放到代码区后并没有马上开始执行,但这时候说明了一个进程准备开始,进程已 经产生了,但还没有开始执行,这就是进程,所以进程其实是一个静态的概念,它本身就不能动。平常所说的进程的执行指的是进程里面主线程开始执行了,也就是main()方法开始执行了。进程是一个静态的概念,在我们机器里面实际上运行的都是线程。
Windows操作系统是支持多线程的,它可以同时执行很多个线程,也支持多进程,因此Windows操作系统是支持多线程多进程的操作系统。Linux和Uinux也是支持多线程和多进程的操作系统。DOS就不是支持多线程和多进程了,它只支持单进程,在同一个时间点只能有一个进程在执行,这就叫单线程。
CPU难道真的很神通广大,能够同时执行那么多程序吗?不是的,CPU的执行是这样的:CPU的速度很快,一秒钟可以算好几亿次,因此CPU把自己的时间分成一个个小时间片,我这个时间片执行你一会,下一个时间片执行他一会,再下一个时间片又执行其他人一会,虽然有几十个线程,但一样可以在很短的时间内把他们通通都执行一遍,但对我们人来说,CPU的执行速度太快了,因此看起来就像是在同时执行一样,但实际上在一个时间点上,CPU只有一个线程在运行。
学习线程首先要理清楚三个概念:
- 进程:进程是一个静态的概念
- 线程:一个进程里面有一个主线程叫main()方法,是一个程序里面的,一个进程里面不同的执行路径。
- 在同一个时间点上,一个CPU只能支持一个线程在执行。因为CPU运行的速度很快,因此我们看起来的感觉就像是多线程一样。
什么才是真正的多线程?如果你的机器是双CPU,或者是双核,这确确实实是多线程。
二、线程的创建和启动
在JAVA里面,JAVA的线程是通过java.lang.Thread类来实现的,每一个Thread对象代表一个新的线程。创建一个新线程出来有两种方法:第一个是从Thread类继承,另一个是实现接口runnable。VM启动时会有一个由主方法(public static void main())所定义的线程,这个线程叫主线程。可以通过创建Thread的实例来创建新的线程。你只要new一个Thread对象,一个新的线程也就出现了。每个线程都是通过某个特定的Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。
范例1:使用实现Runnable接口创建和启动新线程
开辟一个新的线程来调用run方法
1 /** 2 * java 开辟新线程 3 * 2016/5/7 4 **/ 5 package org.Thread; 6 7 8 public class TestThread { 9 10 public static void main(String[] args) { 11 Runner_1 r1 = new Runner_1(); //new一个线程类的对象出来 12 //r1.run(); 这个方法调用的执行是等run()方法执行完之后才会继续继续执行main()方法 13 Thread t = new Thread(r1); //要启动一个新的线程就必须new一个Thread对象出来 14 t.start();//启动新开辟的线程,新线程执行的是run()方法,新线程与主线程回忆起并行执行 15 for(int i = 0; i < 10; i++){ 16 System.out.println("mainthead:"+ i); 17 } 18 } 19 } 20 21 class Runner_1 implements Runnable{ 22 Runner_1(){}; 23 public void run(){ 24 for(int i = 0; i < 10; i++){ 25 System.out.println("Runner_1:"+i); 26 } 27 } 28 }
执行结果:
Runner_1:0 mainthead:0 mainthead:1 Runner_1:1 mainthead:2 Runner_1:2 mainthead:3 Runner_1:3 mainthead:4 Runner_1:4 mainthead:5 Runner_1:5 Runner_1:6 Runner_1:7 Runner_1:8 Runner_1:9 mainthead:6 mainthead:7 mainthead:8 mainthead:9
三、创建线程池
Thread方法对于单一的任务的执行很方便,方式由于必须为每一个任务创建一个线程,所以对大量任务不够高效。所以提出线程池的概念。
1 /** 2 * 线程池,如果要为多个任务创建线程,就需要使用线程池,而不是Thread 3 * 2016/6/14 4 */ 5 package org.Thread; 6 7 import java.util.concurrent.*; 8 9 public class ExecutorDemo { 10 11 public static void main(String[] args) { 12 //创建3个固定的线程池 13 ExecutorService executor = Executors.newFixedThreadPool(3); 14 15 executor.execute( new Print_1()); 16 executor.execute(new Print_2()); 17 executor.execute(new Print_3()); 18 19 executor.shutdown(); //通知执行器关闭,不能接受新的任务,但是现有任务将继续执行直至完成 20 } 21 22 } 23 24 class Print_1 implements Runnable{ 25 Print_1(){}; 26 public void run(){ 27 for(int i = 0; i < 20; i++) 28 System.out.println("这是任务1×××××××××"); 29 } 30 } 31 32 class Print_2 implements Runnable{ 33 Print_2(){}; 34 public void run(){ 35 for(int i = 0; i < 20; i++) 36 System.out.println("这是任务2########"); 37 } 38 } 39 40 class Print_3 implements Runnable{ 41 public void run(){ 42 for(int i = 0; i < 20; i++) 43 System.out.println("这是任务3&&&&&&&&"); 44 } 45 }
执行结果部分截图:
四、多线程同步问题
如果一个资源被多个线程同时访问的话,就有可能会找到破坏。
1 /** 2 * java 多线程同步问题 3 * 2016/5/14 4 **/ 5 package org.Thread; 6 7 import java.util.concurrent.*; 8 public class ExecutorSynch { 9 private static Account account = new Account(); 10 11 public static void main(String[] args) { 12 ExecutorService executor = Executors.newCachedThreadPool(); 13 14 for(int i = 0; i < 100; i++){ 15 executor.execute(new AddApennyTask()); 16 } 17 18 executor.shutdown(); 19 20 while(!executor.isTerminated()){ 21 22 } 23 24 System.out.println("what is balance? " + account.getBalance()); 25 26 } 27 28 private static class AddApennyTask implements Runnable{ 29 public void run(){ 30 account.deposit(1); 31 } 32 } 33 private static class Account{ 34 private int balance = 0; 35 public int getBalance(){ 36 return balance; 37 } 38 39 public void deposit(int amount){ 40 int newBalance = balance + amount; 41 42 try{ 43 Thread.sleep(5); 44 }catch(InterruptedException ex){ 45 46 } 47 balance = newBalance; 48 } 49 } 50 51 }
运行结果:
本来我们创建了100个线程,每个线程都向账户添加了1便士,100个线程结果按照预想的应该是100便士,但是结果确实不可预测的,这是因为第30、40、47行的代码在多个线程中被同时访问了
所以导致结果不可预测。
解决方法一:我们可以使deposit方法中添加关键字synchronized,使Account类成为线程安全的。
将39行换为:public synchronized void deposit (int amount)
解决方法二:利用同步语句块使得线程安全。
将第39行的语句放入到同步块中:
synchronized (account) {
account.deposit(1);
}
方法三:利用加锁来进行同步;
1 /** 2 * java 线程池 利用加锁同步 3 * 2016/5/15 4 **/ 5 package org.Thread; 6 7 import java.util.concurrent.*; 8 import java.util.concurrent.locks.*; 9 10 public class ExecutorSynchLock { 11 12 private static Account account = new Account(); 13 14 public static void main(String[] args) { 15 ExecutorService executor = Executors.newCachedThreadPool(); 16 //新建100个线程 17 for(int i = 0; i < 100; i++){ 18 executor.execute(new AddApennyTask()); 19 } 20 executor.shutdown(); 21 while(!executor.isTerminated()){ 22 } 23 24 System.out.println("what is balance? " + account.getBalance()); 25 26 } 27 28 private static class AddApennyTask implements Runnable{ 29 public void run(){ 30 account.deposit(1); 31 } 32 } 33 private static class Account{ 34 private int balance = 0; 35 private static Lock lock = new ReentrantLock(); //创建锁 36 public int getBalance(){ 37 return balance; 38 } 39 40 public void deposit(int amount){ 41 lock.lock(); //加锁 42 try{ 43 int newBalance = balance + amount; 44 Thread.sleep(5); 45 balance = newBalance; 46 }catch(InterruptedException ex){ 47 }finally{ 48 lock.unlock(); //解锁 49 } 50 } 51 } 52 53 }
方法四:利用信号量模拟加锁保证同步
1 /** 2 * java 线程 信号量 3 * 2016/6/20 4 */ 5 package cn.thread; 6 7 import java.util.concurrent.ExecutorService; 8 import java.util.concurrent.Executors; 9 import java.util.concurrent.Semaphore; 10 11 12 public class Semaphore_ { 13 private static Account account = new Account(); 14 public static void main(String[] args){ 15 ExecutorService executor = Executors.newCachedThreadPool(); 16 //创建启动100个线程 17 for(int i = 0; i < 100; i++){ 18 executor.execute(new AddAPennyTask()); 19 } 20 executor.shutdown(); 21 //等待所有的任务完成 22 while(!executor.isTerminated()) { 23 } 24 25 System.out.println("What is balance?" + account.getBalance()); 26 } 27 //添加一便士 28 private static class AddAPennyTask implements Runnable { 29 public void run() { 30 account.deposit(1); 31 } 32 } 33 //账户类 34 private static class Account { 35 //创建只有一个许可的信号量,表示同时只有一个线程能访问deposit()方法 36 private static Semaphore semaphore = new Semaphore(1); 37 private int balance = 0; 38 public int getBalance() { 39 return balance; 40 } 41 //添加synchronized可以防止多个任务同时访问这个函数 42 public void deposit(int amount) { 43 try{ 44 semaphore.acquire(); //捕获许可 45 int newBalance = balance + amount; 46 balance = newBalance; 47 Thread.sleep(5); 48 }catch (InterruptedException ex){ 49 50 }finally{ 51 semaphore.release(); //释放许可 52 } 53 } 54 } 55 }
五、线程间的协作
有时候可能需要一个线程在某一情况下通知另一个线程执行某个任务。
比如取款时如果账户余额小于取款金额是就应该等待,直到有新的存款进入之后,再进行取款操作。
1 /** 2 * java 线程间协作 3 * 2016/6/20 4 */ 5 package cn.thread; 6 7 import java.util.concurrent.ExecutorService; 8 import java.util.concurrent.Executors; 9 import java.util.concurrent.locks.Condition; 10 import java.util.concurrent.locks.Lock; 11 import java.util.concurrent.locks.ReentrantLock; 12 13 public class ThreadCooperation { 14 private static Account account = new Account(); 15 public static void main(String[] args){ 16 ExecutorService executor = Executors.newFixedThreadPool(2); 17 executor.execute(new DepositTask()); 18 executor.execute(new WithdrawTask()); 19 executor.shutdown(); 20 21 System.out.println("Thread 1\t\tThread 2\t账户余额"); 22 } 23 //给账户存款 24 public static class DepositTask implements Runnable{ 25 public void run() { 26 try { 27 while (true){ 28 account.deposit((int)(Math.random() * 10) + 1); 29 Thread.sleep(1000); 30 } 31 }catch (InterruptedException ex){ 32 ex.printStackTrace(); 33 } 34 } 35 } 36 //从账户中提款 37 public static class WithdrawTask implements Runnable { 38 public void run() { 39 while(true){ 40 account.withdraw((int)(Math.random() * 10) +1); 41 } 42 } 43 } 44 45 private static class Account { 46 private static Lock lock = new ReentrantLock(); 47 48 private static Condition newDeposit = lock.newCondition(); 49 private int balance = 0; 50 public int getBalance(){ 51 return balance; 52 } 53 public void withdraw(int amount){ 54 lock.lock(); 55 try { 56 while(balance < amount){ 57 System.out.println("\t\t\t等待存款"); 58 newDeposit.await(); 59 } 60 balance -= amount; 61 System.out.println("\t\t\t提款" + amount + "\t\t" + getBalance()); 62 } catch (Exception e) { 63 e.printStackTrace(); 64 }finally{ 65 lock.unlock(); 66 } 67 } 68 public void deposit(int amount){ 69 lock.lock(); 70 try { 71 balance += amount; 72 System.out.println("存款" + amount + "\t\t\t\t\t" + getBalance()); 73 newDeposit.signalAll(); 74 } catch (Exception e) { 75 }finally{ 76 lock.unlock(); 77 } 78 } 79 } 80 }
经典实例:生产者/消费者
1 /** 2 * java 线程实例 生产者/消费者问题 3 * 2016/6/20 4 */ 5 package cn.thread; 6 7 import java.util.concurrent.ExecutorService; 8 import java.util.concurrent.Executors; 9 import java.util.concurrent.locks.Condition; 10 import java.util.concurrent.locks.Lock; 11 import java.util.concurrent.locks.ReentrantLock; 12 13 public class ConsumerProducer { 14 private static Buffer buffer = new Buffer(); 15 public static void main(String[] args){ 16 ExecutorService executor = Executors.newFixedThreadPool(2); 17 executor.execute(new ProducerTask()); 18 executor.execute(new ConsumerTask()); 19 executor.shutdown(); 20 21 } 22 //生产者 23 private static class ProducerTask implements Runnable{ 24 public void run(){ 25 try { 26 int i =1; 27 while(true){ 28 System.out.println("生产者生产" + i); 29 buffer.write(i++); 30 Thread.sleep((int)(Math.random() * 10000)); 31 32 } 33 } catch (Exception e) { 34 e.printStackTrace(); 35 } 36 } 37 } 38 //消费者 39 private static class ConsumerTask implements Runnable{ 40 public void run(){ 41 try { 42 while(true){ 43 44 System.out.println("\t\t\t消费者消费" + buffer.read()); 45 Thread.sleep((int)(Math.random() * 10000)); 46 } 47 } catch (Exception e) { 48 e.printStackTrace(); 49 } 50 } 51 } 52 53 private static class Buffer{ 54 private static final int CAPACITY = 1; 55 private java.util.LinkedList<Integer>queue = new java.util.LinkedList<Integer>(); 56 //创建锁 57 private static Lock lock = new ReentrantLock(); 58 59 private static Condition notEmpty = lock.newCondition(); 60 private static Condition notFull = lock.newCondition(); 61 62 public void write(int value){ 63 lock.lock(); 64 try { 65 while(queue.size() == CAPACITY) { 66 System.out.println("等待notFull信号"); 67 notFull.await(); 68 } 69 queue.offer(value); 70 notEmpty.signal(); 71 } catch (InterruptedException e) { 72 e.printStackTrace(); 73 }finally{ 74 lock.unlock(); 75 } 76 } 77 78 public int read(){ 79 int value = 0; 80 lock.lock(); 81 try { 82 while(queue.isEmpty()){ 83 System.out.println("\t\t等待 notEmpty信号"); 84 notEmpty.await(); 85 } 86 87 value = queue.remove(); 88 notFull.signal(); 89 } catch (InterruptedException e){ 90 e.printStackTrace(); 91 }finally{ 92 lock.unlock(); 93 } 94 return value; 95 } 96 } 97 }
使用java内置的阻塞队列来简化上边生产者消费者例子
(此程序中同步已经在ArrayBlockingQueue中实现,所以不需要再手动编码进行实现)
1 /** 2 * java 多线程/阻塞队列 3 * 2016/6/20 4 */ 5 package cn.thread; 6 7 import java.util.concurrent.ArrayBlockingQueue; 8 import java.util.concurrent.ExecutorService; 9 import java.util.concurrent.Executors; 10 11 public class ConsumerProducerUsingBlockingQueue { 12 private static ArrayBlockingQueue<Integer> buffer = new ArrayBlockingQueue<Integer>(2); 13 14 public static void main(String[] args){ 15 ExecutorService executor = Executors.newFixedThreadPool(2);//java内置的阻塞队列 16 executor.execute(new ProducerTask()); 17 executor.execute(new ConsumerTask()); 18 executor.shutdown(); 19 } 20 21 private static class ProducerTask implements Runnable{ 22 public void run(){ 23 try { 24 int i = 1; 25 while(true){ 26 System.out.println("Priducer writes" + i); 27 buffer.put(i++); //在队尾插入一个元素,如果队列已满则等待 28 Thread.sleep((int)(Math.random() * 10000)); 29 30 } 31 } catch (Exception e) { 32 e.printStackTrace(); 33 } 34 } 35 } 36 37 private static class ConsumerTask implements Runnable{ 38 public void run(){ 39 try { 40 while(true){ 41 System.out.println("\t\t\tConsumer reads"+ buffer.take());//返回并删除这个队列的头,如果队列为空则等待 42 Thread.sleep((int)(Math.random() * 10000)); 43 } 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 } 48 } 49 }
六、 创建一个有返回值的线程
我们通过实现Callable接口,实现call方法并且使用ExecutorService.submit()方法调用它就可以实现一个有返回值的线程任务。
1 package thread; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.ExecutorService; 6 import java.util.concurrent.Executors; 7 8 public class TestCallable { 9 public static void main(String[] args) throws InterruptedException, ExecutionException { 10 Integer n; 11 ExecutorService executor = Executors.newFixedThreadPool(1); 12 n = executor.submit(new MyCallable(10)).get(); 13 System.out.println(n); 14 executor.shutdown(); 15 } 16 } 17 18 class MyCallable implements Callable<Integer>{ 19 int num; 20 public MyCallable(int num) { 21 this.num = num; 22 } 23 24 @Override 25 public Integer call() throws Exception { 26 num = num * num; 27 System.out.println("num:" + num); 28 return num; 29 } 30 31 }