Java 线程的创建和使用

线程与进程

  这个涉及到操作系统的知识,可以自行查阅。

 

 

创建线程的三种方式

  1、继承Thread类

  2、实现Runnable接口

  3、实现Callable接口

  下面对这三种方式进行举例说明。

 

 

方式1:继承Thread类

  注意事项:

1、某个类,继承Thread之后,需要重写run()方法,在run()方法中定义要进行的操作。

2、然后实例化该类,通过对象调用start()方法,那么在run方法中定义的操作就会开一个线程去执行。

3、如果直接通过对象调用run()方法,则并不会以线程的方式去执行run()方法中定义的操作。

4、可以调用从Thread继承过来的getName()方法,来获取当前线程的名称。

5、因为Java是单继承,所以这种方式并不推荐,因为如果一个类早期继承Thread类之后,之后如果需要继承其他类,则需要进行重构代码,不利于维护。

代码实例

public class Test{
	public static void main(String[] args) {
		Actor act1 = new Actor(1);
		Actor act2 = new Actor(2);
		Actor act3 = new Actor(3);
		
		act1.start();
		act2.start();
		act3.start();
	}
}

class Actor extends Thread {
	private int i;
	public Actor(int i) {
		this.i= i;
	}
	public void run() {
		System.out.println("run " + this.i + " thread");
	}
}

  

 

方式2:实现Runnable接口

  注意事项:

1、某个类实现Runnable接口之后,仍旧要重写run()方法,其实Thread类也实现了Runnable接口。

2、通过Thread代理类,调用start()方法,因为Runnable接口中未定义start()方法。

  3、实现Runnable接口可以避免单继承一起的问题。

  4、通过Thread代理,可以实现资源共享。

  代码实例:

public class Test{
	public static void main(String[] args) {
		Actor act1 = new Actor(1);
		Actor act2 = new Actor(2);
		Actor act3 = new Actor(3);
		
		// Thread类此时是代理类
		Thread t1 = new Thread(act1);
		Thread t2 = new Thread(act2);
		Thread t3 = new Thread(act3);
		
		t1.start();
		t2.start();
		t3.start();
		
		// 上面的代码等价于:
		//new Thread(new Actor(4)).start();
	}
}

class Actor implements Runnable{
	private int i;
	public Actor(int i) {
		this.i= i;
	}
	public void run() {
		System.out.println("run " + this.i + " thread");
	}
}

    

  使用匿名类方式

new Thread(new Runnable(){
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("mythread print " + i);
		}
	}
}).start();

  

  使用匿名类 + Lambda方式

new Thread(() -> {
		for (int i = 0; i < 10; i++) {
			System.out.println("mythread print " + i);
		}
}).start();

  

  资源共享的例子:

public class Test{
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		
		// 通过多个Thread共享一个ticket对象的数据
		new Thread(ticket, "aaaaaa").start();
		new Thread(ticket, "bbbbbb").start();
		new Thread(ticket, "cccccc").start();
		new Thread(ticket, "dddddd").start();
	}
}

/**
 * Ticket 售票机
 */
class Ticket implements Runnable{
	private int ticket_sum = 100;
	public void run() {
		while (true) {
			if (ticket_sum < 0) {
				break;
			}
			String notice = Thread.currentThread().getName() + " buy ticket number " + this.ticket_sum--;
			System.out.println(notice);
		}
	}
}

  

  模仿龟兔赛跑

public class Demo implements Runnable{
	private static String winner = null;
	
