Java之多线程(一)

一、进程与线程

1.进程:操作系统中一个程序的执行周期称为一个进程。

       线程:一个程序同时执行多个任务。通常,每一个任务就称为一个线程。与进程相比,线程更“轻量级”,创建、撤销一个线程比启动一个新进程开销要小的多。没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。

      多进程与多线程的区别:每个进程拥有自己的一整套变量,而线程则共享数据。共享变量使得线程之间的通信比进程之间更有效、更方便。
      多进程应用:一个浏览器应用可以同时下载多个图片、音乐;一个Web服务器需要同时处理多个并发的请求。
2.线程状态
  

二、Java多线程实现
1.继承Thread类实现多线程
      java.lang.Thread是一个线程操作的核心类。新建一个线程最简单的方法就是直接继承Thread类,而后覆写该类中的run()方法

      如:定义线程的主体类

class MyThread extends Thread{
	private String title;
	public MyThread(String title){
		this.title = title;
	}
	@Override
	public void run(){
		for(int i = 0;i < 10;i++){
			System.out.println(this.title + ",i = " + i);
		}
	}
}

      如:观察调用run()方法

public class Test {
	public static void main(String[] args) {
		MyThread myThread1 = new MyThread("thread1");
		MyThread myThread2 = new MyThread("thread2");
		MyThread myThread3 = new MyThread("thread3");
		myThread1.run();
		myThread2.run();
		myThread3.run();
	}
}
       这时只是做了一个顺序打印,和多线程一点关系都没有。正确启动多线程的方式是调用Thread类中的start()方法。

       如:正确启动多线程

public class Test {
	public static void main(String[] args) {
		MyThread myThread1 = new MyThread("thread1");
		MyThread myThread2 = new MyThread("thread2");
		MyThread myThread3 = new MyThread("thread3");
		myThread1.start();
		myThread2.start();
		myThread3.start();
	}
}
2.Runnable()接口实现多线程
      Thread类的核心功能是进行线程的启动。如果一个类为了实现多线程直接去继承Thread类就会有单继承局限。在Java中又提供了另一种实现模式:Runnable接口。

      如:利用Runnable接口实现线程主体类

class MyThread implements Runnable{	//线程主体类
	private String title;
	public MyThread(String title) {
		this.title = title;
	}
	@Override
	public void run() {	//所有线程从此处开始执行
		for(int i = 0;i < 10;i++) {
			System.out.println(this.title + ",i = " + i);
		}
	}
}
      此时MyThread类继承的不再是Thread类而实现了Runnable接口,虽然解决了单继承局限的问题,但是没有start()方法被继承了,此时就需要用Thread类提供的构造方法。

      Thread类提供的构造方法:

  public Thread(Runnable target)
      可以接收Runnable接口对象

      如:启用多线程

public class Test{
	public static void main(String[] args) {
		MyThread myThread1 = new MyThread("thread1");
		MyThread myThread2 = new MyThread("thread2");
		MyThread myThread3 = new MyThread("thread3");
		new Thread(myThread1).start();
		new Thread(myThread2).start();
		new Thread(myThread3).start();
	}
}
  
      这个时候就启动了多线程,多线程的启动永远都是Thread类中的start()方法。
      Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义。

      如:使用匿名内部类进行Runnable对象创建

public class Test{
	public static void main(String[] args) {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println("hello");
				
			}
		}).start();
	}
}

      如:使用Lambda表达式进行Runnable对象创建

public class Test{
	public static void main(String[] args) {
		Runnable runnable = () -> System.out.println("hello");
		new Thread(runnable).start();
	}
}
3.Thread与Runnable的区别
         从使用形式来讲,使用Runnable实现多线程要比继承Thread类要好,因为可以避免单继承局限。
  线程类的继承结构
  
      除以上关系外,使用Runnable还有一个特点:使用Runnable实现的多线程的程序类可以更好地描述出程序共享的概念。

       如:使用Thread实现数据共享

class MyThread extends Thread{
	private int ticket = 10;
	@Override
	public void run() {
		while(this.ticket > 0) {
			System.out.println("剩余票数:" + this.ticket--);
		}
	}
}
public class Test{
	public static void main(String[] args) {
		new MyThread().start();
		new MyThread().start();
		new MyThread().start();
	}
}
       此时启动三个线程实现卖票处理,结果变为了卖各自的票。

       如:使用Runnable实现共享

class MyThread implements Runnable{
	private int ticket = 10;
	@Override
	public void run() {
		while(this.ticket > 0) {
			System.out.println("剩余票数 " + this.ticket--);
		}
	}
}
public class Test{
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		new Thread(myThread).start();;
		new Thread(myThread).start();;
		new Thread(myThread).start();;
	}
}
       Runnable实现的多线程的程序类可以更好地描述出程序共享的概念
4.Callable实现多线程
       Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是利用Callable来实现多线程,可以让线程执行后带来一些返回结果。

       如:使用Callable定义线程主体类

