Java多线程(一) 线程基础

目录

  • 1.线程和进程
  • 2.线程的创建方式
  • 3.Java线程的调度
  • 4.线程优先级
  • 5.线程状态
  • 6.线程控制
  • 7.wait和sleep的区别

1.线程和进程

进程是程序的一个运行实例;而线程则是CPU调度的基本单位。当前大部分的操作系统都支持多任务运行,这一特性让用户感到计算机可以同时处理很多事件。显然在只有一个CPU核心的情况下,这种“同时”是一种假象。它是操作系统采用分时的方法,为正在运行的多个任务分配合理的、单独的CPU时间片来实现的。假如当前系统中有五个任务,如果采用“平均分配”法,且时间片为10ms,则每个任务40ms才能执行一次,如果计算机的运行速度足够快的话,用户的感觉就是所有任务在同时运行。

2.线程的创建方式

在Java语言中所有调用了start()方法且还未执行结束的Thread类实例就代表了一个线程。Thread也是实现了Runable接口的“可执行代码”,Thread的创建方式有三种。

方式一:继承Thread的类

public class ThreadTest{	
   public static void main(String[] arg){
       ThreadDemo thread=new ThreadDemo();
       //调用start()创建线程
       //创建线程后并不是立即执行多线程的代码,什么时候运行由操作系统决定
	   thread.start();
   }
   
   //定义Thread的子类并重写run()方法,run()方法的方法体就是线程要完成的任务
    public class ThreadDemo extends Thread{
        @Override
        public void run() {
            System.out.print("create a thred");
        }
    }
}

方式二:实现Runable接口

public class ThreadTest{	
   public static void main(String[] arg){
       Thread thread = new Thread(new ThreadDemo());
        thread.start();
   }
   
   //定义类并实现Runable接口,run()方法的方法体就是线程要完成的任务
    public class ThreadDemo implements Runnable {
        @Override
        public void run() {
            System.out.print("create a thred");
        }
    }
}

方式三:实现Callable接口

Callable接口是属于Executor框架中的功能类,它可以在任务结束后提供一个返回值,并且能够抛出异常,Runable接口不具备这样的能力。

public class ThreadTest{	
   public static void main(String[] arg){
       ThreadDemo demo = new ThreadDemo();
       //创建一个线程池
        ExecutorService service = Executors.newSingleThreadExecutor();
        //提交任务,获取到Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法
        Future future = service.submit(demo);
        try {
           //调用get()方法获取运行结果,在调用get()方法的时候线程就会阻塞,直到call()方法执行结束
            System.out.print(future.get());
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
   }
   
   //定义类并实现Callable接口,call()方法的方法体就是线程要完成的任务
     public class ThreadDemo implements Callable {
        @Override
        public Object call() throws Exception {
            return "create a thread";
        }
    }
}

线程是一次性用品,当run方法执行完毕后就废弃,等着GC进行回收。因此线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException异常,这是一种运行时异常。多次调用start方法被认为是编程错误。

3.Java线程的调度

线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度和抢占式线程调度。

3.1 协同式线程调度

线程的执行时间由线程本身来控制,线程把自己的工作执行完成以后,要主动通知系统切换到另一个线程上。

优点:实现简单,线程的切换操作对线程本身来说是可知的,所以没有什么线程同步的问题
缺点:线程的执行时间不可控制,如果一个线程有问题一直不告诉系统进行线程切换,就会一直阻塞在那里。

3.2 抢占式线程调度

每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。

优点:线程的执行时间系统是可控的,也不会有一个线程导致整个进程阻塞的问题。Java使用的线程调度方式就是抢占式调度方式。

4.线程优先级

虽然Java线程调度是系统自动完成的,但是我们还是可以让系统给某些线程多分配一点执行时间,另外一些则可以少分配一点。这项操作可以通过线程的优先级来完成。当两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。

public class ThreadTest{	
   public static void main(String[] arg){
       Thread thread1 = new Thread(new ThreadDemo());
       Thread thread2 = new Thread(new ThreadDemo());
       //设置优先级,优先级越高的线程越容易被系统选择执行
       thread1.setPriority(7); 
       thread2.setPriority(1);
       thread1.start();
       thread2.start();
   }
   
   //定义类并实现Runable接口,run()方法的方法体就是线程要完成的任务
    public class ThreadDemo implements Runnable {
        @Override
        public void run() {
            System.out.print("create a thred");
        }
    }
}

5.线程状态

在Java语言中对于线程定义了6种状态,在任意的一个时间点,一个线程只能有且只有一种状态。

