操作系统每启动一个应用程序都会为其开启一个进程;而在进程里面又可以创建很多线程;Java中运行main方法时,除了main线程,还会启动其他的线程,下面利用JMX来查看:
public static void main(String[] args) {
//获取Java线程管理MXBean
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
//不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
ThreadInfo[] arr = bean.dumpAllThreads(false, false);
//遍历线程信息,仅打印线程ID和线程名称信息
for(ThreadInfo e : arr){
System.out.println("["+e.getThreadId()+"]"+e.getThreadName());
}
}
结果
[5]Attach Listener //提供jvm进程间通信的能力
[4]Signal Dispatcher //分发处理发送给JVM信号的线程
[3]Finalizer //调用对象finalize方法的线程
[2]Reference Handler //清除Reference的线程
[1]main //main线程
JMX(Java Management Extensions)是一个为应用程序、设备、系统等植入管理功能的框架
线程是处理器调度的基本单元,一个时刻只可能有一个线程在处理器执行;在多核处理器的情况下,多线程可以利用多核心的特点,使程序执行更加有效率
例如:一个订单的创建,包括插入订单数据,生成订单快照,发送邮件通知卖家,记录商品销售数量;单线程情况下,用户从单击“订购”按钮开始,就要等待这些操作全部完成才能看到订购成功的结果;这样需要客户等待较长的时间;利用多线程技术,即将数据一致性不强的操作派发给其他线程处理(也可以使用消息队列),如生成订单快照、发送邮件等。这样做的好处是响应用户请求的线程能够尽可能快地处理完成,缩短了响应时间,提升了用户体验
处理器调度线程有两种方式:分时调度模型和抢占式调度模型;JVM采用抢占式调度模型,线程优先级越高,获得CPU时间使用权的几率越高
使用jconsole.exe和jstack查看线程状态,以死锁为例:
public class Thread_2 {
public static void main(String[] args) {
final A a = new A();
final B b = new B();
new Thread(new Runnable() {
@Override
public void run() {
a.invoke(b);
}
}, "线程1").start();;
new Thread(new Runnable() {
@Override
public void run() {
b.invoke(a);
}
}, "线程2").start();;
}
}
class A{
//① 线程一调用A的invoke()方法,并对a对象进行加锁
public synchronized void invoke(B b){
System.out.println(Thread.currentThread().getName()+"进入A的invlke()方法");
//② 线程一休眠100毫秒,CPU切换执行线程二
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//⑤ 线程一继续运行,调用B的print方法,但是b对象在第③步被加锁,没有释放锁,所以线程阻塞等待锁释放
b.print();
}
public synchronized void print(){
System.out.println("A的print()方法");
}
}
class B{
//③ 线程二调用B的invoke()方法,并对b对象进行加锁
public synchronized void invoke(A a){
System.out.println(Thread.currentThread().getName()+"进入B的invlke()方法");
//④ 线程二休眠100毫秒,CPU切换执行线程一
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//⑥ 线程二继续运行,调用A的print方法,但是a对象在第①步被加锁,没有释放锁,所以线程阻塞等待锁释放
a.print();
}
public synchronized void print(){
System.out.println("B的print()方法");
}
}
- 获取Java线程ID:打开CMD输入jps
可以看到,有一条死锁的提示
线程运行的时候拥有独立的栈空间,但是每个线程都在独立运行时,并不能带来很大的价值,只有多个线程相互协作时,才能发挥巨大的作用
多个线程访问一个变量时,可以各自copy一份存储于缓存中以提高程序的执行效率;但是这样做可能造成内存可见性问题:一个线程看到的变量可能不是最新的;volatile修饰的变量,使得线程在访问时,只能从内存中访问,完成写操作后必须刷新回主存;synchronized修饰代码块使得多个线程共同访问时,一次只能由一个线程去访问;
等待/通知机制是指A线程调用了对象O的wait()方法进入等待状态,B线程调用对象O的notify()方法或者notifyAll()方法,线程A收到通知后从wait()方法返回,进而进行后续操作。
让当前线程阻塞1秒
package com.lt.thread03;
/**
* wait()方法:
* @author lt
* @date 2019年5月9日
* @version v1.0
*/
public class Thread_01 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("start...");
try {
this.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}).start();;
}
}
结果:
start...
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at com.lt.thread03.Thread_01$1.run(Thread_01.java:17)
at java.lang.Thread.run(Thread.java:748)
分析:查看IllegalMonitorStateException的JDK源码如下:
Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor.
大概意思是:线程尝试获取对象的监视器,或者唤醒其他线程获取对象的监视器;Java中获取监视器只能通过synchronized(lock不可)获取
package com.lt.thread03;
/**
* wait()方法:
* @author lt
* @date 2019年5月9日
* @version v1.0
*/
public class Thread_02 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("start...");
try {
synchronized (this) {
this.wait(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}).start();;
}
}
所以wait()只能在同步的情况下使用
notify()用来唤醒对象monitor上的一个线程,notifyAll()方法用来唤醒对象monitor上的所有线程
package com.lt.thread03;
/**
* notify()方法:唤醒阻塞的线程
* @author lt
* @date 2019年5月9日
* @version v1.0
*/
public class Thread_03 {
public synchronized void print(){
System.out.println(Thread.currentThread().getName()+":start...");
try {
this.wait(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":end...");
}
public static void main(String[] args) throws Exception {
Thread_03 o = new Thread_03();
for(int i=0; i<5; i++){
new Thread(new Runnable() {
@Override
public void run() {
o.print();
}
}, "线程"+i).start();
}
synchronized (o) {
o.notify();
}
//为何要sleep(3000)?
Thread.sleep(3000);
synchronized (o) {
o.notifyAll();
}
}
}
线程0:start...
线程3:start...
线程4:start...
线程1:start...
线程2:start...
线程4:end...
线程0:end...
线程3:end...
线程1:end...
线程2:end...
- 为什么要sleep(3000),否则代码无法继续执行?
- Thread.sleep()睡眠的是当前线程?
- 线程需要被唤醒(超时唤醒或调用notify/notifyll)
- 线程唤醒后需要竞争到锁(monitor)### 1. join()
A线程中调用B线程的join()方法,A线程会被阻塞进入阻塞状态(Blocked),直到B线程执行完成
package com.lt.thread;
/**
* 线程常用的方法:join()
* join():A线程中调用B线程的join()方法,A线程会被阻塞,直到B线程执行完成
* @author lt
* @date 2019年4月9日
* @version v1.0
*/
public class Thread_04 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=1; i<=10; i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
});
for(int i=1; i<=10; i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
if(i==7){
t1.start();
t1.join();
}
}
}
}
结果
main-1
main-2
main-3
main-4
main-5
main-6
main-7
Thread-0-1
Thread-0-2
Thread-0-3
Thread-0-4
Thread-0-5
Thread-0-6
Thread-0-7
Thread-0-8
Thread-0-9
Thread-0-10
main-8
main-9
main-10
管道输入/输出流利用内存作为媒介,来进行线程间的通信;管道输入/输出流包含4种实现:PipedOutputStream,PipedInputStream,PipedWriter,PipedReader
package com.lt.thread07;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.nio.CharBuffer;
import java.util.Scanner;
/**
* 使用管道输入/输出流进行线程之间的通信
* @author lt
* @date 2019年5月18日
* @version v1.0
*/
@SuppressWarnings("all")
public class PipedTest_1 {
public static void main(String[] args) throws IOException {
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter();
reader.connect(writer);
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
System.out.print("请输入信息:");
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
try {
writer.write(scanner.next());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}, "线程1");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
try {
int len = 0;
while((len=reader.read())!=-1){
System.out.print((char)len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}, "线程2");
t1.start();
t2.start();
}
}
请输入信息:你好!
你好!
线程1接收输入信息,在接收到输入信息后,将其写入PipedWriter中,线程2输出线程1接收到的信息,通过PipedReader打印输入的信息,这样就完成了线程之间的通信;在使用Piped之前,需要调用connect方法进行绑定输入和输出流
ThreadLocal用于给每个线程提供局部变量,使得一个线程可以在生命周期内去操作这个局部变量;比如Connection对象,在service层开启了事务,却需要在dao层提交/回滚事务,常规你可以将Connection对象作为形参向下传递至dao层,但是有了ThreadLocal,你便可以使用ThreadLocal调用set方法来存放Connection对象,并自动绑定到当前线程,方法执行到dao层,便可以使用使用get方法去获取Connection对象,然后去提交/回滚事务
package com.lt.thread08;
/**
* @author lt
* @date 2019年5月19日
* @version v1.0
*/
public class ThreadLocal_1 {
public static void main(String[] args) throws Exception {
ThreadLocal name = new ThreadLocal<>();
ThreadLocal age = new ThreadLocal<>();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
name.set("李华");
//只能获取当前线程set的值
System.out.println(name.get());
//输出为null
System.out.println(age.get());
}
}, "线程1");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
age.set(23);
//输出为null
System.out.println(name.get());
//只能获取当前线程set的值
System.out.println(age.get());
}
}, "线程2");
t1.start();
t2.start();
}
}
让当前线程进入阻塞状态(Blocked),暂停指定的时长;睡眠期结束后,线程进入就绪状态(Runnable),并不会立即执行,如果需要立即执行,可以调用sleep(1)
package com.lt.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* sleep()
* 让当前线程进入阻塞状态,暂停指定的时长;睡眠期结束后,线程进入就绪状态(Runnable),并不会立即执行,
* 如果需要立即执行,可以调用sleep(1)
* @author lt
* @date 2019年4月9日
* @version v1.0
*/
@SuppressWarnings("all")
public class Thread_05 {
public static void main(String[] args) throws Exception {
while(true){
//隔一秒打印一次时间
Thread.currentThread().sleep(1000);
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
}
结果
2019-05-07 15:52:54
2019-05-07 15:52:55
2019-05-07 15:52:56
2019-05-07 15:52:57
2019-05-07 15:52:58
2019-05-07 15:52:59
2019-05-07 15:53:00
2019-05-07 15:53:01
2019-05-07 15:53:02
2019-05-07 15:53:03
2019-05-07 15:53:04
2019-05-07 15:53:05
2019-05-07 15:53:06
2019-05-07 15:53:07
sleep()和wait()都会让线程阻塞,但是wait()让线程阻塞后立马释放monitor对象锁,使得其他线程可以获取该对象的monitor对象锁;而sleep()使线程阻塞后,在线程暂停期间一直持有monitor对象锁,其他线程不能进入