开启一个线程

实现一个线程的方式有两种:继承Thread类实现Runnable接口(也存在说三种的情况,第三种是使用线程并发库中的线程池创建一个线程。这两种方法都需要重写Run方法,具体的线程逻辑代码写在Run方法中。其实Thread类就实现了Runnable接口,并且添加了一些常用的方法。并没有什么说法是使用哪种方式存在效率高低的问题,推荐使用实现Runnable接口的方式,因为更加面向对象,而且实现一个接口比继承一个类更灵活。我们可以使用匿名内部类的方式很方便的开启一个线程(使用Tread类的start方法开启一个线程):

Thread :

  1. new Thread(){
  2. public void run() {
  3.  
  4. };
  5. }.start();

Runnable :

  1. new Thread(new Runnable() {
  2.  
  3. @Override
  4. public void run() {
  5.  
  6. }
  7. }).start();

传统线程同步锁

当多个线程操作同一个共享资源会存在线程安全问题,我们需要使用同步来进行控制,一个线程在操作的时候,其他的线程就不允许再操作了,当这个线程运行完了或是释放了线程锁,其他线程才可以运行。Java中使用 synchronized 关键字进行线程同步互斥:

  1. synchronized(线程锁){
  2.  
  3. }

线程锁可以是任何对象,所以wait,notify等方法被设计成为Object的方法。

值得注意的是:多个线程使用的线程锁一定要是相同的对象,不然是达不到同步的作用,特别是使用String对象作为线程锁的时候,需要好好想想到底是不是同一个String对象。

synchronize关键字还可以加在方法名上,那么在方法名上使用的时候线程锁使用的是哪个对象呢? 没错,就是this

wait和sleep方法

这两个方法都有让线程暂停的效果,但是wait是Object的方法,sleep方法是Thread类的方法。这两个方法都是需要在同步代码块中使用,但是使用wait会让线程放弃线程锁,sleep方法不会放弃线程锁,其他的线程还是会阻塞。使用wait的线程放弃线程锁之后就会进入等待队列中,所以不要忘记还有使用notify或notifyAll来唤醒在等待队列中的线程重新运行(这就是线程之间通信)。下面给出一个使用标志位+wait+notify来等待的例子:

  1. private boolean tage=true; //标志位
  2.  
  3. public synchronized void sub(int i){
  4.  
  5. while(!tage){ //如果不是本线程运行则等待
  6. try {
  7. this.wait();
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12.  
  13. //下面可以写线程运行的操作代码
  14.  
  15. tage=false; //调整标志
  16. this.notify(); //通知(唤醒)下面main方法中线程
  17.  
  18. }
  19.  
  20. public synchronized void main(int i){
  21.  
  22. while(tage){
  23. try {
  24. this.wait();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29.  
  30. //下面可以写线程运行的操作代码
  31.  
  32. tage=true;
  33. this.notify();
  34.  
  35. }

这是一个两个线程进行通信的例子,所以在sub方法的最后我们可以知道接下来会唤醒的线程是名为main方法中的线程,实际上notify唤醒的线程是由JVM或操作系统来决定的,我们并不知道多个在等待队列中等待的线程哪个会被唤醒。

需求:子线程循环10次,切换到主线程循环100次,再切换回子线程,如此往复50次

  1. public class ThreadCommuncation {
  2.  
  3. /**
  4. * 子线程循环10次,切换到主线程循环100次,再切换回子线程,如此往复50次
  5. * @param args
  6. */
  7. public static void main(String[] args) {
  8.  
  9. Business business=new Business();
  10.  
  11. //子线程
  12. new Thread(new Runnable() {
  13.  
  14. @Override
  15. public void run() {
  16. //往复50次
  17. for(int i=1;i<=50;i++){
  18. business.sub(i);
  19. }
  20. }
  21. }).start();
  22.  
  23. //主线程往复50次
  24. for(int i=1;i<=50;i++){
  25. business.main(i);
  26. }
  27.  
  28. }
  29.  
  30. }
  31.  
  32. class Business{
  33.  
  34. private boolean tage=true; //标志位
  35.  
  36. public synchronized void sub(int i){
  37. //子线程循环10次后切换
  38. while(!tage){ //如果不是本线程运行则等待
  39. try {
  40. this.wait();
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45.  
  46. for(int j=1;j<=10;j++){
  47. System.out.println("子线程第"+i+"趟的第"+j+"次循环");
  48. }
  49.  
  50. tage=false; //调整标志
  51. this.notify(); //通知(唤醒)主线程
  52.  
  53. }
  54.  
  55. public synchronized void main(int i){
  56. //主线程循环100次后切换
  57. while(tage){
  58. try {
  59. this.wait();
  60. } catch (InterruptedException e) {
  61. e.printStackTrace();
  62. }
  63. }
  64.  
  65. for(int j=1;j<=100;j++){
  66. System.out.println("主线程第"+i+"趟的第"+j+"次循环");
  67. }
  68.  
  69. tage=true;
  70. this.notify();
  71.  
  72. }
  73.  
  74. }

传统线程模型中的定时器

在传统线程模型中我们使用util包下的Timer类中的 schedule方法 来实现定时任务:

  1. new Timer().schedule(new TimerTask() {
  2.  
  3. @Override
  4. public void run() {
  5.  
  6. }
  7. }, 1000);

run方法中就是需要定时器去执行什么样的工作,后一个参数表示1秒后才开始运行,schedule方法有多个重载方法,可以实现不同定时需求

同一线程内共享数据(线程局部变量)

一个基本的思路是:将需要共享的数据存在Map中,我们就以线程为key,需要共享的数据为value保存:

  1. import java.util.HashMap;
  2. import java.util.Map;
  3. import java.util.Random;
  4.  
  5. /**
  6. * 同一线程范围内共享变量
  7. * @author LZ
  8. *
  9. */
  10. public class ThreadScopeSharaData {
  11.  
  12. private static Map<Thread,Integer> threadData=new HashMap<Thread,Integer>();
  13.  
  14. public static void main(String[] args) {
  15. //开启两个线程
  16. for(int i=0;i<2;i++){
  17. new Thread(new Runnable() {
  18.  
  19. @Override
  20. public void run() {
  21. //每个线程共享的数据
  22. int data=new Random().nextInt(10);
  23. System.out.println(Thread.currentThread().getName()+" put data "+data);
  24. threadData.put(Thread.currentThread(), data);
  25. new A().get();
  26. new B().get();
  27. }
  28. }).start();
  29. }
  30. }
  31.  
  32. static class A{
  33. public void get(){
  34. int data=threadData.get(Thread.currentThread());
  35. System.out.println("A "+Thread.currentThread().getName()+" get data "+data);
  36. }
  37. }
  38.  
  39. static class B{
  40. public void get(){
  41. int data=threadData.get(Thread.currentThread());
  42. System.out.println("B "+Thread.currentThread().getName()+" get data "+data);
  43. }
  44. }
  45.  
  46. }

开启了两个线程,这两个线程中都调用A,B类中的方法输出共享数据data,会发现每个线程中data的值是一样的,这就完成了线程内共享数据。

注意:但是hashmap不是线程安全的,可能会某些情况下报异常

所以推荐使用ThreadLocal(线程局部变量)类来共享线程类的数据,它的用法与map类似:

  1. import java.util.Random;
  2.  
  3. /**
  4. * 同一线程范围内共享变量(使用Threadlocal)
  5. * @author LZ
  6. *
  7. */
  8. public class ThreadLocalTest {
  9.  
  10. //private static Map<Thread,Integer> threadData=new HashMap<Thread,Integer>();
  11.  
  12. public static void main(String[] args) {
  13. //开启两个线程
  14. for(int i=0;i<2;i++){
  15. new Thread(new Runnable() {
  16.  
  17. @Override
  18. public void run() {
  19. //每个线程共享的数据
  20. int data=new Random().nextInt(10);
  21. System.out.println(Thread.currentThread().getName()+" put data "+data);
  22. //threadData.put(Thread.currentThread(), data);
  23. MyThreadScopDate.getInstance().setName("name"+data);
  24. MyThreadScopDate.getInstance().setAge("age"+data);
  25. new A().get();
  26. new B().get();
  27. }
  28. }).start();
  29. }
  30. }
  31.  
  32. static class A{
  33. public void get(){
  34. //int data=threadData.get(Thread.currentThread());
  35. MyThreadScopDate myDate = MyThreadScopDate.getInstance();
  36. System.out.println("A "+Thread.currentThread().getName()+" get Mydate "+myDate.getName()+"\t"+myDate.getAge());
  37. }
  38. }
  39.  
  40. static class B{
  41. public void get(){
  42. //int data=threadData.get(Thread.currentThread());
  43. MyThreadScopDate myDate = MyThreadScopDate.getInstance();
  44. System.out.println("B "+Thread.currentThread().getName()+" get Mydate "+myDate.getName()+"\t"+myDate.getAge());
  45. }
  46. }
  47.  
  48. }
  49.  
  50. /**
  51. * 将Threadlocal放入MyThreadScopDate类作为属性,使用单例模式创建该类的对象
  52. * @author LZ
  53. *
  54. */
  55. class MyThreadScopDate{
  56. private static ThreadLocal<MyThreadScopDate> map=new ThreadLocal<MyThreadScopDate>();
  57.  
  58. private MyThreadScopDate(){}
  59.  
  60. public static MyThreadScopDate getInstance(){
  61. MyThreadScopDate myThreadScopDate=map.get();
  62. if(myThreadScopDate==null){
  63. myThreadScopDate=new MyThreadScopDate();
  64. map.set(myThreadScopDate);
  65. }
  66. return myThreadScopDate;
  67. }
  68.  
  69. private String name;
  70. private String age;
  71.  
  72. public String getName() {
  73. return name;
  74. }
  75. public void setName(String name) {
  76. this.name = name;
  77. }
  78. public String getAge() {
  79. return age;
  80. }
  81. public void setAge(String age) {
  82. this.age = age;
  83. }
  84.  
  85. }

并发库

在JDK5之后添加了线程的并发库,用于帮助我们操作线程,并发库中的东西大多都是java.util.concurrent 包下的,下来简单记录一下并发库中常见的类及其使用方法

Lock锁

这个Lock的作用和synchronized关键字作用一样,但是比关键字更加的面向对象,就是不能再用在方法名上。Lock是一个接口,需要使用其实现类,用法如下:

  1. class Inner{
  2. //实例化锁(Lock是一个接口,使用ReentrantLock实现类,
  3. //还有ReadLock读锁,对资源读时使用。writeLock写锁,写资源时使用)
  4. Lock lock=new ReentrantLock();
  5. public void output(String name){
  6. lock.lock(); //上锁
  7. try {
  8. int length=name.length();
  9. for(int i=0;i<length;i++){
  10. System.out.print(name.charAt(i));
  11. }
  12. System.out.println();
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }finally {
  16. lock.unlock(); //解锁
  17. }
  18.  
  19. }
  20.  
  21. }

Condition

使用Condition接口来完成线程之间的通信,实例化需要使用Lock对象的newCondition()方法。

需求:线程1循环10次,切换到线程2循环20次,再切换回主线程循环100次,如此往复10次

  1. import java.util.concurrent.locks.Condition;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4.  
  5. public class ThreadConditionCommuncation {
  6.  
  7. /**
  8. * 使用Condition进行线程通信方式实现
  9. * 线程1循环10次,切换到线程2循环20次,再切换回主线程循环100次,如此往复10次
  10. * @param args
  11. */
  12. public static void main(String[] args) {
  13.  
  14. Business business=new Business();
  15.  
  16. //子线程1
  17. new Thread(new Runnable() {
  18.  
  19. @Override
  20. public void run() {
  21. //往复10次
  22. for(int i=1;i<=10;i++){
  23. business.sub(i);
  24. }
  25. }
  26. }).start();
  27.  
  28. //子线程2
  29. new Thread(new Runnable() {
  30.  
  31. @Override
  32. public void run() {
  33. //往复10次
  34. for(int i=1;i<=10;i++){
  35. business.sub2(i);
  36. }
  37. }
  38. }).start();
  39.  
  40. //主线程往复10次
  41. for(int i=1;i<=10;i++){
  42. business.main(i);
  43. }
  44.  
  45. }
  46.  
  47. static class Business{
  48.  
  49. private Lock lock=new ReentrantLock();
  50.  
  51. //使用condition实现线程之间通信(实例化3个condition对应3个线程)
  52. Condition condition1=lock.newCondition();
  53.  
  54. Condition condition2=lock.newCondition();
  55.  
  56. Condition condition3=lock.newCondition();
  57.  
  58. private int tage=1; //线程1开始执行
  59.  
  60. public void sub(int i){
  61. lock.lock();
  62. try {
  63. //子线程循环10次后切换
  64. while(tage!=1){ //如果不是线程1等待
  65. try {
  66. //this.wait();
  67. condition1.await(); //使用Condition的await替代Object的wait
  68. } catch (InterruptedException e) {
  69. e.printStackTrace();
  70. }
  71. }
  72.  
  73. for(int j=1;j<=10;j++){
  74. System.out.println("子线程1第"+i+"趟的第"+j+"次循环");
  75. }
  76.  
  77. // tage=false;
  78. // this.notify();
  79.  
  80. tage=2; //标志为2
  81. condition2.signal(); //线程2唤醒
  82.  
  83. } catch (Exception e) {
  84. e.printStackTrace();
  85. }finally {
  86. lock.unlock();
  87. }
  88.  
  89. }
  90.  
  91. public void sub2(int i){
  92. lock.lock();
  93. try {
  94. //子线程2循环20次后切换
  95. while(tage!=2){
  96. try {
  97. //this.wait();
  98. condition2.await();
  99. } catch (InterruptedException e) {
  100. e.printStackTrace();
  101. }
  102. }
  103.  
  104. for(int j=1;j<=20;j++){
  105. System.out.println("子线程2第"+i+"趟的第"+j+"次循环");
  106. }
  107.  
  108. // tage=false;
  109. // this.notify();
  110.  
  111. tage=3;
  112. condition3.signal();
  113.  
  114. } catch (Exception e) {
  115. e.printStackTrace();
  116. }finally {
  117. lock.unlock();
  118. }
  119.  
  120. }
  121.  
  122. public void main(int i){
  123. lock.lock();
  124. try {
  125. //主线程循环100次后切换
  126. while(tage!=3){
  127. try {
  128. //this.wait();
  129. condition3.await();
  130. } catch (InterruptedException e) {
  131. e.printStackTrace();
  132. }
  133. }
  134.  
  135. for(int j=1;j<=100;j++){
  136. System.out.println("主线程第"+i+"趟的第"+j+"次循环");
  137. }
  138.  
  139. // tage=true;
  140. // this.notify();
  141.  
  142. tage=1;
  143. condition1.signal();
  144.  
  145. } catch (Exception e) {
  146. e.printStackTrace();
  147. }finally {
  148. lock.unlock();
  149. }
  150.  
  151. }
  152.  
  153. }
  154.  
  155. }

线程池

使用线程池将一些线程放在一个池里,使用就从池中取,用完再放回池中,我们可以一直给线程池任务,而不用太关心使用哪个线程来完成这些任务,因为线程池自己可以处理。线程池使用Executors类中的方法来实例化,有三种线程池的类型:

1.(FixedThreadPool)固定大小的线程池

2.(CachedThreadPool)带缓存的线程池(池中线程个数不一定,不够用时会自动创建)

3.(SingleThreadExecutor)单一线程池(保证线程池中只有一个线程,如果这个线程被销毁,则会在创建一个新的线程)

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3.  
  4. /**
  5. * JDK5中的线程池
  6. * @author LZ
  7. *
  8. */
  9. public class ThreadPoolTest {
  10.  
  11. public static void main(String[] args) {
  12. ExecutorService threadPool = Executors.newFixedThreadPool(3); //固定大小的线程池
  13. //ExecutorService threadPool =Executors.newCachedThreadPool(); //带缓存的线程池(池中线程个数不一定,需要则自动创建)
  14. //ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单一线程池(保证线程池中只有一个线程,如果这个线程被销毁,则会在创建一个新的线程)
  15. for(int i=1;i<=10;i++){ //10个任务
  16. final int tage=i;
  17. threadPool.execute(new Runnable() { //给线程池任务
  18. @Override
  19. public void run() {
  20. //每个任务要求线程循环10次
  21. for(int j=1;j<=10;j++){
  22. try {
  23. Thread.sleep(50);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println(Thread.currentThread().getName()+" is loopingt of "+j+" the task is "+tage);
  28. }
  29. //threadPool.shutdown(); //任务完成销毁线程池
  30. //threadPool.shutdownNow(); //立刻销毁线程池
  31. }
  32. });
  33. }
  34. }
  35.  
  36. }

我们使用线程池的 execute 方法来完成任务,这个方法没有返回结果,线程池还有一个可以返回结果的方法——submit方法

Callable和Future

我们在上面说线程池运行可以有个返回结果,使用的是submit方法,Callable就是这个方法需要的参数类型,需要重写Callable中的call方法;返回的结果被封装到Future中,使用Future的get方法可以取到结果:

  1. import java.util.concurrent.Callable;
  2. import java.util.concurrent.CompletionService;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.ExecutorCompletionService;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Future;
  8. import java.util.concurrent.TimeUnit;
  9.  
  10. public class CallableAndFuture {
  11.  
  12. public static void main(String[] args) {
  13. //线程池
  14. ExecutorService threadPool = Executors.newSingleThreadExecutor();
  15. //返回的结果封装到Future对象中
  16. Future<String> future = threadPool.submit(new Callable<String>() { //使用submit方法提交可以得到结果(不需要得到结果的可以使用execute方法)
  17.  
  18. @Override
  19. public String call() throws Exception {
  20. Thread.sleep(2000);
  21. return "Hello";
  22. }
  23. });
  24.  
  25. System.out.println("等待结果");
  26.  
  27. try {
  28. //使用Future对象的get方法得到返回值
  29. System.out.println("拿到结果:"+future.get());
  30. } catch (Exception e) {
  31. e.printStackTrace();
  32. }
  33.  
  34. }
  35.  
  36. }

当然我们还可以使用一组Callable任务,使用CompletionService来提交一组Callable任务:

  1. ExecutorService threadPool2 = Executors.newFixedThreadPool(10);
  2. //CompletionService用于提交一组Callable任务
  3. CompletionService<Integer> completionService=new ExecutorCompletionService<Integer>(threadPool2);
  4. for(int i=1;i<=10;i++){
  5. final int sta=i;
  6. completionService.submit(new Callable<Integer>() {
  7.  
  8. @Override
  9. public Integer call() throws Exception {
  10. return sta;
  11. }
  12. });
  13. }
  14.  
  15. for(int i=0;i<10;i++){
  16. try {
  17. //task方法用于返回已完成的第一个Callable任务的结果(就是Future),在使用Future的get方法得到值
  18. System.out.println(completionService.take().get());
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. }

可阻塞的队列

BlockingQueue接口用于实现一个可阻塞的队列,主要用于生产者-使用者队列。它的实现类有ArrayBlockingQueue,SynchronousQueue等等。它的put方法用于向队列中存数据,take方法用于取数据。

需求1:线程1循环10次,切换回主线程循环100次,如此往复10次

  1. import java.util.concurrent.ArrayBlockingQueue;
  2. import java.util.concurrent.BlockingQueue;
  3.  
  4. public class BlockingQueueCommuncation2 {
  5.  
  6. /**
  7. * 使用阻塞队列(BlockingQueue)实现
  8. * 线程1循环10次,切换回主线程循环100次,如此往复10次
  9. * @param args
  10. */
  11. public static void main(String[] args) {
  12.  
  13. Business business=new Business();
  14.  
  15. //子线程1
  16. new Thread(new Runnable() {
  17.  
  18. @Override
  19. public void run() {
  20. //往复10次
  21. for(int i=1;i<=10;i++){
  22. business.sub(i);
  23. }
  24. }
  25. }).start();
  26.  
  27. //主线程往复10次
  28. for(int i=1;i<=10;i++){
  29. business.main(i);
  30. }
  31.  
  32. }
  33.  
  34. static class Business{
  35.  
  36. //使用两个大小为1的阻塞队列
  37. BlockingQueue<Integer> queue1=new ArrayBlockingQueue<>(1);
  38. BlockingQueue<Integer> queue2=new ArrayBlockingQueue<>(1);
  39.  
  40. //下面是匿名构造块(优先于构造方法,实例化几个对象就执行几次)
  41. {
  42. try {
  43. queue2.put(1); //队列2放数据
  44. } catch (InterruptedException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48.  
  49. public void sub(int i){
  50. // lock.lock();
  51. // try {
  52. // //子线程循环10次后切换
  53. // while(tage!=1){ //如果不是线程1等待
  54. // try {
  55. // //this.wait();
  56. // } catch (InterruptedException e) {
  57. // e.printStackTrace();
  58. // }
  59. // }
  60.  
  61. try {
  62. queue1.put(1); //队列1放数据
  63. } catch (InterruptedException e) {
  64. e.printStackTrace();
  65. }
  66.  
  67. for(int j=1;j<=10;j++){
  68. System.out.println("子线程第"+i+"趟的第"+j+"次循环");
  69. }
  70.  
  71. try {
  72. queue2.take(); //队列2取数据
  73. } catch (InterruptedException e) {
  74. e.printStackTrace();
  75. }
  76.  
  77. // tage=false;
  78. // this.notify();
  79. //
  80. // } catch (Exception e) {
  81. // e.printStackTrace();
  82. // }finally {
  83. // lock.unlock();
  84. // }
  85.  
  86. }
  87.  
  88. public void main(int i){
  89. // lock.lock();
  90. // try {
  91. // //主线程循环100次后切换
  92. // while(tage!=3){
  93. // try {
  94. // //this.wait();
  95. // } catch (InterruptedException e) {
  96. // e.printStackTrace();
  97. // }
  98. // }
  99.  
  100. try {
  101. queue2.put(1); //队列2放数据
  102. } catch (InterruptedException e) {
  103. e.printStackTrace();
  104. }
  105.  
  106. for(int j=1;j<=100;j++){
  107. System.out.println("主线程第"+i+"趟的第"+j+"次循环");
  108. }
  109.  
  110. try {
  111. queue1.take(); //队列1取数据
  112. } catch (InterruptedException e) {
  113. e.printStackTrace();
  114. }
  115.  
  116. // tage=true;
  117. // this.notify();
  118.  
  119. //
  120. // } catch (Exception e) {
  121. // e.printStackTrace();
  122. // }finally {
  123. // lock.unlock();
  124. // }
  125.  
  126. }
  127.  
  128. }
  129.  
  130. }

需求2:现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象

  1. import java.util.concurrent.ArrayBlockingQueue;
  2. import java.util.concurrent.BlockingQueue;
  3.  
  4. /**
  5. * 现有的程序代码模拟产生了16个日志对象,
  6. * 并且需要运行16秒才能打印完这些日志,
  7. * 请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,
  8. * 程序只需要运行4秒即可打印完这些日志对象
  9. * @author LZ
  10. *
  11. */
  12. public class Test1 {
  13. public static void main(String[] args) {
  14.  
  15. //使用可阻塞的队列实现
  16. final BlockingQueue<String> queue=new ArrayBlockingQueue<String>(16);
  17.  
  18. for(int i=0;i<4;i++){ //开4个线程
  19. new Thread(new Runnable() {
  20.  
  21. @Override
  22. public void run() {
  23. while(true){ //使用while不断从队列中取日志
  24. try {
  25. String log = queue.take(); //从队列中取日志
  26. parseLog(log);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. }).start();
  33. }
  34.  
  35. System.out.println("begin: "+(System.currentTimeMillis()/1000));
  36. for(int i=0;i<16;i++){ //这行代码不能改动
  37. final String log=""+(i+1); //这行代码不能改动
  38. //Test1.parseLog(log);
  39. try {
  40. queue.put(log); //将日志放入队列
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45.  
  46. }
  47.  
  48. //parseLog方法内部的代码不能改动
  49. public static void parseLog(String log){
  50. System.out.println(log+" : "+(System.currentTimeMillis()/1000));
  51. try {
  52. Thread.sleep(1000);
  53. } catch (InterruptedException e) {
  54. e.printStackTrace();
  55. }
  56. }
  57. }

需求3:现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理,就好像生产者在不断地产生数据,消费者在不断消费数据。请将程序改造成有10个线程来消费生成者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有顺序的

  1. import java.util.concurrent.SynchronousQueue;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4.  
  5. /**
  6. * 现成程序中的Test类中的代码在不断地产生数据,
  7. * 然后交给TestDo.doSome()方法去处理,就好像生产者在不断地产生数据,消费者在不断消费数据。
  8. * 请将程序改造成有10个线程来消费生成者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,
  9. * 故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,
  10. * 只有上一个消费者消费完后,下一个消费者才能消费数据,
  11. * 下一个消费者是谁都可以,
  12. * 但要保证这些消费者线程拿到的数据是有顺序的
  13. * @author LZ
  14. *
  15. */
  16. public class Test2 {
  17. public static void main(String[] args) {
  18.  
  19. //使用同步库中同步队列
  20. SynchronousQueue<String> queue=new SynchronousQueue<>();
  21.  
  22. //使用Lock
  23. Lock lock=new ReentrantLock();
  24.  
  25. for(int i=0;i<10;i++){ //开10个线程
  26. new Thread(new Runnable() {
  27.  
  28. @Override
  29. public void run() {
  30. try {
  31. //将线程阻塞
  32. lock.lock();
  33. String input=queue.take();
  34. String output = TestDo.doSome(input);
  35. System.out.println(Thread.currentThread().getName()+ ":" + output);
  36. //释放
  37. lock.unlock();
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }).start();
  43. }
  44.  
  45. System.out.println("begin:"+(System.currentTimeMillis()/1000));
  46. for(int i=0;i<10;i++){ //这行不能改动
  47. String input = i+""; //这行不能改动
  48. // String output = TestDo.doSome(input);
  49. // System.out.println(Thread.currentThread().getName()+ ":" + output);
  50.  
  51. try {
  52. queue.put(input);
  53. } catch (InterruptedException e) {
  54. e.printStackTrace();
  55. }
  56.  
  57. }
  58. }
  59. }
  60.  
  61. //不能改动此TestDo类
  62. class TestDo {
  63. public static String doSome(String input){
  64.  
  65. try {
  66. Thread.sleep(1000);
  67. } catch (InterruptedException e) {
  68. e.printStackTrace();
  69. }
  70. String output = input + ":"+ (System.currentTimeMillis() / 1000);
  71. return output;
  72. }
  73. }

线程安全的集合

我们知道常用的ArrayList,HashSet,HashMap都是线程不安全的,JDK5的并发库中提供了实现线程安全的集合

如:CopyOnWriteArrayList等等。我们在使用Iterator或foreach遍历集合时不能对集合进行添加,删除,修改的操作,但是使用线程安全的集合就可以使用Iterator迭代器进行这些操作了。

ps :或者不使用线程安全的集合,使用ListIterator就可以对List集合在遍历时操作;又或者将线程安全的集合和Listiterator结合使用

并发库中的东西不止我罗列的这些,还有一些同步器,计数器等等的,就不在这叙述了

Java复习——多线程与并发库的更多相关文章

  1. java的多线程和并发库

    一.多线程基础知识 1.传统使用类Thread和接口Runnable实现 1)在Thread子类覆盖的run方法中编写运行代码 2)在传递给Thread对象的Runnable对象的run方法中编写代码 ...

  2. Java多线程与并发库高级应用-java5线程并发库

    java5 中的线程并发库 主要在java.util.concurrent包中 还有 java.util.concurrent.atomic子包和java.util.concurrent.lock子包 ...

  3. Java中的线程--并发库中的集合

    线程中的知识点基本都已经学完了,看看Java5并发库中提供的集合... 一.可堵塞队列 队列包含固定长度的队列和不固定长度的队列 ArrayBlockQueue中只有put()方法和take()方法才 ...

  4. java复习-多线程

    和线程之间的关系: 进程:进程是程序的一次动态执行过程,他经理了代码加载,执行到执行完毕的一个完整过程,这个过程也是进程本身从产生,发展到最终消亡的过程. 线程:线程是实现并发机制的一种有效手段,进程 ...

  5. 【Java多线程与并发库】4.传统线程同步通信技术

    我们先通过一道面试题来了解传统的线程同步通信. 题目:子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次,请写出程序. 我没有看答案, ...

  6. [Java复习] 多线程&并发 知识点补充

    0. wait/notify/notifyAll的理解? wait:让持有该对象锁的线程等待: notify: 唤醒任何一个持有该对象锁的线程: notifyAll: 唤醒所有持有该对象锁的线程: 它 ...

  7. Java多线程与并发库高级应用-同步集合

    ArrayBlockingQueue LinkedBlockingQueue 数组是连续的一片内存 链表是不连续的一片内存  传统方式下用Collections工具类提供的synchronizedCo ...

  8. Java多线程与并发库高级应用-工具类介绍

    java.util.concurrent.Lock 1.Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互 ...

  9. Java多线程与并发库高级应用-传统线程机制回顾

    1.传统线程机制的回顾 1.1创建线程的两种传统方式 在Thread子类覆盖的run方法中编写运行代码 // 1.使用子类,把代码放到子类的run()中运行 Thread thread = new T ...

随机推荐

  1. iOS 阶段学习第四天笔记(循环)

    iOS学习(C语言)知识点整理笔记 一.分支结构 1.分支结构分为单分支 即:if( ){ } ;多分支 即:if( ){ }else{ }  两种 2.单分支 if表达式成立则执行{ }里的语句:双 ...

  2. Amazon面试题

    亚马逊面试题: 如下所示的Map中,0代表海水,1代表岛屿,其中每一个岛屿与其八领域的区间的小岛能相连组成岛屿群.写代码,统计Map中岛屿个数. /* Q1. Map [ 0 0 0 0 0 0 0 ...

  3. postfix邮件服务器搭建01-准备篇

    本系列文章主要介绍linux下主流的开源邮件系统postfix的搭建过程,构建一个通过postfix虚拟用户管理的完整的邮件系统, 该系统包括以下组件: 邮件收发端postfix,dovecot, 邮 ...

  4. 2018秋C语言程序设计(初级)作业- 第3次作业

    7-1 找出最小值 #include<stdio.h> int main() { int min,i,n,count; scanf("%d",&n); for( ...

  5. ios 控制器的生命周期

    #pragma mark - 控制器生命周期 // 视图将要出现 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:anima ...

  6. 使用LinkedBlockingQueue来实现生产者消费者的例子

    工作中,经常有将文件中的数据导入数据库的表中,或者将数据库表中的记录保存到文件中.为了提高程序的处理速度,可以设置读线程和写线程,这些线程通过消息队列进行数据交互.本例就是使用了LinkedBlock ...

  7. win10/ubuntu双系统卸载删除ubuntu系统

    1.重启进入boot-设置windows启动项为首选项. 2.删除EFI中ubuntu引导启动项: a.将EFI分区挂载到M盘->(管理员权限)命令行输入:mountvol M: /s b.进入 ...

  8. BZOJ1208 HNOI2004 宠物收养所 【非旋转Treap】

    BZOJ1208 HNOI2004 宠物收养所 Description 最近,阿Q开了一间宠物收养所.收养所提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物.每个领养者都希望领养到自己满意的 ...

  9. .NET/C# 使用 Span 为字符串处理提升性能

    .NET Core 2.1 和 C# 7.2 带来了 Span 的原生支持,原本需要使用不安全代码操作的内存块现在可以使用安全的方式来完成.此前在性能和稳定性上需要有所取舍,而现在可以兼得了. 简单的 ...

  10. 呵呵sql

    INSERT  INTO fnd_document_folder_structure_t (folder_name,parent_folder_id,company_type_id,inv_flag, ...