Java基础总结篇---多线程实现

这篇文章只是对java多线程的应用级泛泛而谈,其实还是挺难的,就那几个状态转换就可以把你安排上了。实则要研究还是有挺多学问的我有一本《Java并发编程实战》至今还没有打开第一章。

在说多线程直接首先需要明确一点:同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。

多线程


假如一个程序有多条执行流程,那么,该程序就是多线程程序。

多线程有什么意义呢?

  • 多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率
  • 程序的执行其实都是在抢CPU的资源,CPU的执行权
  • 多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
  • 我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性

但是:如果线程过多

CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低(线程的执行效率降低)

由此引出多线程的缺点:开启线程需要占用一定的内存空间,如果开启大量的线程,会占用大量的内存空间,降低程序的性能。线程越多,CPU在调度线程上的开销就越大。程序设计更加复杂:比如线程之间的通信、多线程的数据共享。

线程和进程有什么区别?

线程是依赖于进程存在的

Java基础总结篇---多线程实现

  • 进程:

    • 正在运行的程序,是系统进行资源分配和调用的独立单位。
    • 每一个进程都有它自己的内存空间和系统资源。

Java基础总结篇---多线程实现

  • 线程:

    • 是进程中的单个顺序控制流,是一条执行路径
    • 一个进程如果只有一条执行路径,则称为单线程程序。
    • 一个进程如果有多条执行路径,则称为多线程程序。

并行和并发

  • 前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。简单来说就是多个线程在某一时间段内同时运行。
  • 后者是物理上同时发生,指在某一个时间点同时运行多个程序。简单来说就是同一时间访问同一服务器。在大数据应用里面高并发是常常需要考虑的一个问题。

其次,你需要明确1个线程中任务的执行是串行的。如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务。

Java基础总结篇---多线程实现

Java程序运行原理


Java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

Jvm虚拟机的启动是单线程的还是多线程?

  • Jvm虚拟机的启动是多线程的。
    • 原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。
    • 现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。

进入正文----

如何实现多线程


public class Thread extends Object implements Runnable 线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。常用 实现Runnable接口方法实现多线程。

方式一:继承Thread类

自定义类继承Thread类

在自定义类里面重写run方法

创建对象

启动线程

代码例子:

	public class MyThread extends Thread {
	
		@Override
		public void run() {
			for (int x = 0; x < 200; x++) {
				System.out.println(x);
			}
		}
	
	}

	public class MyThreadDemo {
		public static void main(String[] args) {
		

			// MyThread my = new MyThread();
			// my.start();
			//  IllegalThreadStateException:非法的线程状态异常
			//  为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。我都启动一次了你还启动的话就报错。
			// my.start();
	
			// 创建两个线程对象
			MyThread my1 = new MyThread();
			MyThread my2 = new MyThread();
	
			my1.start();
			my2.start();
		}
	}

方式二:实现Runnable接口

自定义类MyRunnable实现Runnable接口

重写run()方法

创建MyRunnable类的对象

创建Thread类的对象,并把C步骤的对象作为构造参数传递

Java基础总结篇---多线程实现

代码例子:

	public class MyRunnable implements Runnable {
	
		@Override
		public void run() {
			for (int x = 0; x < 100; x++) {
				// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
				System.out.println(Thread.currentThread().getName() + ":" + x);
			}
		}
	
	}

	public class MyRunnableDemo {
		public static void main(String[] args) {
			// 创建MyRunnable类的对象
			MyRunnable my = new MyRunnable();
	
			// 创建Thread类的对象,并把C步骤的对象作为构造参数传递,参数要的是接口实质上要的是实现类对象
			// Thread(Runnable target)
			// Thread t1 = new Thread(my);
			// Thread t2 = new Thread(my);
			// t1.setName("林青霞");
			// t2.setName("刘意");
	
			// Thread(Runnable target, String name)
			Thread t1 = new Thread(my, "zjl");
			Thread t2 = new Thread(my, "zyf");
	
			t1.start();
			t2.start();
		}
	}