class MyThread implements Callable<String>{
	private int ticket = 10;
	@Override
	public String call() throws Exception {
		while(this.ticket > 0) {
			System.out.println("剩余票数:" + this.ticket--);
		}
		return "票卖完了";
	}
}

       Callable实现多线程:


       如:启动并取得多线程的执行结果

public class Test{
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		FutureTask<String> task = new FutureTask<>(new MyThread());
		new Thread(task).start();
		new Thread(task).start();
		System.out.println(task.get());
	}
}
三、多线程的常用操作方法
1.线程的命名与取得

       在Thread类中提供有如下的线程名称方法:


       如:观察线程名称的取得

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i = 0;i < 10;i++) {
			System.out.println("当前线程:" + Thread.currentThread().getName() + ",i = " + i);
		}
	}
}
public class Test{
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		new Thread(myThread).start();	//没有设置名字
		new Thread(myThread).start();	//没有设置名字
		new Thread(myThread,"A").start();	//有设置名字
	}
}
       通过以上发现,如果没有设置线程名字,则会自动分配一个线程名字。需要注意的是,线程名字如果要设置避免重复,同时中间不要修改。

       如:观察线程的执行结果

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i = 0;i < 10;i++) {
			System.out.println("当前线程:" + Thread.currentThread().getName() + ",i = " + i);
		}
	}
}
public class Test{
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		myThread.run();  //直接通过对象调用run()方法
		new Thread(myThread).start();	//通过线程调用
	}
}
       通过以上程序我们发现,主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的。
       实际上,每当使用java命令去解释程序的时候,都表示启动了一个JVM进程。而主方法只是这个进程上的一个线程而已。

       线程的一张图:


2.线程休眠(sleep方法)
       线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。
       线程休眠会交出CPU,让CPU去执行其他的任务。但是sleep方法不会释放锁,也就是说如果当线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
       sleep方法:

 public static native void sleep(long millis) throws InterruptedException
       休眠时间使用毫秒做单位。

       如:处理休眠操作

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i = 0;i < 10;i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("当前线程:" + Thread.currentThread().getName() + ",i = " + i);
		}
	}
}
public class Test{
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		new Thread(myThread).start();
		new Thread(myThread).start();
		new Thread(myThread).start();
	}
}
       通过观察结果我们会以为这三个线程是同时休眠的,但实际上不是的,所有的代码是依次进入到run()方法中的。
3.线程让步(yield()方法)
       yield()方法:暂停当前正在执行的线程对象,并执行其他线程。
       意思就是调用yield方法会让当前线程交出CPU权限,让CPU去执行其它的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
       注意:调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间。

       如:观察yield()方法

class MyThread implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 0;i < 10;i++) {
			Thread.yield();
			System.out.println("当前线程:" + Thread.currentThread().getName() + ",i = " + i);
		}
	}
}
public class Test{
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		new Thread(myThread).start();
		new Thread(myThread).start();
		new Thread(myThread).start();
	}
}
4.join()方法
       join()方法:等待该线程终止。意思就是如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run方法先执行完毕之后在开始执行主线程。

       如:观察join()方法

class MyThread implements Runnable{
	public void run() {
		for(int i = 0;i < 10;i++) {
			System.out.println("当前线程" + Thread.currentThread().getName() + ",i = " + i);
		}
	}
}
public class Test{
	public static void main(String[] args) throws InterruptedException {
		MyThread myThread1 = new MyThread();
		MyThread myThread2 = new MyThread();
		Thread A = new Thread(myThread1,"A");
		Thread B = new Thread(myThread2,"B");
		B.start();
		B.join();	//等B线程执行完毕后再执行A线程
		A.start();
		
	}
}
5.线程停止
       多线程有三种方式可以停止线程
       (1)设置标记位,可以使线程正常退出。
       (2)使用stop方法强制使线程退出,但是该方法不太安全。
       (3)使用Thread类中的interrupt()可以中断线程

       如:设置标记位使线程退出

