Thinking in Java--Java多线程学习笔记(1)
2015-07-13 11:45
706 查看
这几天开始学习java多线程并发编程的内容了,以前也学习过多线程的知识,但是总是觉得学的不是很清楚;希望这一次学习《java编程思想》能让自己对并发,多线程的概念有一个更加深入的了解。这一章估计要写好几篇博客了,这篇博客是对于基础的一个总结,主要内容是对启动一个线程的几种方式和对线程一些操作函数的总结。
首先来了解一下多线程的概念,多线程看起来同一时刻在同时运行多个任务,但是从操作系统的层面来讲只是让多个任务以极快的速度进行切换而已,一个时刻实际上还是只有一个任务在cpu上运行的。Java中的多线程靠的是Thread类实现的,我们将任务交给Thread类的对象,然后它就会以多线程的方式来运行我们的任务。Thread类中有一个run()函数,这个函数体中放我们需要执行的任务。
我们可以有三种方式来启动一个线程。第一是直接继承Thread类,实现里面的run()方法;这种方法最简单,但是这样就不能再继承别的类了。第二种方法是实现Runnable接口,我们也需要实现里面的run()方法;但是现在它还是没有任何的线程能力的,要实现线程行为,你必须显示的将一个任务附在线程上实现,具体来说就是将一个Runnable对象传给一个Thread对象。第三种方法是实现Callable接口,这个接口的内容和Runnable接口并没有什么不同,不同点在于Callable接口是可以有返回值的,我们可以显式的捕获这个返回值。
然后在上面的代码中我们看到了一个Executor对象,这里解释下:Executor(执行器)是从Java5开始提供的一个管理Thread对象的类,Executor对象在客户端和任务执行之间提供了一个间阶层;与客户端直接执行任务不同,任务由这个中介对象执行。Executor允许你管理异步任务的执行,而无需显示的管理线程的生命周期。具体的步骤是创建一个ExecutorService对象,然后将线程放入这个对象中去运行,对应的ExecutorService对象三种:第一是前面用的CachedThreadPool,会在程序中创建与所需数量相同的线程。第二是FixedThreadPool,可以一次性创建指定数目的线程(通过在构造器中指定数目实现)。第三种是SingleThreadExecutor,这种执行器每次只能执行一个线程;如果向其提交了多个任务,那么这些任务将排队,依次执行。
下面简要的介绍一下几种控制线程的方法(函数)
(1).join()方法
join()方法意味着让一个线程等待另一个线程。如果我在现在的线程上调用t.join(),那么当前线程就会阻塞,直到t线程完成为止;join()有一个重载的方法可以指定阻塞的时间。
(2).setDaemon(true)方法
这个方法意味着将当前线程设为后台线程。后台线程是在后台运行的,它的任务是为其它的线程提供服务(如JVM的垃圾回收机制就是典型的后台线程)。后台线程的特点在于如果所有的前台线程都死亡了,那么后台线程就会自动死亡。
(3).线程睡眠sleep()
sleep()的功能是很简单的,只是简单的让当前正在执行的线程一段时间(我们可以指定这段时间),并进入阻塞状态。sleep()是Thread类的一个静态方法。
(4).线程让步yield()
yield()和sleep()有点类似,也是让当前正在执行的线程暂停,但是它不会阻塞当前线程,而是让其进入就绪状态。然后在进行一次调度,调取一个优先级相同或更高的线程进行执行。指的注意的是yield()只是一个建议性的方法,它并不能完全保证当前线程让步,所以我们应该更多的使用sleep()而不是yield()。
(5).setPriority()改变线程的优先级
一般来说优先级越高的线程其获得执行的机会就越大,但这是依赖于具体的操作系统的;而且不同的操作系统的优先级的级别划分也是不同的。所以Java提供了三个静态常量来表示优先级:MAX_PRIORITY 表示最高优先级。MIN_PRIORITY表示最低优先级。NORM_PRIORITY表示一般的优先级;如果我们要设定线程的优先级,我们应该尽量选择这三个常量,而不是其它的数字。
首先来了解一下多线程的概念,多线程看起来同一时刻在同时运行多个任务,但是从操作系统的层面来讲只是让多个任务以极快的速度进行切换而已,一个时刻实际上还是只有一个任务在cpu上运行的。Java中的多线程靠的是Thread类实现的,我们将任务交给Thread类的对象,然后它就会以多线程的方式来运行我们的任务。Thread类中有一个run()函数,这个函数体中放我们需要执行的任务。
我们可以有三种方式来启动一个线程。第一是直接继承Thread类,实现里面的run()方法;这种方法最简单,但是这样就不能再继承别的类了。第二种方法是实现Runnable接口,我们也需要实现里面的run()方法;但是现在它还是没有任何的线程能力的,要实现线程行为,你必须显示的将一个任务附在线程上实现,具体来说就是将一个Runnable对象传给一个Thread对象。第三种方法是实现Callable接口,这个接口的内容和Runnable接口并没有什么不同,不同点在于Callable接口是可以有返回值的,我们可以显式的捕获这个返回值。
///通过继承Thread实现一个线程类 public class FirstThread extends Thread{ private int i; public void run(){ for(int i=0;i<10;i++){ ///getName()是Thread类的方法,因为继承了Thread类 ///所以可以直接调用 System.out.println(getName()+" "+i); } } public static void main(String[] args){ for(int i=0;i<10;i++){ //一般情况可以通过Thread.currentThread取得当前的线程 System.out.println(Thread.currentThread().getName()+" "+i); if(i==1){ ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new FirstThread()); //添加一个新任务 exec.shutdown(); ///防止后面再有新任务被添加进来 } } }/*Output main 0 main 1 main 2 Thread-0 0 Thread-0 1 Thread-0 2 Thread-0 3 Thread-0 4 Thread-0 5 Thread-0 6 Thread-0 7 Thread-0 8 Thread-0 9 main 3 main 4 main 5 main 6 main 7 main 8 main 9 */ }
package lkl; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //通过实现Runnable接口来实现一个线程类 public class SecondThread implements Runnable{ private int i; ///同样要实现run()方法 public void run(){ for(int i=0; i<10 ;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args){ for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); if(i==1){ ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new Thread(new SecondThread(),"新线程1")); exec.shutdown(); } }/* main 0 main 1 pool-1-thread-1 0 pool-1-thread-1 1 pool-1-thread-1 2 pool-1-thread-1 3 pool-1-thread-1 4 pool-1-thread-1 5 pool-1-thread-1 6 pool-1-thread-1 7 pool-1-thread-1 8 pool-1-thread-1 9 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 */ } }
package lkl; //通过继承Callable接口来实现线程类 //Callable接口和FutureTask接口接合使用 //我们使用FutureTask对Callable进行封装,然后 //就可以通过FutureTask对象获得线程的返回值, ///在这种情况下,也可以捕获线程抛出的异常。 //另外注意:这两个类都是有泛型限制的,具体的类型和call()方法的返回值一样 import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; public class ThirdThread implements Callable<Integer>{ private int i; ///实现call()方法,作为线程执行体 public Integer call(){ for(i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } return i; } public static void main(String[] args){ ThirdThread rt = new ThirdThread(); //使用FutureTask来包装Callable对象 FutureTask<Integer> task = new FutureTask(rt); for(int i=0; i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); if(i==1){ ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(task); exec.shutdown(); } } try{ //获取线程返回值,如果线程还没执行完,则会阻塞直到取得返回值 System.out.println("线程的返回值:"+task.get()); }catch(Exception ex){ ex.printStackTrace(); } }/* main 0 main 1 pool-1-thread-1 0 pool-1-thread-1 1 pool-1-thread-1 2 pool-1-thread-1 3 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 pool-1-thread-1 4 pool-1-thread-1 5 pool-1-thread-1 6 pool-1-thread-1 7 pool-1-thread-1 8 pool-1-thread-1 9 线程的返回值:10 */ }
然后在上面的代码中我们看到了一个Executor对象,这里解释下:Executor(执行器)是从Java5开始提供的一个管理Thread对象的类,Executor对象在客户端和任务执行之间提供了一个间阶层;与客户端直接执行任务不同,任务由这个中介对象执行。Executor允许你管理异步任务的执行,而无需显示的管理线程的生命周期。具体的步骤是创建一个ExecutorService对象,然后将线程放入这个对象中去运行,对应的ExecutorService对象三种:第一是前面用的CachedThreadPool,会在程序中创建与所需数量相同的线程。第二是FixedThreadPool,可以一次性创建指定数目的线程(通过在构造器中指定数目实现)。第三种是SingleThreadExecutor,这种执行器每次只能执行一个线程;如果向其提交了多个任务,那么这些任务将排队,依次执行。
下面简要的介绍一下几种控制线程的方法(函数)
(1).join()方法
join()方法意味着让一个线程等待另一个线程。如果我在现在的线程上调用t.join(),那么当前线程就会阻塞,直到t线程完成为止;join()有一个重载的方法可以指定阻塞的时间。
package lkl; ///join()方法的使用 public class JoinThread extends Thread{ public JoinThread(String name){ super(name); } public void run(){ for(int i=0;i<10;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) throws Exception{ for(int i=0; i<10;i++){ if(i==1){ JoinThread jt = new JoinThread("被Join的线程"); jt.start(); //main线程调用了jt线程的Join()方法,则main线程 //只有等jt结束后,main线程才会继续进行 jt.join(); } System.out.println(Thread.currentThread().getName()); } } }
(2).setDaemon(true)方法
这个方法意味着将当前线程设为后台线程。后台线程是在后台运行的,它的任务是为其它的线程提供服务(如JVM的垃圾回收机制就是典型的后台线程)。后台线程的特点在于如果所有的前台线程都死亡了,那么后台线程就会自动死亡。
package lkl; public class DaemonThread extends Thread{ public void run(){ for(int i=0;i<10;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) throws Exception{ DaemonThread dt = new DaemonThread(); dt.setDaemon(true); //设为后台线程,需要在启动之前设置 dt.start(); for(int i=0; i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } //可以看到在main线程结束自后,Daemon线程也结束了 } }
package lkl; import java.util.concurrent.*; //后台线程会在不执行finally子句的前提下 //就会终止其run()方法 class Adaemon implements Runnable{ public void run(){ try{ System.out.println("Starting ADamon"); TimeUnit.SECONDS.sleep(1); } catch(InterruptedException e){ System.out.println("err"); } finally{ System.out.println("This should always run?"); } } } public class DaemonsDontRunFinally { public static void main(String[] args){ Thread t = new Thread(new Adaemon()); t.setDaemon(true); t.start(); } }
(3).线程睡眠sleep()
sleep()的功能是很简单的,只是简单的让当前正在执行的线程一段时间(我们可以指定这段时间),并进入阻塞状态。sleep()是Thread类的一个静态方法。
(4).线程让步yield()
yield()和sleep()有点类似,也是让当前正在执行的线程暂停,但是它不会阻塞当前线程,而是让其进入就绪状态。然后在进行一次调度,调取一个优先级相同或更高的线程进行执行。指的注意的是yield()只是一个建议性的方法,它并不能完全保证当前线程让步,所以我们应该更多的使用sleep()而不是yield()。
(5).setPriority()改变线程的优先级
一般来说优先级越高的线程其获得执行的机会就越大,但这是依赖于具体的操作系统的;而且不同的操作系统的优先级的级别划分也是不同的。所以Java提供了三个静态常量来表示优先级:MAX_PRIORITY 表示最高优先级。MIN_PRIORITY表示最低优先级。NORM_PRIORITY表示一般的优先级;如果我们要设定线程的优先级,我们应该尽量选择这三个常量,而不是其它的数字。
相关文章推荐
- JAVAEE学习
- 脱离eclipse运行用例
- 解决eclipse+git中每次clean项目需要重新commit文件
- java js 文件图片通过FTP进行上传和同步
- java学习之旅59--模拟ArrayList容器的底层实现_JDK源码分析ArrayList
- Myeclipse莫名出错的问题
- java初学1
- netbeans 将项目打包生成单个可执行的 jar
- 表达式解析器 IKExpression
- Java--对象内存布局
- #笔记#圣思园 JavaWeb 第32讲——Servlet多线程同步问题,Cookie
- 常用的eclipse快捷键-也适合sts
- java 多重继承
- [LeetCode][Java] Combination Sum
- Java基础知识总结之多线程
- jdk 5并发包中CopyOnWrite类的用法
- java单例模式
- Java中数据相互转换
- Java之list
- 转!!为什么要java环境变量配置?