java多线程-基础

1. 线程及进程

进程:进程是系统中正在运行的一个程序,程序一旦运行就是进程。
进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信。进程是系统进行资源分配和调度的一个独立单位。
线程:线程可以理解为进程中独立运行的子任务。

2. 多线程中常用方法

2.1 创建线程

通过继承Thread类:
继承Thread方法,重写Thread的run()方法

public class MyThread extends Thread{
 
	 @Override
	public void run() {
	    doSomething();
	}
 
	private void doSomething() {
            System.out.println("我是一个线程中的方法");
	}
}

public class NewThread {
	public static void main(String[] args) {
		MyThread myThread=new MyThread();
		myThread.start();//开启一个线程方法
	}
}

复制代码

Thread类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象run方法。如果调用myThread.run()就不是异步执行,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法。
实现Runable接口

public class RunnableThread implements Runnable{
 
	@Override
	public void run() {
		doSomeThing();
	}
	private void doSomeThing() {
		System.out.println("我是一个线程方法");
	}
}
复制代码

实现Runable接口类中没有start()方法,需要用Thread构造个方法开启线程。

public class NewThread {
	public static void main(String[] args) {
		Runnable runnable=new RunnableThread();
		Thread thread=new Thread(runnable);
		thread.start();//开启一个线程方法
	}
}
复制代码

实现Callable接口和Future创建线程
首先创建Callable接口的实现类CallableThread,实现call()方法,并且有返回值。Callable接口是一个带泛型的接口,泛型的类型就是线程返回值的类型。实现Callable接口中的call()方法,方法的返回类型与泛型的类型相同。

public class CallableThread implements Callable<String>{
 
	@Override
	public String call() throws Exception {
		doSomeThing();
		return "需要返回的值";
	}
 
	private void doSomeThing() {
		System.out.println("我是线程中的方法");
	}
}
复制代码

Callable不能直接获取返回值,需要用FutureTask在外部封装一下再获取返回值。

public class NewThread {
	public static void main(String[] args) {
		Callable<String> callable=new CallableThread();
		FutureTask<String> futureTask=new FutureTask<String>(callable);
		Thread thread=new Thread(futureTask);
		thread.start();//开启一个线程方法
	       //以下的方法可与上边的线程并发执行
		doSomething();
		try {
			futureTask.get();//获取线程返回值
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
	}
 
	private static void doSomething() {
	}
}
复制代码

三种创建方式的优缺点:

  1. 继承Thread显然有个很大的缺点,java是单继承了,如果继承了Thread那么就无法继承其他类。但是继承Thread编写简单,实现方便。
  2. 实现Runnable接口和Callable接口。大致一样,区别就是Callable接口的实现可以有返回值,且可以抛出显示异常。其余大致一样。 他们的优势是实现接口,那么实现类可以有其他父类,避免的Thread的问题,其次可以用一个实现了该接口的对象来创 建多个线程,从而方便一些基本的资源共享,因为是同一个对象。

2.2 currentThread()方法

currentThread()方法可返回代码段正在被那个线程调用的信息。

public class MyRun extends Thread {

    public MyRun(){
        System.out.println("构造 thread :" + Thread.currentThread().getName());
        System.out.println("构造 thread :" + this.getName());
    }
    
    @Override
    public void run() {
        System.out.println("Thread.currentThread().getName(): " + Thread.currentThread().getName());
        System.out.println("this.getname:"+ this.getName());
    }
}
复制代码
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyRun myRun = new MyRun();
        myRun.setName("aaaa");
        myRun.start();
    }
}
复制代码

运行结果

构造 thread :main
构造 thread :Thread-0
Thread.currentThread().getName(): aaaa
this.getnam:eaaaa
复制代码

2.3 isAlive()方法

isAlive()方法用于判断当前线程是否处于活动状态。活动状态时线程已经启动尚未终止。线程处于正在运行或者准备开始运行的状态就认为线程是存活的。

2.4 sleep()方法

sleep()方法是在指定的毫秒数内让当前“正在执行的线程”休眠(暂时执行)。这个正在执行的线程是指this.currentThread()返回的线程。

2.5 getId()方法

getId()方法可以取得线程的唯一标识。

2.6 判断线程是否停止

Thread.java类中提供了两种方法来判断线程是否停止。

  1. this.interrupted():测试当前线程是否已经中断,当前线程指运行this.interrupted()的线程。同是具有清除状态的功能,及如果连续两次调用该方法,则第二次调用将返回false。
  2. this.isInterrupted():测试线程是否已经中断。不具备清除状态功能。