	@Override
	public void run() {
		for (int step = 1; step <= 100; step++) {
			System.out.println(Thread.currentThread().getName() + " 走了 " + step + " 步");
			if (isOver(step)) {
				break;
			}
			if (Thread.currentThread().getName().equals("兔子") && step % 10 == 0) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public boolean isOver(int step) {
		if (this.winner != null) {
			return true;
		} else if (step == 100) {
			winner = Thread.currentThread().getName();
			return true;
		} else {
			return false;
		}
	}
	
	public static void main(String[] args) {
		Demo d = new Demo();
		new Thread(d, "乌龟").start();
		new Thread(d, "兔子").start();
	}
}

  

 

方式3:实现Callable接口

  使用Callable的优点:可以抛出异常,可以有返回值(注意Runnable方法的run方法是不能抛出异常,并且返回值为void)。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Demo implements Callable<Boolean> {
	
	@Override
	public Boolean call() throws Exception {
		System.out.println(Thread.currentThread().getName());
		return true;
	}
	
	public static void main(String[] args) throws Exception {
		Demo d1 = new Demo();
		Demo d2 = new Demo();
		Demo d3 = new Demo();
		
		// 创建执行服务
		ExecutorService ser = Executors.newFixedThreadPool(3);
		
		// 提交执行
		Future<Boolean> result1 = ser.submit(d1);
		Future<Boolean> result2 = ser.submit(d2);
		Future<Boolean> result3 = ser.submit(d3);
		
		// 获取结果
		Boolean r1 = result1.get();
		Boolean r2 = result2.get();
		Boolean r3 = result3.get();
		
		// 关闭服务
		ser.shutdown();
	}
}

  

 

理解Java的线程代理类

  这其实是一个设计模式:代理模式。可以使用一个简单的代码来理解。

  代理类和真实类都实现同一个接口。

public class Demo {
	public static void main(String[] args) {
		new BirthdayCompany(new HappyBirthday("ganlixin")).Congratulation();
	}
}

interface Happy{
	public void Congratulation();
}

//真实的目标
class HappyBirthday implements Happy {
	public String name;
	
	public HappyBirthday (String name) {
		this.name = name;
	}
	
	@Override
	public void Congratulation() {
		System.out.println("Happy Birthday To You");
	}
}

//代理类
class BirthdayCompany implements Happy {
	private Happy obj;
	
	public BirthdayCompany(Happy O) {
		this.obj = O;
	}
	
	@Override
	public void Congratulation() {
		this.before();
		this.obj.Congratulation();
		this.after();
	}
	public void before () {
		System.out.println("前期准备");
	}
	public void after () {
		System.out.println("后期打扫");
	}
}

  

  

利用Lambda表达式来创建线程

  使用Lambda表达式的前提条件就是,实现一个接口,该接口中有且仅有1个方法必须被重写。

  这里的Runnable接口,刚好需要重写一个run()方法,且只需要重写这1个方法。

  所以,可以这样写:

public class Test{	
	public static void main(String[] args) {
		
		// 方式1
		Runnable r = () -> {  
			//code....写run()方法中要执行的操作
			System.out.println(Thread.currentThread().getName());
		};
		new Thread(r, "Thread-aaaa").start();
		
		// 方式2
		new Thread(() -> {
			//code....写run()方法中要执行的操作
			System.out.println(Thread.currentThread().getName());
		}, "Thread-bbbb").start();
		
	}
}

  

 

Thread.sleep()

  使用Thread.sleep(millis)可以让当前线程休眠多少毫妙。

  sleep()操作会将进程状态从运行态转换为阻塞状态。

  Thread.sleep(1000)让线程休眠1秒,之后继续执行后续操作。

  注意,Thread.sleep()可能会抛出InterruptedException异常,所以需要使用try/catch 或者 throws结构。

public class Test{	
	public static void main(String[] args) {
		try {
			int i = 10;
			while (i > 0) {
				System.out.println(i--);
				Thread.sleep(1000);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

  

 

Thread.yield()

  yield()的功能是让出当前线程所占用的时间片,也就是说,当前线程主动让出CPU,然后当前线程由运行状态转换为就绪状态。

  注意yield()和sleep()的区别。

public class Test{	
	public static void main(String[] args) {
		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName() + "----> " + i);
				if (i % 2 == 0) {
					Thread.yield();
				}
			}
		}, "aaaa").start();
		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName() + "----> " + i);
				if (i % 3 == 0) {
					Thread.yield();
				}
			}
		}, "bbbbb").start();
	}
}

  

 

Thread.join()

  join()的字面意思是合并线程,但是准确的说,可以认为是插入线程,表示当前线程阻塞,需要等待另外某个线程执行完毕后,当前线程才可以转换为就绪状态,进而再次运行。

  注意。sleep()和yield()都是静态方法(类方法),而join()是成员方法(实例方法)。

  方法声明:

// 指定的线程运行完毕之后再运行当前线程
void java.lang.Thread.join() throws InterruptedException

// 在指定的时间内,如果指定的线程还没有运行完毕,那么当前线程继续运行。
void java.lang.Thread.join(long millis) throws InterruptedException

  

    join()的示例用法如下:

public class Test{	
	public static void main(String[] args) {
		Thread t = new Thread(() -> {
			for (int i = 0; i < 100; i++) {
				System.out.println(Thread.currentThread().getName() + " ----> " + i);
			}
		}, "Thread A");
		
		t.start();
		
		new Thread(() -> {
			for (int i = 0; i < 100; i++) {
				System.out.println(Thread.currentThread().getName() + " ----> " + i);
				
				// 当i小于10时,两个线程交替执行。
				// 当i等于10时,当前线程阻塞,然后等待线程t执行完毕,然后本线程再恢复为就绪,再运行
				if (i == 10) {
					try {
						t.join();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}, "Thread B").start();
	}
}

  

  以父亲叫儿子去买烟为例

public class Test{	
	public static void main(String[] args) {
		new Thread(new Father()).start();
	}
}

class Father implements Runnable {
	public void run() {
		System.out.println("想抽烟了,叫儿子去买烟");
		Thread t = new Thread(new Son());
		t.start();
		
		// 如果没有t.join(),那么,当前线程和线程t都各自执行,并没有先后顺序
		// 所以“接过烟,把零钱给儿子” 会在 “ 儿子出门买烟” 之前运行
		// 但是加入了join就规定了先后顺序,要在t线程执行完之后,才会继续当前线程
		try {
			t.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("接过烟,把零钱给儿子");
	}
}

class Son implements Runnable {
	public void run() {
		System.out.println("接过钱,出门");
		System.out.println("路过游戏厅");
		try {
			Thread.sleep(3 * 1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("想起买烟了");
		System.out.println("买了烟回家");
	}
}

  

 

Thread.stop()

  stop()方法已经被废弃了,不再推荐使用。

  但是可以使用其他方式来实现:设置一个标志,将该标志设定为线程是否继续运行的判断条件,通过修改该标志来决定线程是否继续运行。

public class Test {
	public static void main(String[] args) throws InterruptedException {
		MyThread t = new MyThread();
		new Thread(t, "aaaaaaa").start();
		
		Thread.sleep(1000 * 5);
		//主线程修改5秒钟后,手动去停止子线程
		
		// 通过调用t的方法,修改标志位,达到停止线程的目的
		t.stopThread();
	}
}

class MyThread implements Runnable {
	private boolean needStop = false;
	
	public void run() {
		while (!needStop) {
			int i = 0;
			System.out.println(Thread.currentThread().getName() + " ---> " + i++);
		}
	}
	
	public void stopThread() {
		this.needStop = true;
	}
}

  

 

线程的五个状态

  五个状态分别是:创建、就绪、运行、阻塞、死亡。状态的转换过程如下如所示:

  

 

  创建状态(Thread.State.NEW)

    一个线程经过new Thread()之后,就变成了创建状态。

  就绪状态(Thread.State.RUNNABLE)

    1、处于创建状态的线程,调用start()方法之后,就会变成就绪状态。

    2、线程从阻塞状态中解除,会转换为就绪状态,注意并不会立即变为运行状态。

    3、时间片轮转调度是,线程所分得的时间片用完,则会转换为就绪状态。

    4、调用Thread.yield()主动让出时间片(让出CPU),则会转换为就绪状态。

  阻塞状态(Thread.State.BLOCKED、Thread.State.WAITING)

    1、线程处于运行状态,如果需要I/O(文件IO,网络IO),在IO完成之前就会转换为阻塞状态(Thread.State.BLOCKED;)。

    2、调用Thread.sleep()、Thread.wait()方法,让线程由运行状态转换为阻塞状态(Thread.State.WAITING;)。

  运行状态(Thread.State.RUNNABLE)

    1、由就绪状态转换为运行状态,注意,阻塞状态不能直接转换为运行状态。

  死亡状态(Thread.State.TERMINATED)

    1、线程完成指定的操作,正常结束

    2、被强制性地终止

    3、线程抛出异常,但是没有捕获异常。

  等待其他线程完成 (Thread.State.TIMED_WAITING)

    1、等待其他线程完成,此时本线程处于Thread.State.TIMED_WAITING状态

 

 

线程的优先级

  首先声明:

  1、优先级并不等于执行先后顺序,优先级只是一个概率、可能性,优先级高的线程,更有可能执行的机会越大。

  2、线程在创建的时候,要在调用start()前设置优先级(不是调用start()方法后才设置)

  3、优先级最低为1,最高为10,初始默认为5,可以手动修改优先级。

public class Test {
	public static void main(String[] args) throws InterruptedException {
		/*
		Thread.MIN_PRIORITY 	-->默认为1;
		Thread.NORM_PRIORITY 	-->默认为5;
		Thread.MAX_PRIORITY     -->默认为10;
		*/
		
		Thread t = new Thread(()->{});
		
		//Thread.getPriority 获取线程的优先级
		System.out.println(t.getPriority());  // 5
		
		//Thread.setPriority(int newPriority)		
		//t.setPriority(Thread.MIN_PRIORITY);
		t.setPriority(7);
		System.out.println(t.getPriority());
		
		t.start();
		
		t.setPriority(Thread.MAX_PRIORITY);
		System.out.println(t.getPriority());
	}
}

  

 

守护线程

  主要注意几点:

  1、线程分为用户线程和守护线程

  2、JVM必须确保用户线程执行完毕后,JVM才会停止

  3、JVM不用等待守护线程执行完毕,也就是说,即使守护线程还在执行,JVM也会停止,不关心守护线程。

  4、默认所有线程都是用户线程,必须调用setDaemon()来显式说明线程是守护线程,在start()之前设置 。

  守护线程比如:操作日志,监控内存使用等,他们都是为用户线程服务的。

public class Test {
	public static void main(String[] args) throws InterruptedException {
		God god = new God();
		Person person = new Person();
				
		// 如果没有将god线程设置为守护线程,那么god线程将会被认为是用户线程。
		// 即使person线程已经结束,但是god线程并没有结束,所以程序会一直运行,不会结束。
		
		// 将用户线程设置为守护线程
		god.setDaemon(true);
		
		god.start();
		person.start();
		
		// 所有的用户线程结束后,即使守护线程没有结束,JVM就停止了,并不会关心守护进程是否结束
	}
}


class Person extends Thread{
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("Still alive, the " + i + " day");
		}
		System.out.println("Game Over");
	}
}

class God extends Thread {
	public void run() {
		// 注意,该线程是一个死循环
		while (true) {
			System.out.println("I will bless you");
		}
	}
}

  

 

代理线程的名称和真实线程的名称

  如果实现Runnable接口后,利用Thread代理类来启动线程,传递给代理类的线程名称是代理类线程名称,而真实线程的名称可以使用真实线程的属性来实现。

public class ThreadName implements Runnable {
	private String threadName;
	
	public ThreadName() {
		super();
	}

	public ThreadName(String threadName) {
		super();
		this.threadName = threadName;
	}

	public String getThreadName() {
		return threadName;
	}

	public void setThreadName(String threadName) {
		this.threadName = threadName;
	}

	@Override
	public void run() {
		System.out.println("真实线程的名称 -> " + this.getThreadName());
		System.out.println("代理类线程的名称 -> " + Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		new Thread(new ThreadName("real threadName"), "proxy threadName").start();
	}
}

  

 

 

 

 Thread类的常用方法

方法名 功能
public void run() 线程所要执行的代码,子类需要重写该方法
public void start() 启动线程,将线程的状态切换为就绪状态
public static Thread currentThread() 返回当前正在执行的线程对象的引用
public static sleep([long mills]) 指定当前所在线程休眠多少毫秒
public final boolean isAlive() 判断指定线程是否终止
public final void setName(String name) 改变线程的名称
public final String getName() 返回线程的名称
public final void join() 阻塞当前线程,等待指定的线程执行结束后,再运行当前线程
public static void yield() 让出时间片
public final void setPriority(int newPriority) 设置线程优先级
public final int getPriority() 获取线程的优先级
posted @ 2018-11-22 17:42  寻觅beyond  阅读(249)  评论(0)    收藏  举报
返回顶部