Java高并发编程一--什么是线程

1.什么是线程

    在讲解java高并发时必需要先聊聊线程,那么什么是线程呢?下面是网上的答案:java

  • 1.线程:进程中负责程序执行的执行单元
  •              线程自己依靠程序进行运行
  •              线程是程序中的顺序控制流,只能使用分配给程序的资源和环境
  • 2.进程:执行中的程序
  •              一个进程至少包含一个线程
  • 3.单线程:程序中只存在一个线程,实际上主方法就是一个主线程
  • 4.多线程:在一个程序中运行多个任务
  •                 目的是更好地使用CPU资源

我这里通俗一点讲其实就是:一个程序里头不一样的执行路径,能够放在不一样的cpu里面同步运行git

2.如何启动一个线程

2.1继承Thread

//在java.lang包中定义, 继承Thread类必须重写run()方法
class MyThread extends Thread{
    private static int num = 0;
 
    public MyThread(){
        num++;
    }
 
    @Override
    public void run() {
        System.out.println("主动建立的第"+num+"个线程");
    }
}
/*建立好了本身的线程类以后,就能够建立线程对象了,而后经过start()方法去启动线程。
注意,不是调用run()方法启动线程,run方法中只是定义须要执行的任务,若是调用run方法,
即至关于在主线程中执行run方法,跟普通的方法调用没有任何区别,
此时并不会建立一个新的线程来执行定义的任务。*/
public class Test {
    public static void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyThread thread1 = new MyThread("thread1");
        thread1.start();
        MyThread thread2 = new MyThread("thread2");
        thread2.run();
    }
}
 
class MyThread extends Thread{
    private String name;
 
    public MyThread(String name){
        this.name = name;
    }
 
    @Override
    public void run() {
        System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
    }

2.2实现Runnable接口

//在Java中建立线程除了继承Thread类以外,还能够经过实现Runnable接口来实现相似的功能。
//实现Runnable接口必须重写其run方法。
//下面是一个例子:
public class Test {
    public static void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
} 
class MyRunnable implements Runnable{
    public MyRunnable() {
    }
 
    @Override
    public void run() {
        System.out.println("子线程ID:"+Thread.currentThread().getId());
    }
}
//Runnable的中文意思是“任务”,顾名思义,经过实现Runnable接口,咱们定义了一个子任务,
//而后将子任务交由Thread去执行。注意,这种方式必须将Runnable做为Thread类的参数,
//而后经过Thread的start方法来建立一个新线程来执行该子任务。若是调用Runnable的run方法的话,
//是不会建立新线程的,这根普通的方法调用没有任何区别。

//事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。

//在Java中,这2种方式均可以用来建立线程去执行子任务,具体选择哪种方式要看本身的需求。
//直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,可是因为Java只容许单继承,
//因此若是自定义类须要继承其余类,则只能选择实现Runnable接口。

2.3使用ExecutorService、Callable、Future实现有返回结果的多线程

多线程后续会讲到,这里暂时先知道一下有这种方法便可。编程

/**
* 有返回值的线程 
*/ 
@SuppressWarnings("unchecked")  
public class Test {  
public static void main(String[] args) throws ExecutionException,  
    InterruptedException {  
   System.out.println("----程序开始运行----");  
   Date date1 = new Date();  
 
   int taskSize = 5;  
   // 建立一个线程池  
   ExecutorService pool = Executors.newFixedThreadPool(taskSize);  
   // 建立多个有返回值的任务  
   List<Future> list = new ArrayList<Future>();  
   for (int i = 0; i < taskSize; i++) {  
    Callable c = new MyCallable(i + " ");  
    // 执行任务并获取Future对象  
    Future f = pool.submit(c);  
    // System.out.println(">>>" + f.get().toString());  
    list.add(f);  
   }  
   // 关闭线程池  
   pool.shutdown();  
 
   // 获取全部并发任务的运行结果  
   for (Future f : list) {  
    // 从Future对象上获取任务的返回值,并输出到控制台  
    System.out.println(">>>" + f.get().toString());  
   }  
 
   Date date2 = new Date();  
   System.out.println("----程序结束运行----,程序运行时间【" 
     + (date2.getTime() - date1.getTime()) + "毫秒】");  
}  
}  
 
class MyCallable implements Callable<Object> {  
private String taskNum;  
 
MyCallable(String taskNum) {  
   this.taskNum = taskNum;  
}  
 
public Object call() throws Exception {  
   System.out.println(">>>" + taskNum + "任务启动");  
   Date dateTmp1 = new Date();  
   Thread.sleep(1000);  
   Date dateTmp2 = new Date();  
   long time = dateTmp2.getTime() - dateTmp1.getTime();  
   System.out.println(">>>" + taskNum + "任务终止");  
   return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";  
}
}
/*代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
建立固定数目线程的线程池。

public static ExecutorService newCachedThreadPool()
建立一个可缓存的线程池,调用execute 将重用之前构造的线程(若是线程可用)。
若是现有线程没有可用的,则建立一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

public static ExecutorService newSingleThreadExecutor()
建立一个单线程化的Executor。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
建立一个支持定时及周期性的任务执行的线程池,多数状况下可用来替代Timer类。

ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,
返回Future。若是Executor后台线程池尚未完成Callable的计算,
这调用返回Future对象的get()方法,会阻塞直到计算完成。*/

3.线程的状态

