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() | 获取线程的优先级 |