2.7 yield()方法

yield()方法的作用是放弃当前的CPU资源,让给其他的任务去占用CPU执行的时间。但是放弃的时间不确定,有可能刚放弃,马上又获取了CPU的时间片。

2.8 停止线程

停止线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前操作。
有三种停止线程的方式:
停止一个线程可以使用Thread.stop()方法,但最好不要使用,虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且已经被废弃。 大多数停止线程的操作使用Thread.interrupt()方法,但这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
  3. 使用interrupt方法中断线程。

使用退出标志退出线程:
当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的。如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。

package chapter2;  
   
public class ThreadFlag extends Thread  {  
    public volatile boolean exit = false;  
   
    public void run(){  
        while (!exit){
             //do something
        };  
    }  
    public static void main(String[] args) throws Exception{  
        ThreadFlag thread = new ThreadFlag();  
        thread.start();  
        sleep(5000); // 主线程延迟5秒  
        thread.exit = true;  // 终止线程thread  
        thread.join();  
        System.out.println("线程退出!");  
    }  
}  
复制代码

使用stop方法终止线程:
使用stop方法可以强行终止正在运行或挂起的线程。我们可以使用如下的代码来终止线程:

thread.stop();  
复制代码

虽然使用上面的代码可以终止线程,但使用stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程。
使用interrupt方法终止线程:
使用interrupt方法来终端线程可分为两种情况:

  1. 线程处于阻塞状态,如使用了sleep,同步锁的wait,socket中的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出InterruptException异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后break跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用interrupt方法线程就会结束,实际上是错的, 一定要先捕获InterruptedException异常之后通过break来跳出循环,才能正常结束run方法。
public class ThreadSafe extends Thread {
    public void run() { 
        while (true){
            try{
                    Thread.sleep(5*1000);//阻塞5妙
                }catch(InterruptedException e){
                    e.printStackTrace();
                    break;//捕获到异常之后,执行break跳出循环。
                }
        }
    } 

复制代码
  1. 使用while(!isInterrupted()){……}来判断线程是否被中断。

public class ThreadSafe extends Thread {
    public void run() { 
        while (!isInterrupted()){
            //do something, but no throw InterruptedException
        }
    } 
}
复制代码

为什么要区分进入阻塞状态和和非阻塞状态两种情况了,是因为当阻塞状态时,如果有interrupt()发生,系统除了会抛出InterruptedException异常外,还会调用interrupted()函数,调用时能获取到中断状态是true的状态,调用完之后会复位中断状态为false,所以异常抛出之后通过isInterrupted()是获取不到中断状态是true的状态,从而不能退出循环,因此在线程未进入阻塞的代码段时是可以通过isInterrupted()来判断中断是否发生来控制循环,在进入阻塞状态后要通过捕获异常来退出循环。因此使用interrupt()来退出线程的最好的方式应该是两种情况都要考虑。

2.9 暂停线程

暂停线程意味着此线程还可以恢复运行,可以使用suspend()方法暂停线程,使用resume()方法恢复线程。

public class MyRun extends Thread {
    private long i = 0;
    public long getI(){
        return i;
    }
    public void setI(long i){
        this.i = i;
    }
    @Override
    public void run() {
        while (true){
            i++;
        }
    }
}
复制代码
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyRun myRun = new MyRun();
        myRun.start();
        Thread.sleep(3000);
        myRun.suspend();
        System.out.println("A= " + System.currentTimeMillis() + " i = " + myRun.getI());
//        myRun.resume();
        Thread.sleep(5000);
//        myRun.suspend();
        System.out.println("B= " + System.currentTimeMillis() + " i = " + myRun.getI());
    }
}
复制代码

suspend和resume方法可以是线程暂停和重启,但是如果使用不当,极易造成公共的同步对象的独占和因为线程暂停而导致的数据不同步的情况。

2.10 守护线程

在java线程中有两种线程,一种是用户线程,一种是守护线程。
守护线程是一种特殊得线程,它得特性有“陪伴”得含义,当进程中不存在非守护线程,则守护线程自动销毁。典型得守护线程就是垃圾回收线程。
可以使用setDaemon()设置线程为守护线程。不能把一个正在运行得线程设置为守护线程,所以,setDaemon()方法必须在start()方法前面。并且守护线程中产生得线程也是守护线程。

猜你喜欢

转载自juejin.im/post/5cdb848fe51d453ae1105440