  • 建立(new)状态: 准备好了一个多线程的对象
  • 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
  • 运行(running)状态: 执行run()方法
  • 阻塞(blocked)状态: 暂时中止执行, 可能将资源交给其它线程使用
  • 终止(dead)状态: 线程销毁

当须要新起一个线程来执行某个子任务时,就建立了一个线程。可是线程建立以后,不会当即进入就绪状态,由于线程的运行须要一些条件(好比内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,因此须要为线程分配必定的内存空间),只有线程运行须要的全部条件知足了,才进入就绪状态。  当线程进入就绪状态后,不表明马上就能获取CPU执行时间,也许此时CPU正在执行其余的事情,所以它要等待。当获得CPU执行时间以后,线程便真正进入运行状态。  线程在运行状态过程当中,可能有多个缘由致使当前线程不继续运行下去,好比用户主动让线程睡眠(睡眠必定的时间以后再从新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待必定的事件)、waiting(等待被唤醒)、blocked(阻塞)。  当因为忽然中断或者子任务执行完毕,线程就会被消亡。缓存

在有些教程上将blocked、waiting、time waiting统称为阻塞状态,这个也是能够的,只不过这里我想将线程的状态和Java中的方法调用联系起来,因此将waiting和time waiting两个状态分离出来。安全

注:sleep和wait的区别:多线程

  • sleepThread类的方法,waitObject类中定义的方法.
  • Thread.sleep不会致使锁行为的改变, 若是当前线程是拥有锁的, 那么Thread.sleep不会让线程释放锁.
  • Thread.sleepObject.wait都会暂停当前的线程. OS会将执行时间分配给其它线程. 区别是, 调用wait后, 须要别的线程执行notify/notifyAll才可以从新得到CPU执行时间.

4.上下文切换

对于单核CPU来讲(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程当中转去运行另一个线程,这个叫作线程上下文切换(对于进程也是相似)。并发

因为可能当前线程的任务并无执行完毕,因此在切换时须要保存线程的运行状态,以便下次从新切换回来时可以继续切换以前的状态运行。举个简单的例子:好比一个线程A正在读取一个文件的内容,正读到文件的一半,此时须要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,咱们不但愿线程A又从文件的开头来读取。ide

所以须要记录线程A的运行状态,那么会记录哪些数据呢?由于下次恢复时须要知道在这以前当前线程已经执行到哪条指令了,因此须要记录程序计数器的值,另外好比说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候须要知道以前挂起时变量的值时多少,所以须要记录CPU寄存器的状态。因此通常来讲,线程上下文切换过程当中会记录程序计数器、CPU寄存器状态等数据。高并发

说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行可以从中断点恢复执行测试

虽然多线程可使得任务执行的效率获得提高,可是因为在线程切换时一样会带来必定的开销代价,而且多个线程会致使系统资源占用的增长,因此在进行多线程编程时要注意这些因素。

5.线程的经常使用方法

编号 方法 说明
1 public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public void run() 若是该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;不然,该方法不执行任何操做并返回。
3 public final void setName(String name) 改变线程名称,使之与参数 name 相同。
4 public final void setPriority(int priority) 更改线程的优先级。
5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
7 public void interrupt() 中断线程。
8 public final boolean isAlive() 测试线程是否处于活动状态。
9 public static void yield() 暂停当前正在执行的线程对象,并执行其余线程。
10 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操做受到系统计时器和调度程序精度和准确性的影响。
11 public static Thread currentThread() 返回对当前正在执行的线程对象的引用。

6.中止线程

中止线程是在多线程开发时很重要的技术点,掌握此技术能够对线程的中止进行有效的处理。
中止一个线程可使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有如下3种方法能够终止正在运行的线程:

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止线程,可是不推荐使用这个方法,由于stop和suspend及resume同样,都是做废过时的方法,使用他们可能产生不可预料的结果。
  • 使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还须要加入一个判断才能够完成线程的中止。
  • 暂停线程

    interrupt()方法

7.线程的优先级

在操做系统中,线程能够划分优先级,优先级较高的线程获得的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮“线程规划器”肯定在下一次选择哪个线程来优先执行。
设置线程的优先级使用setPriority()方法,此方法在JDK的源码以下:

public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

//在Java中,线程的优先级分为1~10这10个等级,若是小于1或大于10,
//则JDK抛出异常throw new IllegalArgumentException()。
//JDK中使用3个常量来预置定义优先级的值,代码以下:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
/*线程优先级特性:

继承性
好比A线程启动B线程,则B线程的优先级与A是同样的。
规则性
高优先级的线程老是大部分先执行完,但不表明高优先级线程所有先执行完。
随机性
优先级较高的线程不必定每一次都先执行完。
*/

8.守护线程

在Java线程中有两种线程,一种是User Thread(用户线程),另外一种是Daemon Thread(守护线程)。
Daemon的做用是为其余线程的运行提供服务,好比说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来讲去没啥区别的,惟一的区别之处就在虚拟机的离开:若是User Thread所有撤离,那么Daemon Thread也就没啥线程好服务的了,因此虚拟机也就退出了。

守护线程并不是虚拟机内部能够提供,用户也能够自行的设定守护线程,方法:public final void setDaemon(boolean on) ;可是有几点须要注意:

  • thread.setDaemon(true)必须在thread.start()以前设置,不然会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是建立后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;因此说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
  • 在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程再也不是守护进程,尽管它把父进程的进程相关信息复制过去了,可是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,而后文件0,1,2都是/dev/null,当前目录到/”)
  • 不是全部的应用均可以分配给Daemon线程来进行服务,好比读写操做或者计算逻辑。由于在Daemon Thread还没来的及进行操做时,虚拟机可能已经退出了

码云地址:https://gitee.com/zhangzeli/java-concurrent