实现接口方式的好处

  • A:可以避免由于Java单继承带来的局限性。
  • B:适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想,说白了你只创建了一个实现类对象,可以让多个线程去共享一个资源。
		public class SellTicket implements Runnable {
		// 定义100张票
		private int tickets = 100;
		   //多个窗口售卖,实现一个门票资源共享
			@Override
			public void run() {
				while (true) {
					if (tickets > 0) {
						System.out.println(Thread.currentThread().getName() + "正在出售第"
								+ (tickets--) + "张票");
					}
				}
			}
		    }
		
		public class SellTicketDemo {
		public static void main(String[] args) {
			// 创建资源对象
			SellTicket st = new SellTicket();
	
			// 创建三个线程对象
			Thread t1 = new Thread(st, "窗口1");
			Thread t2 = new Thread(st, "窗口2");
			Thread t3 = new Thread(st, "窗口3");
	
			// 启动线程
			t1.start();
			t2.start();
			t3.start();
		}
	}

Thread类的基本获取和设置方法

  • public final String getName()
		public class MyThread extends Thread {
	
		public MyThread() {
		}
		
		public MyThread(String name){
			super(name);
		}
	
		@Override
		public void run() {
			for (int x = 0; x < 100; x++) {
				System.out.println(getName() + ":" + x);
			}
		}
	}
  • public final void setName(String name)
    其实通过构造方法也可以给线程起名字,当然我觉得最方便的还是用set方法设置名字。
	  public class MyThreadDemo {
		public static void main(String[] args) {
			// 创建线程对象
			//无参构造+setXxx()
			// MyThread my1 = new MyThread();
			// MyThread my2 = new MyThread();
			// //调用方法设置名称
			// my1.setName("林青霞");
			// my2.setName("刘意");
			// my1.start();
			// my2.start();
			
			//带参构造方法给线程起名字
			// MyThread my1 = new MyThread("zjl");
			// MyThread my2 = new MyThread("zrf");
			// my1.start();
			// my2.start();			
		}
	}

如何获取main方法所在的线程名称呢?

  • public static Thread currentThread()
		//我要获取main方法所在的线程对象的名称,该怎么办呢?
		System.out.println(Thread.currentThread().getName());

这样就可以获取任意方法所在的线程名称。

问题一:为什么需要重写run方法?

不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。

问题二:调用run()方法为什么是单线程的呢?

因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果。

如何实现线程调度?


假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

Java使用的是抢占式调度模型。 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

如何设置和获取线程优先级

  • public final int getPriority()

    默认优先级的值是5

  • public final void setPriority(int newPriority)

    参数范围 1-10

