Java线程小刀牛试
线程简介
什么是线程
现代操作系统调度的最小单元是线程,也叫轻量级进程(light weight process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
线程生命周期
java.lang.thread.state
中定义了 6 种不同的线程状态,在给定的一个时刻,线程只能处于其中的一个状态。
以下是各状态的说明,以及状态间的联系:
- 开始(new) - 还没有调用
start()
方法的线程处于此状态。 - 可运行(runnable) - 已经调用了
start()
方法的线程状态。此状态意味着,线程已经准备好了,一旦被线程调度器分配了 cpu 时间片,就可以运行线程。 - 阻塞(blocked) - 阻塞状态。线程阻塞的线程状态等待监视器锁定。处于阻塞状态的线程正在等待监视器锁定,以便在调用
object.wait()
之后输入同步块/方法或重新输入同步块/方法。 -
等待(waiting) - 等待状态。一个线程处于等待状态,是由于执行了 3 个方法中的任意方法:
object.wait()
thread.join()
locksupport.park()
-
定时等待(timed waiting) - 等待指定时间的状态。一个线程处于定时等待状态,是由于执行了以下方法中的任意方法:终止(terminated) - 线程
run()
方法执行结束,或者因异常退出了run()
方法,则该线程结束生命周期。死亡的线程不可再次复生。thread.sleep(sleeptime)
object.wait(timeout)
thread.join(timeout)
locksupport.parknanos(timeout)
locksupport.parkuntil(timeout)
启动和终止线程
构造线程
构造线程主要有三种方式
- 继承
thread
类 - 实现
runnable
接口 - 实现
callable
接口
继承 thread 类
通过继承 thread 类构造线程的步骤:
- 定义 thread 类的子类,并重写该类的 run() 方法,该 run() 方法的方法体就代表了线程要完成的任务。因此把 run() 方法称为执行体。
- 创建 thread 子类的实例,即创建了线程对象。
- 调用线程对象的 start() 方法来启动该线程。
示例:
public class threaddemo02 { public static void main(string[] args) { thread02 mt1 = new thread02("线程a "); // 实例化对象 thread02 mt2 = new thread02("线程b "); // 实例化对象 mt1.start(); // 调用线程主体 mt2.start(); // 调用线程主体 } static class thread02 extends thread { private int ticket = 5; thread02(string name) { super(name); } @override public void run() { for (int i = 0; i < 100; i++) { if (this.ticket > 0) { system.out.println(this.getname() + " 卖票:ticket = " + ticket--); } } } } }
实现 runnable 接口
通过实现 runnable 接口构造线程的步骤:
- 定义 runnable 接口的实现类,并重写该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。
- 创建 runnable 实现类的实例,并依此实例作为 thread 的 target 来创建 thread 对象,该 thread 对象才是真正的线程对象。
- 调用线程对象的 start() 方法来启动该线程。
示例:
public class runnabledemo { public static void main(string[] args) { mythread t = new mythread("runnable 线程"); // 实例化对象 new thread(t).run(); // 调用线程主体 new thread(t).run(); // 调用线程主体 new thread(t).run(); // 调用线程主体 } static class mythread implements runnable { private int ticket = 5; private string name; mythread(string name) { this.name = name; } @override public void run() { for (int i = 0; i < 100; i++) { if (this.ticket > 0) { system.out.println(this.name + " 卖票:ticket = " + ticket--); } } } } }
实现 callable 接口
通过实现 callable 接口构造线程的步骤:
- 创建 callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 callable 实现类的实例,使用 futuretask 类来包装 callable 对象,该 futuretask 对象封装了该 callable 对象的 call() 方法的返回值。
- 使用 futuretask 对象作为 thread 对象的 target 创建并启动新线程。
- 调用 futuretask 对象的 get() 方法来获得子线程执行结束后的返回值。
示例:
public class callableandfuturedemo { public static void main(string[] args) { callable<integer> callable = () -> new random().nextint(100); futuretask<integer> future = new futuretask<>(callable); new thread(future).start(); try { thread.sleep(1000);// 可能做一些事情 system.out.println(future.get()); } catch (interruptedexception | executionexception e) { e.printstacktrace(); } } }
三种创建线程方式对比
- 实现 runnable 接口优于继承 thread 类,因为实现接口方式更便于扩展类。
- 实现 runnable 接口的线程没有返回值;而实现 callable 接口的线程有返回值。
中断线程
当一个线程运行时,另一个线程可以直接通过 interrupt()
方法中断其运行状态。
public class threadinterruptdemo { public static void main(string[] args) { mythread mt = new mythread(); // 实例化runnable子类对象 thread t = new thread(mt, "线程"); // 实例化thread对象 t.start(); // 启动线程 try { thread.sleep(2000); // 线程休眠2秒 } catch (interruptedexception e) { system.out.println("3、休眠被终止"); } t.interrupt(); // 中断线程执行 } static class mythread implements runnable { @override public void run() { system.out.println("1、进入run()方法"); try { thread.sleep(10000); // 线程休眠10秒 system.out.println("2、已经完成了休眠"); } catch (interruptedexception e) { system.out.println("3、休眠被终止"); return; // 返回调用处 } system.out.println("4、run()方法正常结束"); } } }
终止线程
thread 中的 stop 方法有缺陷,已废弃。
安全地终止线程有两种方法:
- 中断状态是线程的一个标识位,而中断操作是一种简便的线程间交互方式,而这种交互方式最适合用来取消或停止任务。
- 还可以利用一个 boolean 变量来控制是否需要停止任务并终止该线程。
public class threadstopdemo03 { public static void main(string[] args) throws exception { mytask one = new mytask(); thread countthread = new thread(one, "countthread"); countthread.start(); // 睡眠1秒,main线程对countthread进行中断,使countthread能够感知中断而结束 timeunit.seconds.sleep(1); countthread.interrupt(); mytask two = new mytask(); countthread = new thread(two, "countthread"); countthread.start(); // 睡眠1秒,main线程对runner two进行取消,使countthread能够感知on为false而结束 timeunit.seconds.sleep(1); two.cancel(); } private static class mytask implements runnable { private long i; private volatile boolean on = true; @override public void run() { while (on && !thread.currentthread().isinterrupted()) { i++; } system.out.println("count i = " + i); } void cancel() { on = false; } } }
thread 中的重要方法
run
- 线程的执行实体。start
- 线程的启动方法。setname
、getname
- 可以通过 setname()、 getname() 来设置、获取线程名称。setpriority
、getpriority
- 在 java 中,所有线程在运行前都会保持在就绪状态,那么此时,哪个线程优先级高,哪个线程就有可能被先执行。可以通过 setpriority、getpriority 来设置、获取线程优先级。setdaemon
、isdaemon
- 可以使用 setdaemon() 方法设置线程为守护线程;可以使用 isdaemon() 方法判断线程是否为守护线程。isalive
- 可以通过 isalive 来判断线程是否启动。interrupt
- 当一个线程运行时,另一个线程可以直接通过 interrupt() 方法中断其运行状态。join
- 使用 join() 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。thread.sleep
- 使用 thread.sleep() 方法即可实现休眠。thread.yield
- 可以使用 thread.yield() 方法将一个线程的操作暂时让给其他线程执行。
设置/获取线程名称
在 thread 类中可以通过 setname()
、 getname()
来设置、获取线程名称。
public class threadnamedemo { public static void main(string[] args) { mythread mt = new mythread(); // 实例化runnable子类对象 new thread(mt).start(); // 系统自动设置线程名称 new thread(mt, "线程-a").start(); // 手工设置线程名称 thread t = new thread(mt); // 手工设置线程名称 t.setname("线程-b"); t.start(); } static class mythread implements runnable { @override public void run() { for (int i = 0; i < 3; i++) { system.out.println(thread.currentthread().getname() + "运行,i = " + i); // 取得当前线程的名字 } } } }
判断线程是否启动
在 thread 类中可以通过 isalive()
来判断线程是否启动。
public class threadalivedemo { public static void main(string[] args) { mythread mt = new mythread(); // 实例化runnable子类对象 thread t = new thread(mt, "线程"); // 实例化thread对象 system.out.println("线程开始执行之前 --> " + t.isalive()); // 判断是否启动 t.start(); // 启动线程 system.out.println("线程开始执行之后 --> " + t.isalive()); // 判断是否启动 for (int i = 0; i < 3; i++) { system.out.println(" main运行 --> " + i); } // 以下的输出结果不确定 system.out.println("代码执行之后 --> " + t.isalive()); // 判断是否启动 } static class mythread implements runnable { @override public void run() { for (int i = 0; i < 3; i++) { system.out.println(thread.currentthread().getname() + "运行,i = " + i); } } } }
守护线程
在 java 程序中,只要前台有一个线程在运行,则整个 java 进程就不会消失,所以此时可以设置一个守护线程,这样即使 java 进程结束了,此守护线程依然会继续执行。可以使用 setdaemon()
方法设置线程为守护线程;可以使用 isdaemon()
方法判断线程是否为守护线程。
public class threaddaemondemo { public static void main(string[] args) { thread t = new thread(new mythread(), "线程"); t.setdaemon(true); // 此线程在后台运行 system.out.println("线程 t 是否是守护进程:" + t.isdaemon()); t.start(); // 启动线程 } static class mythread implements runnable { @override public void run() { while (true) { system.out.println(thread.currentthread().getname() + "在运行。"); } } } }
设置/获取线程优先级
在 java 中,所有线程在运行前都会保持在就绪状态,那么此时,哪个线程优先级高,哪个线程就有可能被先执行。
public class threadprioritydemo { public static void main(string[] args) { system.out.println("主方法的优先级:" + thread.currentthread().getpriority()); system.out.println("max_priority = " + thread.max_priority); system.out.println("norm_priority = " + thread.norm_priority); system.out.println("min_priority = " + thread.min_priority); thread t1 = new thread(new mythread(), "线程a"); // 实例化线程对象 thread t2 = new thread(new mythread(), "线程b"); // 实例化线程对象 thread t3 = new thread(new mythread(), "线程c"); // 实例化线程对象 t1.setpriority(thread.min_priority); // 优先级最低 t2.setpriority(thread.max_priority); // 优先级最低 t3.setpriority(thread.norm_priority); // 优先级最低 t1.start(); // 启动线程 t2.start(); // 启动线程 t3.start(); // 启动线程 }
static class mythread implements runnable { @override public void run() { for (int i = 0; i < 5; i++) { try { thread.sleep(500); // 线程休眠 } catch (interruptedexception e) { e.printstacktrace(); } // 取得当前线程的名字 string out = thread.currentthread().getname() + ",优先级:" + thread.currentthread().getpriority() + ",运行:i = " + i; system.out.println(out); } } } }
线程间通信
wait/notify/notifyall
wait、notify、notifyall 是 object 类中的方法。
wait
- 线程自动释放其占有的对象锁,并等待 notify。notify
- 唤醒一个正在 wait 当前对象锁的线程,并让它拿到对象锁。notifyall
- 唤醒所有正在 wait 前对象锁的线程。
生产者、消费者示例:
public class threadwaitnotifydemo02 { private static final int queue_size = 10; private static final priorityqueue<integer> queue = new priorityqueue<>(queue_size); public static void main(string[] args) { new producer("生产者a").start(); new producer("生产者b").start(); new consumer("消费者a").start(); new consumer("消费者b").start(); } static class consumer extends thread { consumer(string name) { super(name); } @override public void run() { while (true) { synchronized (queue) { while (queue.size() == 0) { try { system.out.println("队列空,等待数据"); queue.wait(); } catch (interruptedexception e) { e.printstacktrace(); queue.notifyall(); } } queue.poll(); // 每次移走队首元素 queue.notifyall(); try { thread.sleep(500); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + " 从队列取走一个元素,队列当前有:" + queue.size() + "个元素"); } } } } static class producer extends thread { producer(string name) { super(name); } @override public void run() { while (true) { synchronized (queue) { while (queue.size() == queue_size) { try { system.out.println("队列满,等待有空余空间"); queue.wait(); } catch (interruptedexception e) { e.printstacktrace(); queue.notifyall(); } } queue.offer(1); // 每次插入一个元素 queue.notifyall(); try { thread.sleep(500); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + " 向队列取中插入一个元素,队列当前有:" + queue.size() + "个元素"); } } } } }
线程的礼让
在线程操作中,可以使用 thread.yield()
方法将一个线程的操作暂时让给其他线程执行。
public class threadyielddemo { public static void main(string[] args) { mythread t = new mythread(); new thread(t, "线程a").start(); new thread(t, "线程b").start(); } static class mythread implements runnable { @override public void run() { for (int i = 0; i < 5; i++) { try { thread.sleep(1000); } catch (exception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + "运行,i = " + i); if (i == 2) { system.out.print("线程礼让:"); thread.yield(); } } } } }
线程的强制执行
在线程操作中,可以使用 join()
方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
public class threadjoindemo { public static void main(string[] args) { mythread mt = new mythread(); // 实例化runnable子类对象 thread t = new thread(mt, "mythread"); // 实例化thread对象 t.start(); // 启动线程 for (int i = 0; i < 50; i++) { if (i > 10) { try { t.join(); // 线程强制运行 } catch (interruptedexception e) { e.printstacktrace(); } } system.out.println("main 线程运行 --> " + i); } }
static class mythread implements runnable { @override public void run() { for (int i = 0; i < 50; i++) { system.out.println(thread.currentthread().getname() + " 运行,i = " + i); // 取得当前线程的名字 } } } }
线程的休眠
直接使用 thread.sleep()
方法即可实现休眠。
public class threadsleepdemo { public static void main(string[] args) { new thread(new mythread("线程a", 1000)).start(); new thread(new mythread("线程a", 2000)).start(); new thread(new mythread("线程a", 3000)).start(); } static class mythread implements runnable { private string name; private int time; private mythread(string name, int time) { this.name = name; // 设置线程名称 this.time = time; // 设置休眠时间 } @override public void run() { try { thread.sleep(this.time); // 休眠指定的时间 } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(this.name + "线程,休眠" + this.time + "毫秒。"); } } }
threadlocal
threadlocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道 threadlocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
源码
threadlocal 的主要方法: public class threadlocal<t> { public t get() {} public void remove() {} public void set(t value) {} public static <s> threadlocal<s> withinitial(supplier<? extends s> supplier) {} }
- get()方法是用来获取 threadlocal 在当前线程中保存的变量副本。
- set()用来设置当前线程中变量的副本。
- remove()用来移除当前线程中变量的副本。
- initialvalue()是一个 protected 方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。
get() 源码实现
get 源码 public t get() { thread t = thread.currentthread(); threadlocalmap map = getmap(t); if (map != null) { threadlocalmap.entry e = map.getentry(this); if (e != null) { @suppresswarnings("unchecked") t result = (t)e.value; return result; } } return setinitialvalue(); }
- 取得当前线程。
- 通过 getmap() 方法获取 threadlocalmap。
- 成功,返回 value;失败,返回 setinitialvalue()。
threadlocalmap 源码实现
threadlocalmap 源码
threadlocalmap 是 threadlocal 的一个内部类。
threadlocalmap 的 entry 继承了 weakreference,并且使用 threadlocal 作为键值。
setinitialvalue 源码实现
private t setinitialvalue() { t value = initialvalue(); thread t = thread.currentthread(); threadlocalmap map = getmap(t); if (map != null) map.set(this, value); else createmap(t, value); return value; }
如果 map 不为空,就设置键值对;为空,再创建 map,看一下 createmap 的实现:
void createmap(thread t, t firstvalue) { t.threadlocals = new threadlocalmap(this, firstvalue); }
threadlocal 源码小结
至此,可能大部分朋友已经明白了 threadlocal 是如何为每个线程创建变量的副本的:
- 在每个线程 thread 内部有一个 threadlocal.threadlocalmap 类型的成员变量 threadlocals,这个 threadlocals 就是用来存储实际的变量副本的,键值为当前 threadlocal 变量,value 为变量副本(即 t 类型的变量)。
- 在 thread 里面,threadlocals 为空,当通过 threadlocal 变量调用 get()方法或者 set()方法,就会对 thread 类中的 threadlocals 进行初始化,并且以当前 threadlocal 变量为键值,以 threadlocal 要保存的副本变量为 value,存到 threadlocals。
- 在当前线程里面,如果要使用副本变量,就可以通过 get 方法在 threadlocals 里面查找。
示例
threadlocal 最常见的应用场景为用于解决数据库连接、session 管理等问题。
示例 - 数据库连接
private static threadlocal<connection> connectionholder = new threadlocal<connection>() { public connection initialvalue() { return drivermanager.getconnection(db_url); } }; public static connection getconnection() { return connectionholder.get(); }
示例 - session 管理
private static final threadlocal threadsession = new threadlocal(); public static session getsession() throws infrastructureexception { session s = (session) threadsession.get(); try { if (s == null) { s = getsessionfactory().opensession(); threadsession.set(s); } } catch (hibernateexception ex) { throw new infrastructureexception(ex); } return s; }
管道输入/输出流
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。 管道输入/输出流主要包括了如下 4 种具体实现:pipedoutputstream、pipedinputstream、pipedreader 和 pipedwriter,前两种面向字节,而后两种面向字符。
public class piped { public static void main(string[] args) throws exception { pipedwriter out = new pipedwriter(); pipedreader in = new pipedreader(); // 将输出流和输入流进行连接,否则在使用时会抛出ioexception out.connect(in); thread printthread = new thread(new print(in), "printthread"); printthread.start(); int receive = 0; try { while ((receive = system.in.read()) != -1) { out.write(receive); } } finally { out.close(); } } static class print implements runnable { private pipedreader in; print(pipedreader in) { this.in = in; } public void run() { int receive = 0; try { while ((receive = in.read()) != -1) { system.out.print((char) receive); } } catch (ioexception e) { e.printstacktrace(); } } } }
faq
start() 和 run() 有什么区别?可以直接调用 thread 类的 run() 方法么?
run() 方法是线程的执行体。
start() 方法会启动线程,然后 jvm 会让这个线程去执行 run() 方法。
可以直接调用 thread 类的 run() 方法么?
- 可以。但是如果直接调用 thread 的 run()方法,它的行为就会和普通的方法一样。
- 为了在新的线程中执行我们的代码,必须使用 thread.start()方法。
sleep()、yield()、join() 方法有什么区别?为什么 sleep()和 yield()方法是静态的?
-
yield()
- yield() 方法可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程从 running 状态转入 runnable 状态。
- 当某个线程调用了 yield() 方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。
-
sleep()
- sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入 blocked 状态。
- 该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。
- 但是, sleep() 方法不会释放“锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据。
-
join()
- join() 方法会使当前线程转入 blocked 状态,等待调用 join() 方法的线程结束后才能继续执行。
参考阅读:java 线程中 yield 与 join 方法的区别 参考阅读:
为什么 sleep() 和 yield() 方法是静态的
- thread 类的 sleep() 和 yield() 方法将处理 running 状态的线程。所以在其他处于非 running 状态的线程上执行这两个方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
java 的线程优先级如何控制?高优先级的 java 线程一定先执行吗?
-
java 中的线程优先级如何控制
- java 中的线程优先级的范围是 [1,10],一般来说,高优先级的线程在运行时会具有优先权。可以通过
thread.setpriority(thread.max_priority)
的方式设置,默认优先级为 5。
- java 中的线程优先级的范围是 [1,10],一般来说,高优先级的线程在运行时会具有优先权。可以通过
-
高优先级的 java 线程一定先执行吗
- 即使设置了线程的优先级,也无法保证高优先级的线程一定先执行。
- 原因:这是因为线程优先级依赖于操作系统的支持,然而,不同的操作系统支持的线程优先级并不相同,不能很好的和 java 中线程优先级一一对应。
- 结论:java 线程优先级控制并不可靠。
什么是守护线程?为什么要用守护线程?如何创建守护线程?
-
什么是守护线程
- 守护线程(daemon thread)是在后台执行并且不会阻止 jvm 终止的线程。
- 与守护线程(daemon thread)相反的,叫用户线程(user thread),也就是非守护线程。
-
为什么要用守护线程
- 守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。典型的应用就是垃圾回收器。
-
如何创建守护线程
- 使用 thread.setdaemon(true) 可以设置 thread 线程为守护线程。
- 注意点:
- 正在运行的用户线程无法设置为守护线程,所以 thread.setdaemon(true) 必须在 thread.start() 之前设置,否则会抛出 llegalthreadstateexception 异常;
- 一个守护线程创建的子线程依然是守护线程。
- 不要认为所有的应用都可以分配给 daemon 来进行服务,比如读写操作或者计算逻辑。
参考阅读:java 中守护线程的总结
为什么线程通信的方法 wait(), notify()和 notifyall()被定义在 object 类里?
java 的每个对象中都有一个锁(monitor,也可以成为监视器) 并且 wait(),notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用。在 java 的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是 object 类的一部分,这样 java 的每一个类都有用于线程间通信的基本方法
为什么 wait(), notify()和 notifyall()必须在同步方法或者同步块中被调用?
当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
免费java资料需要自己领取,涵盖了java、redis、mongodb、mysql、zookeeper、spring cloud、dubbo/kafka、hadoop、hbase、flink等高并发分布式、大数据、机器学习等技术。
传送门:https://mp.weixin.qq.com/s/jzddfh-7ynudmkjt0irl8q