Java并发总结(一):线程基础

来源:互联网 发布:java数组长度 编辑:程序博客网 时间:2024/09/21 09:01

最近在看并发,看到两篇blog,总结的很好,转过来学习下,以后如果有啥想法的,再在这两篇博客的基础之上做些补充和完善。。。


最近复习Java并发,写点东西总结总结。好记性不如烂博客。

并发

什么是并发?

与顺序编程不同,并发使得程序在同一时刻可以执行多个操作(宏观)。

为什么需要并发?

通常是为了提高程序的运行速度或者改善程序的设计。

线程

Java对并发编程提供了语言级别的支持。Java通过线程来实现并发编程。一个线程通常完成某个特定的任务,一个进程可以拥有多个线程,当这些线程一起执行的时候,就实现了并发。与操作系统中的进程相似,每个线程看起来好像拥有自己的CPU,但是其底层是通过切分CPU时间来实现的。与进程不同的是,线程并不是相互独立的,它们通常要相互合作来完成一些任务。

实现线程

你可以通过继承Thread类来实现自己的线程,关键的部分是,依靠重写run()方法来完成线程的工作。先写一个简单的线程,它的任务就是在控制台中依次打印出0,1,2,3,4。

12345678910111213
public class SimpleThread extends Thread{  @Override  public void run() {      for(int i = 0; i < 5 ;i++) {          System.out.println(i);          try {              sleep(20); //让线程每打印一个数字之后休息20毫秒          } catch (InterruptedException e) {              System.out.println("Throw InterruptedException!");          }      }  }}

之后,我们启动两个这样的线程,让它们分别完成自己的任务。

12345678
public class ThreadTest {  public static void main(String[] args) {      SimpleThread simpleThread1 = new SimpleThread();      SimpleThread simpleThread2 = new SimpleThread();      simpleThread1.start();      simpleThread2.start();  }}

执行结果如下:

0 0 1 1 2 2 3 3 4 4

结果似乎并不是像单线程程序那样,分先后两次分别打印出0,1,2,3,4,而是两个线程似乎同时在执行一样,都分别打印出自己的0,1,2,3,4,这便是“并行”。在调用一个Thread对象的start()方法之后,JVM就会启动一个新的线程。

直接继承Thread类并不是一个好的方法。因为有时候我们自己的线程类可能需要继承别的类,而Java并不支持多重继承。这个时候Runnable接口就有了用处(实际上Thread类本身也实现了Runnable接口)。我们要做的就是实现Runnable接口,并且重写run()方法。最后将一个实现了Runnable接口的类的实例交给Thread类的构造函数,就可以实例化出一个可用的线程了。

1234567891011
class MyTask implements Runnable {        @override        public void run() {               //需要进行的操作        }}//像下面这样来使用MyTask mt = new MyTask();Thread t = new Thread(mt);t.start();

实际上,在很多时候,我们可以将一个线程看做一个任务,这个线程存在的价值就是为了完成某个任务。

休眠

正如第一个例子那样,我们可以让一个线程暂时休息一会儿。Thread类有一个sleep静态方法,你可以将一个long类型的数据当做参数传进去,单位是毫秒,表示线程将会休眠的时间。第一个例子中之所以调用了sleep方法,是因为打印5个数字的时间太短,如果不休眠的话可能看不到“并发”的效果,因为CPU的时间片还来不及转换,线程的任务就已经完成了。

让步

Thread类还有一个名为yield()的静态方法。这个方法的作用是为了建议当前正在运行的线程做个让步,让出CPU时间给别的线程来运行。程序中可能会有一个线程在某个时刻已经完成了一大部分的任务,并且这个时候让别的线程来运行比较合理。这样的情况下,就可以调用yield()方法进行让步。不过,调用这个方法并不能保证一定会起作用,毕竟它只是建议性的。所以,不应该用这个方法来控制程序的执行流程。

串入(join)

当一个线程t1在另一个线程t2上调用t1.join()方法的时候,线程t2将等待线程t1运行结束之后再开始运行。正如下面这个例子:

1234567891011121314151617181920212223242526272829303132333435
public class ThreadTest {  public static void main(String[] args) {      SimpleThread simpleThread = new SimpleThread();      Thread t = new Thread(simpleThread);      t.start();  }}public class SimpleThread implements Runnable{  @Override  public void run() {      Thread tempThread = new Thread() {                              @Override                              public void run() {                                  for(int i = 10; i < 15 ;i++) {                                      System.out.println(i);                                  }                              }                          };            tempThread.start();            try {          tempThread.join();        //tempThread串入      } catch (InterruptedException e) {          e.printStackTrace();      }            for(int i = 0; i < 5; i++) {          System.out.println(i);      }  }}

输出结果为:

101112131401234

优先级

我们可以给一个线程设定一个优先级。线程调度器在做调度工作的时候,优先级越高的线程越可能得到先运行的机会。Thread类的setPriority方法和getPriority方法分别用来设置线程的优先级和获取线程的优先级。由于线程调度器根据优先级的大小来调度线程的效果在各种不同的JVM上差别很大,所以在绝大多数情况下,我们不应该依靠设定优先级来完成我们的工作,保持默认的优先级是一条很好的建议。

守护线程(deamon thread)

通常,程序中有一些线程的工作并不是不可或缺的,它只是用来协助其他线程来工作。这样的线程叫做守护线程或者后台线程。当进程中的所有非守护线程结束时,守护线程也就终止了,就算它还没有完全完成自己的任务。我们可以在一个线程启动之前调用setDaemon()方法来将这个线程设定成守护线程。

转自:http://blog.psjay.com/posts/summary-of-java-concurrency-one-thread-fundation/