class MyThread implements Runnable{
	private boolean flag = true;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		int i = 1;
		while(flag) {
			try {
				Thread.sleep(1000);
				System.out.println("第" + i + "次执行,线程名称为:" + Thread.currentThread().getName());
				i++;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}
public class Test{
	public static void main(String[] args) throws InterruptedException {
		MyThread myThread = new MyThread();
		Thread thread = new Thread(myThread,"A");
		thread.start();
		Thread.sleep(5000);
		myThread.setFlag(false);
		System.out.println("代码结束");
	}
}

       如:使用stop方法使线程退出

class MyThread implements Runnable{
	@Override
	public void run() {
		int i = 0;
		while(true) {
			try {
				Thread.sleep(1000);
				System.out.println("第" + i + "次执行,线程名称为:" + Thread.currentThread().getName());
				i++;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
public class Test{
	public static void main(String[] args) throws InterruptedException {
		MyThread myThread = new MyThread();
		Thread thread = new Thread(myThread,"A");
		thread.start();
		Thread.sleep(5000);
		thread.stop();
		System.out.println("代码结束");
	}
}
       使用stop()方法强制使线程退出,但是该方法不太安全所以已经被废弃了。之所以不安全,是因为stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void{x = 1;y = 2;} 由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 1;时,被调用了stop()方法,即使在同步块中,它也会马上stop了,这样就产生了不完整的数据。

       如:使用Thread.interrupt()

class MyThread implements Runnable{
	private boolean flag = true;
	@Override
	public void run() {
		int i = 0;
		while(flag) {
			try {
				Thread.sleep(1000);
				boolean bool = Thread.currentThread().isInterrupted();
				if(bool) {
					System.out.println("非阻塞情况下执行该操作,线程状态:" + bool);
					break;
				}
				System.out.println("第" + i + "次执行,线程名称为:" + Thread.currentThread().getName());
				i++;
			} catch (InterruptedException e) {
				System.out.println("退出了");
				boolean bool = Thread.currentThread().isInterrupted();
				System.out.println(bool);
				return;
			}
		}
	}
	
}
public class Test{
	public static void main(String[] args) throws InterruptedException {
		MyThread myThread = new MyThread();
		Thread thread = new Thread(myThread,"A");
		thread.start();
		Thread.sleep(3000);
		thread.interrupt();
		System.out.println("代码结束");
	}
}
       interrupt()方法只是改变中断状态而已,它不会中断一个正在运行的进程。这一方法的实际完成是,给受阻塞的线程发出一个中断信号,这样受阻线程就得已退出阻塞的状态。然而interrupt()方法并不会立即执行中断操作;具体而言,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么将中断标志设置为true后,还会有如下的操作之一:
       1>如果是wait、sleep以及join三个方法引起的阻塞,那么会将线程的中断标志重新置为false,并抛出一个InterruptedException;
       2>如果在中断时线程处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理。例如:一个线程在运行状态中,其中断标志被设置为true之后,一单线程调用了wait、join、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志会被程序自动清除,重新设置为false。
       总结:调用线程类的interrupt方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。
6.线程优先级
       线程优先级:线程的优先级越高越有可能先执行,但仅仅是有可能而已。
       在Thread类中提供有如下的优先级方法:

       ·设置优先级

  public final void setPriority(int newPriority)

       ·取得优先级

  public final int getPriority()
       对于优先级设置的内容可以通过Thread类的几个常量来决定:
       (1)最高优先级:public final int MAX_PRORITY = 10;
       (2)中等优先级:public final int NORM_PRORITY = 5;
       (3)最低优先级:public final int MIN_PRORITY = 1;

       如:设置优先级

class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i = 0;i < 5;i++) {
			System.out.println("当前线程:" + Thread.currentThread().getName() + ",i = " + i);
		}
	}
}
public class Test{
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		Thread thread1 = new Thread(myThread,"A");
		Thread thread2 = new Thread(myThread,"B");
		Thread thread3 = new Thread(myThread,"C");
		thread1.setPriority(Thread.MIN_PRIORITY);
		thread2.setPriority(Thread.NORM_PRIORITY);
		thread3.setPriority(Thread.MAX_PRIORITY);
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

       如:主线程的优先级(是中等优先级)

public class Test{
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getPriority());
	}
}

      如:线程具有继承性(比如在A线程中启动B线程,那么B和A的优先级将是一样)

class A implements Runnable{
	@Override
	public void run() {
		System.out.println("A的优先级为:" + Thread.currentThread().getPriority());
		Thread thread = new Thread(new B());
		thread.start();
	}
}
class B implements Runnable{
	@Override
	public void run() {
		System.out.println("B的优先级为:" + Thread.currentThread().getPriority());
	}
}
public class Test{
	public static void main(String[] args) {
		Thread thread = new Thread(new A());
		thread.setPriority(Thread.MIN_PRIORITY);
		thread.start();
	}
}
7.守护线程
       java中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是用户线程;否则就是守护线程。典型的守护线程就是垃圾回收线程。只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后一个非守护线程结束时,守护线程才会随着JVM一同停止工作。
       注意:主线程main是用户线程

       如:观察守护线程

class A implements Runnable{
	private int i;
	@Override
	public void run() {
		try {
			while(true) {
				i++;
				System.out.println("线程名称:" + Thread.currentThread().getName() + ",i = " + i + ",是否为守护线程:" + Thread.currentThread().isDaemon());
				Thread.sleep(1000);
			}
		}catch(InterruptedException e){
			System.out.println("线程名称:" + Thread.currentThread().getName() + "中断线程了");
		}
	}
}
public class Test{
	public static void main(String[] args) throws InterruptedException {
		Thread thread1 = new Thread(new A(),"A");
		thread1.setDaemon(true);  //设置A为守护线程
		thread1.start();
		Thread thread2 = new Thread(new A(),"B");
		thread2.start();
		Thread.sleep(3000);
		thread2.interrupt();
		Thread.sleep(5000);
		System.out.println("代码结束");
	}
}
      可以看出,B是用户线程当它中断了之后守护线程还没有结束,是因为主线程(用户线程)还没有结束,所以说明是所有的用户线程结束之后守护线程才会结束。


猜你喜欢

转载自blog.csdn.net/wzh_123_123/article/details/80182518