  • 新建(New):创建后尚未启动的线程处于这种状态
  • 运行(Runable):Runable包括了操作系统线程状态中的Running和Ready,也就是处于这个状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间。
  • 无限期等待(Waiting):处于这种状态的线程不会分配CPU执行时间,他们要等待被其他线程显式的唤醒
  • 限期等待(Timed Waiting):处于这种状态的线程也不会被分配CPU时间,不过无需等待其他线程的显式唤醒,在一定时间之后它们会由系统自动唤醒。
  • 阻塞(Blocked):线程被阻塞了,线程阻塞和线程等待的区别是:阻塞状态时在等待一个排他锁,这个事件将会在其他线程放弃这个锁的时候发生;而等待状态是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
  • 结束状态(Terminated):已终止线程的线程状态,线程已经结束执行。

以上六种状态的转换,如下图所示:
Java多线程(一) 线程基础_第1张图片

6.线程控制

6.1 wait和notify/notifyAll

wait函数的原型如下:

public final void wait(long millis)
public final native void wait(long millis, int nanos)
public final native void wait()

可以看到调用wait方法的时候可以传递Timeout参数,因此wait函数的使用可以分为两种情况

  • 设置了Timeout参数:是有条件限制的等待,当超出设定时间时仍未被唤醒会被认为出错,直接返回。这种做法的目的是防止程序出错的情况下出现“死等待”情况。
  • 没有设置Timeout参数:无限期等待,直到有人来唤醒他。

和其他接口方法不同,这三个函数是由Object类定义的,也就意味着它们是所有类的共有函数。当某个线程调用一个Object的wait()方法以后,系统就要在这个Object中记录这个请求,因为调用者很可能不止一个,所以可以使用列表的形式逐个添加它们。当后期的唤醒条件满足时,Object既可以使用notify来唤醒列表中的一个等待线程,也可以使用notifyAll来唤醒列表中所有的线程。
值得注意的是调用者只有成为Object的monitor后,才能调用它的wait方法,而成为一个对象的monitor有以下3中途径

  • 执行这个object的synchronized方法
  • 执行一段synchronized代码块,并且是基于这个object做的同步。
  • 如果object是Class类,可以执行它的synchronized static方法。
6.2 interrupted

interrupt方法可以用来请求中断线程,当一个线程调用interrupted方法时,线程的中断标识位会被置位,线程会不时的检测这个中断标识位,以判断线程是否应该被中断。如果线程正在被阻塞在某个Object的wait上,或者join()、sleep()方法中,那么线程会被唤醒,中断状态会被清除,同时抛出InterruptedException异常。

6.3 join

join方法有如下几个原型

public final void join()
public final void join(long millis, int nanos)
public final void join(long millis)

如下代码所示:

Thread thread1 = new Thread(runable1);
Thread thread2 = new Thread(runable2);

thread1.start();
thread1.join();
thread2.start();

以上的代码能够达到的目的是:只有当thread1线程执行完成以后,才会执行后面的thread2.start()。这样就保证了两个线程的顺序执行。如果是带有参数的join()则多了一个限制,即假如在规定时间内thread1线程没有执行完成,也会执行thread2.start(),以防线程无限等待从而拖垮程序。

6.4 sleep

这个方法是项目中使用最广泛的。wait是等待某个Object,而sleep则是等待时间,一旦设置的时间到了就会被系统自动唤醒。

public static void sleep(long millis)
public static void sleep(long millis, int nanos)
6.5 yield

暂停当前线程,让出线程占用CPU执行时间。这个暂停时间是极其短暂的。如果在这时间内其他线程仍然没有抢占到CPU的执行时间,则当前的线程会重新抢占CPU的执行时间。

6.6 守护线程

有的时候应用中需要一个长期驻留的服务程序,但是不希望其影响应用退出,就可以设置为守护线程,如果JVM发现只有守护线程存在的时候,将结束进程。

Thread thread=new Thread(test);
//必须在start()方法之前设置
thread.setDaemon(true);
thread.start();

7. wait和sleep的区别

  1. sleep方法没有释放锁,而wait方法释放了锁
  2. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
  3. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
  4. sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行时间给其他线程,但是监控状态依然保持,到时后会自动唤醒。
  5. wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,只有针对此对象调用notify/notifyAll方法后本线程会被唤醒。

你可能感兴趣的:(Java多线程)