如何实现线程控制?


  • 线程休眠

    public static void sleep(long millis)

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

	public class ThreadSleep extends Thread {
		@Override
		public void run() {
			for (int x = 0; x < 100; x++) {
				System.out.println(getName() + ":" + x + ",日期:" + new Date());
				// 睡眠
				// 困了,我稍微休息1秒钟
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
  • 线程加入

    public final void join()

等待该线程终止。

	public class ThreadJoin extends Thread {
		@Override
		public void run() {
			for (int x = 0; x < 100; x++) {
				System.out.println(getName() + ":" + x);
			}
		}
	}
	public class ThreadJoinDemo {
		public static void main(String[] args) {
			ThreadJoin tj1 = new ThreadJoin();
			ThreadJoin tj2 = new ThreadJoin();
			ThreadJoin tj3 = new ThreadJoin();
	
			tj1.setName("李渊");
			tj2.setName("李世民");
			tj3.setName("李元霸");
	
			tj1.start();
			try {
				tj1.join();    // 等待该线程终止后其他线程开始抢占才能执行,保证线程一可以最先执行完毕
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			tj2.start();
			tj3.start();
		}
	}

  • 线程礼让
    public static void yield()

暂停当前正在执行的线程对象,并执行其他线程。让多个线程的执行更和谐,近似于一人执行一次,但是不能靠它保证一人一次。

	public class ThreadYield extends Thread {
		@Override
		public void run() {
			for (int x = 0; x < 100; x++) {
				System.out.println(getName() + ":" + x);
				Thread.yield();
			}
		}
	}

	public class ThreadYieldDemo {
		public static void main(String[] args) {
			ThreadYield ty1 = new ThreadYield();
			ThreadYield ty2 = new ThreadYield();
	
			ty1.setName("zjl");
			ty2.setName("zyf");
	
			ty1.start();
			ty2.start();
		}
	}

  • 后台线程
    public final void setDaemon(boolean on)

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。

守护线程图解

Java基础总结篇---多线程实现
代码例子:

	public class ThreadDaemon extends Thread {
		@Override
		public void run() {
			for (int x = 0; x < 100; x++) {
				System.out.println(getName() + ":" + x);
			}
		}
	}
	
	//这程序里面,刘备作为主线程,当刘备死了以后,此时正在运行的线程中只有他的两个守护线程张飞关羽,所以Java 虚拟机退出,这两个线程挂掉。
	public class ThreadDaemonDemo {
		public static void main(String[] args) {
			ThreadDaemon td1 = new ThreadDaemon();
			ThreadDaemon td2 = new ThreadDaemon();
	
			td1.setName("关羽");
			td2.setName("张飞");
	
			// 设置守护线程
			td1.setDaemon(true);
			td2.setDaemon(true);
	
			td1.start();
			td2.start();
	
			Thread.currentThread().setName("刘备");  //设置主线程名字为刘备
			for (int x = 0; x < 5; x++) {
				System.out.println(Thread.currentThread().getName() + ":" + x);
			}
		}
	}
  • 中断线程
    public final void stop()

让线程停止,过时了,但是还可以使用。只是不太安全,太暴力了。

public void interrupt():InterruptedException

中断线程。 把线程的状态终止,并抛出一个异常,一定会走catch

	public class ThreadStop extends Thread {
		@Override
		public void run() {
			System.out.println("开始执行:" + new Date());
	
			// 我要休息10秒钟,亲,不要打扰我哦
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				// e.printStackTrace();
				System.out.println("线程被终止了");
			}
	
			System.out.println("结束执行:" + new Date());
		}
	}

	public class ThreadStopDemo {
		public static void main(String[] args) {
			ThreadStop ts = new ThreadStop();
			ts.start();
	
			// 你超过三秒不醒过来,我就干死你
			try {
				Thread.sleep(3000);
				// ts.stop();
				ts.interrupt();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

线程的生命周期


线程的生命周期和进程的生命周期高度一致。由此也可以引出进程的生命周期。

Java基础总结篇---多线程实现

注意:运行的时候可能回到就绪状态,因为被别的线程抢到执行权,其次运行的线程被阻塞后不能直接回到运行状态,他回到的是就绪状态,此时再和其他线程进行一个CPU执行权的抢夺。带run方法执行结束后进入死亡状态,等待GC回收。

线程安全问题


前面买票那个例子就存在典型的线程安全问题,卖同一张票,卖负票。

首先想为什么出现安全问题?
  • 是否是多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据
在所如何解决多线程安全问题呢?

基本思想:让程序没有安全问题的环境。
那么怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可—> synchronized

同步代码块格式

同步可以解决安全问题的根本原因就在那个锁对象上。该对象如同锁的功能。当线程开始执行同步代码块前,必须先获得对同步代码块的锁定。并且任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。

	 synchronized(对象)
	{
	 需要同步的代码;
	}
	
//常见的三种写法,实质上同步代码块才是核心
public class Demo {
	//就是同步代码块,同步锁对象是类的class对象(Demo类里的静态方法锁对象就是Demo.class)
    public static synchronized void fun1(){
    }
    //就是同步代码块,同步锁对象是this
    public synchronized void fun2(){
    }
    public static void main(String args[]) throws Exception{
    //就是锁住这个对象,表示这个对象正在为我服务,其他人不能用.通常推荐使用可能被并发访问的共享资源充当同步监视器。
        synchronized(xxx) {
            
        }
    }
}

三种同步类型
代码例子:

	public class SellTicket implements Runnable {
		// 定义100张票
		private int tickets = 100;
		//创建锁对象,得是同一个锁对象,这个对象我们可以认为是任意一个对象,同步方法锁对象是 this
		private Object obj = new Object();
	
		@Override
		public void run() {
			while (true) {
				synchronized (obj) {
					if (tickets > 0) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "正在出售第" + (tickets--) + "张票");
					}
				}
			}
		}
	}

	// 同步方法
	private static synchronized void sellTicket() {
			if (tickets > 0) {
			try {
					Thread.sleep(100);
			} catch (InterruptedException e) {
					e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()
						+ "正在出售第" + (tickets--) + "张票 ");
			}
		}
同步的前提
  • 程序中需要多个线程
  • 多个线程使用的是同一个锁对象
同步的好处
  • 同步的出现解决了多线程的安全问题。
同步的弊端
  • 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

是否线程安全


前面的文章介绍过有些类是线程不安全的,那么我们如何判断呢?简单明了看源码,看有没有synchronized关键字…

	public class ThreadDemo {
		public static void main(String[] args) {
			// 线程安全的类
			StringBuffer sb = new StringBuffer();
			Vector<String> v = new Vector<String>();
			Hashtable<String, String> h = new Hashtable<String, String>();
	
			// Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你
			// 那么到底用谁呢?
			// public static <T> List<T> synchronizedList(List<T> list)
			List<String> list1 = new ArrayList<String>();// 线程不安全
			List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全
		}
	}

JDK5中Lock锁的使用

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。

public interface Lock ,Lock是一个接口 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

Java基础总结篇---多线程实现

	public class SellTicket implements Runnable {
	
		// 定义票
		private int tickets = 100;
	
		// 定义锁对象
		private Lock lock = new ReentrantLock();
	
		@Override
		public void run() {
			while (true) {
				try {
					// 加锁
					lock.lock();
					if (tickets > 0) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "正在出售第" + (tickets--) + "张票");
					}
				} finally {
					// 释放锁
					lock.unlock();
				}
			}
		}
	
	}

简单了解一下就行了,毕竟你看到的线程安全里面的各种类的源码使用的仍然是synchronized关键字.

死锁问题


是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象

死锁代码例子,其中创建了A,B两把锁:

	public class DieLock extends Thread {
	
		private boolean flag;	
		public DieLock(boolean flag) {
			this.flag = flag;
		}
	
		@Override
		public void run() {
			if (flag) {
				synchronized (MyLock.objA) {
					System.out.println("if objA");
					synchronized (MyLock.objB) {
						System.out.println("if objB");
					}
				}
			} else {
				synchronized (MyLock.objB) {
					System.out.println("else objB");
					synchronized (MyLock.objA) {
						System.out.println("else objA");
					}
				}
			}
		}
	}

线程间通信


不同种类的线程操作同一资源,由此引出一个线程间通信问题。

Java基础总结篇---多线程实现

代码例子:

	public class Student {
		String name;
		int age;
	}
	public class SetThread implements Runnable {
	
		private Student s;
		private int x = 0;
	
		public SetThread(Student s) {
			this.s = s;
		}
	
		@Override
		public void run() {
			while (true) {
				synchronized (s) {
					if (x % 2 == 0) {
						s.name = "zjl";//刚走到这里,就被别人抢到了执行权
						s.age = 27;
					} else {
						s.name = "zyf"; //刚走到这里,就被别人抢到了执行权
						s.age = 30;
					}
					x++;
				}
			}
		}
	}
	public class GetThread implements Runnable {
		private Student s;
	
		public GetThread(Student s) {
			this.s = s;
		}
	
		@Override
		public void run() {
			while (true) {
				synchronized (s) {
					System.out.println(s.name + "---" + s.age);
				}
			}
		}
	}
	public class StudentDemo {
		public static void main(String[] args) {
			//创建资源
			Student s = new Student();
			
			//设置和获取的类
			SetThread st = new SetThread(s);
			GetThread gt = new GetThread(s);
	
			//线程类
			Thread t1 = new Thread(st);
			Thread t2 = new Thread(gt);
	
			//启动线程
			t1.start();
			t2.start();
		}
	}

等待唤醒机制

wait():等待

notify():唤醒单个线程

notifyAll():唤醒所有线程

Java基础总结篇---多线程实现

代码例子:

	/ * 等待唤醒:
	 */
	public class StudentDemo {
		public static void main(String[] args) {
			//创建资源,统一学生资源
			Student s = new Student();
			
			//设置和获取的类
			SetThread st = new SetThread(s);
			GetThread gt = new GetThread(s);
	
			//创建两类线程,一个获取一个设置
			Thread t1 = new Thread(st);
			Thread t2 = new Thread(gt);
	
			//启动线程
			t1.start();
			t2.start();
		}
	}

	public class Student {
		String name;
		int age;
		boolean flag; // 默认情况是没有数据,如果是true,说明有数据
	}
	

	public class GetThread implements Runnable {
		private Student s;
	
		public GetThread(Student s) {
			this.s = s;
		}
	
		@Override
		public void run() {
			while (true) {
				synchronized (s) {
					if(!s.flag){
						try {
							s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					
					System.out.println(s.name + "---" + s.age);
				
					
					//修改标记
					s.flag = false;
					//唤醒线程
					s.notify(); //唤醒t1
				}
			}
		}
	}
	
	public class SetThread implements Runnable {
	
		private Student s;
		private int x = 0;
	
		public SetThread(Student s) {
			this.s = s;
		}
	
		@Override
		public void run() {
			while (true) {
				synchronized (s) {
					//判断有没有
					if(s.flag){
						try {
							s.wait(); //t1等着,释放锁
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					
					if (x % 2 == 0) {
						s.name = "zjl";
						s.age = 27;
					} else {
						s.name = "zyf";
						s.age = 30;
					}
					x++; //x=1
					
					//修改标记
					s.flag = true;
					//唤醒线程
					s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
				}
				//t1有,或者t2有
			}
		}
	}

线程状态转换图


Java基础总结篇---多线程实现

线程组


Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

Java基础总结篇---多线程实现

  • 默认情况下,所有的线程都属于主线程组。

public final ThreadGroup getThreadGroup()

  • 我们也可以给线程设置分组

Thread(ThreadGroup group, Runnable target, String name)

代码例子:

	public class ThreadGroupDemo {
		public static void main(String[] args) {
			// method1();
	
			// 我们如何修改线程所在的组呢?
			// 创建一个线程组
			// 创建其他线程的时候,把其他线程的组指定为我们自己新建线程组
			method2();
	
			// t1.start();
			// t2.start();
		}
	
		private static void method2() {
			// ThreadGroup(String name)
			ThreadGroup tg = new ThreadGroup("这是一个新的组");
	
			MyRunnable my = new MyRunnable();
			// Thread(ThreadGroup group, Runnable target, String name)
			Thread t1 = new Thread(tg, my, "zjl");
			Thread t2 = new Thread(tg, my, "zyf");
			
			System.out.println(t1.getThreadGroup().getName());
			System.out.println(t2.getThreadGroup().getName());
			
			//通过组名称设置后台线程,表示该组的线程都是后台线程
			tg.setDaemon(true);
		}
	
		private static void method1() {
			MyRunnable my = new MyRunnable();
			Thread t1 = new Thread(my, "zjl");
			Thread t2 = new Thread(my, "zyf");
			// 我不知道他们属于那个线程组,我想知道,怎么办
			// 线程类里面的方法:public final ThreadGroup getThreadGroup()
			ThreadGroup tg1 = t1.getThreadGroup();
			ThreadGroup tg2 = t2.getThreadGroup();
			// 线程组里面的方法:public final String getName()
			String name1 = tg1.getName();
			String name2 = tg2.getName();
			System.out.println(name1);
			System.out.println(name2);
			// 通过结果我们知道了:线程默认情况下属于main线程组
			// 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
			System.out.println(Thread.currentThread().getThreadGroup().getName());
		}
	}

线程池


程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

  • 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
  • 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

public static ExecutorService newCachedThreadPool()

public static ExecutorService newFixedThreadPool(int nThreads)

public static ExecutorService newSingleThreadExecutor()

代码例子:

	public class ExecutorsDemo {
		public static void main(String[] args) {
			// 创建一个线程池对象,控制要创建几个线程对象。
		
			ExecutorService pool = Executors.newFixedThreadPool(2);
	
			// 可以执行Runnable对象或者Callable对象代表的线程
			pool.submit(new MyRunnable());
			pool.submit(new MyRunnable());
	
			//可以手动结束线程池
			pool.shutdown();
		}
	}

匿名内部类实现多线程

  • new Thread(){代码…}.start();
  • new Thread(new Runnable(){代码…}).start();
		/*
		 * 匿名内部类的格式:
		 * 		new 类名或者接口名() {
		 * 			重写方法;
		 * 		};
		 * 		本质:是该类或者接口的子类对象。
		 */
		public class ThreadDemo {
		public static void main(String[] args) {
			// 继承Thread类来实现多线程
			new Thread() {
				public void run() {
					for (int x = 0; x < 100; x++) {
						System.out.println(Thread.currentThread().getName() + ":"
								+ x);
					}
				}
			}.start();
	
			// 实现Runnable接口来实现多线程
			new Thread(new Runnable() {
				@Override
				public void run() {
					for (int x = 0; x < 100; x++) {
						System.out.println(Thread.currentThread().getName() + ":"
								+ x);
					}
				}
			}) {
			}.start();
	
			// 子类对象一个run  Runnable一个对象      **实质上走的是Runnable的run,可以这样写但是没什么鸟用。
			new Thread(new Runnable() {
				@Override
				public void run() {
					for (int x = 0; x < 100; x++) {
						System.out.println("hello" + ":" + x);
					}
				}
			}) {
				public void run() {
					for (int x = 0; x < 100; x++) {
						System.out.println("world" + ":" + x);
					}
				}
			}.start();
		}
	}

定时器


定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。开发中常用Quartz,它是一个完全由java编写的开源调度框架。

Java基础总结篇---多线程实现

  • Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。

    • public Timer()

    创建一个新计时器。

    • public void schedule(TimerTask task, long delay)

    安排在指定延迟后执行指定的任务。

    • public void schedule(TimerTask task,long delay,long period)

      安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。

  • TimerTask:(抽象类)由 Timer 安排为一次执行或重复执行的任务

    • public abstract void run()

    此计时器任务要执行的操作。

    • public boolean cancel()

      取消此计时器任务。

代码例子:

	public class TimerDemo {
		public static void main(String[] args) {
			// 创建定时器对象
			Timer t = new Timer();
			// 3秒后执行爆炸任务
			// t.schedule(new MyTask(), 3000);
			//结束任务
			t.schedule(new MyTask(t), 3000);
		}
	}
	
	// 做一个任务
	class MyTask extends TimerTask {
	
		private Timer t;
		
		public MyTask(){}
		
		public MyTask(Timer t){
			this.t = t;
		}
		
		@Override
		public void run() {
			System.out.println("beng,爆炸了");
			t.cancel();
		}
	
	}

总结


  • 多线程有几种实现方案,分别是哪几种?

    三种,常用的是两种,一种是继承Thread类一种是实现Runnable接口。

  • 同步有几种方式,分别是什么?

    两种:同步代码块,同步方法。

  • 启动一个线程是run()还是start()?它们的区别?

    启动线程是用start()

    run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用

    start():启动线程,并由JVM自动调用run()方法

  • sleep()和wait()方法的区别

    sleep():必须指时间;不释放锁,即使有人想进来也不行。

    wait():可以不指定时间,也可以指定时间;释放锁,运行别的线程执行。

  • 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

    因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
    而Object代码任意的对象,所以,定义在这里面。

  • 线程的生命周期图

    Java基础总结篇---多线程实现