线程创建有三种方式:
继承Thread类并重写run方法
package threadtxt;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
/***
* 继承Thread 类并重写run方法
*/
public static class MyThread extends Thread{
@Override
public void run() {
System.out.println("i am a child thread");
}
}
public static void main(String[] args) {
// 创建线程
MyThread myThread = new MyThread();
// 启动线程
myThread.start();
}
}
注意点:
当线程创建完thread对象后该线程并没有启动,直到调用了start方法才真正的启动了线程。调用start方法后线程没有马上执行,而是进入了就绪状态,这个时候已经获取了除CPU资源外的其他资源,等待获取CPU资源后才会真正处于运行状态。run方法执行完毕之后,该线程就处于终止状态。
具体可以参考 : Java多线程:概念
实现Runnable接口的run方法
package threadtxt;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
/***
* 实现Runnable接口
*/
public static class RunableTask implements Runnable{
@Override
public void run() {
System.out.println("i am a child thread");
}
}
public static void main(String[] args) {
RunableTask runableTask = new RunableTask();
new Thread(runableTask).start();
new Thread(runableTask).start();
}
}
实现Callable接口,使用FutureTask方式
package threadtxt;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
/***
* 使用FutureTask方式实现
*/
public static class CallerTask implements Callable<String>{
@Override
public String call() throws Exception {
return "hello";
}
}
public static void main(String[] args) {
// 创建异步任务
FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
// 创建线程并启动
new Thread(futureTask).start();
try {
// 等待任务执行完毕,返回结果
String rtn = futureTask.get();
System.out.println(rtn);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
知识点
在程序里面调用start方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法。但是不可多次调用start方法,在第一次调用start方法后,再次调用start方法会抛出异常,因为start方法中在使用前会先判断threadStatus变量是否为0,如果不为0则抛出异常
Java多线程的等待/通知机制是基于Object类的wait()方法和notify()、notifyAll()方法来实现。
notify()方法会随机唤醒一个正在等待的线程,而notifyAll()会叫醒所有正在等待的线程
线程调用wait方法时,该线程就会被阻塞挂起,直到发生下面几件才会返回:
知识点
调用wait方法的线程没有事先获取该对象的监视器锁,则调用wait方法时,会抛出illegalMonitorStateException异常
问题1:一个线程如何才能获取一个共享变量的监视器锁?
(1) 执行synchronized同步代码块时,使用该共享变量作为参数
synchronized(共享变量){
//业务逻辑
}
(2)调用该共享变量的方法,并且该犯法使用了synchronized修饰
synchronized void add(int a,int b){
//业务逻辑
}
一个线程从挂起状态变为运行状态(被唤醒),即使该线程没有被其他线程调用notify()、notifyAll()方法进行通知,或者是被中断、或者等待超时
问题1:怎么防止虚假唤醒?
不停的去测试该线程被唤醒的条件是否满足,不满足则继续等待,即是在一个循环中调用wait方法进行防范。退出循环的条件是满足了唤醒该线程的条件。
synchronized(obj){
while(条件是否满足){
obj.wait();
}
}
以上代码中的while()循环,则不能用if代替,如果使用if则只会判断一次,当下次条件不满足时 ,线程也会唤醒。所以等待应该出现在循环当中
一般并发线程操作的步骤:
知识点
当前线程调用共享变量wait方法后只会释放当前变量上的锁,如果当前线程还持有其他共享变量锁,则这些锁是不会被释放的。
wait(long timeout)相比wait()方法多了一个超时参数,如果一个线程调用共享对象的该方法挂起后,没有在指定的timeout ms 时间内被其他线程调用该共享变量的notify()或者notifyAll()方法唤醒,那么该函数还是会因为超时而返回。
知识点
wait(0)方法和wait()方法效果一样,因为在wait方法内部就是调用了wait(0)。需要注意的是,如果在调用函数时,传递了一个负的timeout则会抛出illegalArgumentException异常。
线程调用notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。
知识点
- 一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。
- 被唤醒的线程不能马上从wait方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到该共享对象的监视器锁,因为其他线程会和当前线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。
- 只有当前线程获取到了共享变量的监视器锁后,才可以调用该共享变量的notify方法,否会抛出illegalMontitorStateException异常。
notifyAll方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
怎么快速理解下线程中join()方法???
想象一下,你现在在排队买奶茶,快要到你的时候,突然来个非常漂亮的妹子说:帅哥,可以让我先买吗?(这个时候想,长得还可以,让你先买吧,等你买完就到我了,说不定让你先买,还能留个微信号,交个朋友),然后你说:可以啊,于是妹子站在你前面买奶茶,可是这个时候,她的七大姑八大姨都来了,都排在那个妹子前面,你从前面的第三位,直接变成第N位。这个时候只能感叹,早知道我就先买了。线程中的join方法就是这个道理了。让其他的线程先执行完毕后,然后自己在执行操作。
举个栗子
package threadtxt2;
import java.util.concurrent.TimeUnit;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread tA = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" child tA over!!! ");
});
Thread tB = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" child tB over!!! ");
});
tA.start();
tB.start();
System.out.println("wait all child");
//等待子线程执行完毕,返回
tA.join();
tB.join();
System.out.println("over all child");
}
}
返回结果
wait all child
child tB over!!!
child tA over!!!
over all child
知识点
join()方法是Thread 类的一个实例方法。它的作用是让当前线程陷入“等待”状态,等待join的这个线程执行完成后,再继续执行当前线程。
有时候,主线程创建并启动了子线程,如果子线程需要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。
如果主线程想等待子线程执行完毕后,获得子线程中的处理完的某个数据,就要用到join方法
注意点
join()方法由两个重载方法,一个是join(long),一个是join(long,int)。
实际上,通过源码发现,join()方法及其重载方法底层都是利用了wait(long timeout)这个方法
对于join(long,int),通过查看源码(JDK1.8)发现,底层并没有精确到纳秒,而是对第二个参数做了简单的判断和处理
当一个执行中的线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有状态。
指定的睡眠时间到了后该函数就会正常返回,线程处于就绪状态,然后参与CPU调度,获取CPU资源后就可以继续运行了。
知识点
在调用sleep方法的线程,睡眠期间其他线程调用了interrupt()方法中断了该线程,则该线程会在调用sleep方法的地方抛出InterruptedException异常返回
当一个线程调用yield方法时,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程。
当前线程调用sleep方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度其他线程。
调用yield方法时,线程只是让出自己剩余的时间片,并没有挂起,而是出于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行
java中的线程中断是一种线程间的协作模式,线程中断机制是一种协作机制,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。
Thread类里提供的关于线程中断的几个方法:
在线程的中断机制里,当其他线程通知需要被中断的线程后,线程中断的状态被设置为true,但是具体被要求中断的线程要怎么处理,完全由被中断线程自己而定,可以在适合的实际处理中中断请求,也可以完全不处理继续执行下去。
死锁:指两个或者两个以上的线程在执行过程中,因争夺资源而造成的相互等待的现象。在无外力作用的情况下,这些线程会一直相互等待而无法继续运作下去。
死锁的产生必须满足以下四个条件:
Java中的线程分为两类,分别为daemon线程和user线程。
知识点
JVM的退出和用户线程有关,和守护线程无关。只要有一个用户线程没有结束,正常情况下JVM就不会退出。
守护线程默认的优先级比较低。一个线程默认是非守护线程,可以通过Thread类的setDaemon(boolean on )来设置
java中用ThreadGroup来表示线程组,我们可以使用线程组对线程进行批量控制。
每个Thread必然存在于一个ThreadGroup中,Thread不能独立于ThreadGroup存在。
ThreadGroup管理着它下面的Thread,ThreadGroup是一个标准的向下引用的树状结构。这样设计的原因是防止上级线程被下级线程引用而无法有效地被GC回收。
Java中线程优先级可以指定,范围是1~10。但是并不是所有操作系统都支持10级优先级的划分,java只是给操作系统一个优先级参考值,线程最终在操作系统的优先级还是由操作系统决定。
知识点
java默认的线程优先级为5,线程的执行顺序有调度程序来决定,线程的优先级会在线程调用前设定。通常情况下,高优先级的线程比低优先级有更高的几率得到执行。我们使用方法Thread类的setPriority()方法来设定线程的优先级
获取当前的线程组名字
Thread.currentThread().getThreadGroup().getName()
复制线程组
//复制一个线程数组到一个线程组
Thread[] threads = new Thread[threadGroup.activeCount()];
ThreadGroup threadGroup = new ThreadGroup();
threadGroup.enumerate(threads);
线程组统一异常处理
public static void main(String[] args){
ThreadGroup threadGroup1 = new ThreadGroup("group1") {
// 继承ThreadGroup并重新定义以下⽅法
// 在线程成员抛出unchecked exception
// 会执⾏此⽅法
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t.getName() + ": " + e.getMessage());
}
};
// 这个线程是threadGroup1的⼀员
Thread thread1 = new Thread(threadGroup1, new Runnable() {
public void run() {
// 抛出unchecked异常
throw new RuntimeException("测试异常");
}
});
thread1.start();
}
线程组的数据结构
线程组还可以包含其他的线程组,不仅仅是线程。
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
private final ThreadGroup parent; // ⽗亲ThreadGroup
String name; // ThreadGroupr 的名称
int maxPriority; // 线程最⼤优先级
boolean destroyed; // 是否被销毁
boolean daemon; // 是否守护线程
boolean vmAllowSuspension; // 是否可以中断
int nUnstartedThreads = 0; // 还未启动的线程
int nthreads; // ThreadGroup中线程数⽬
Thread threads[]; // ThreadGroup中的线程
int ngroups; // 线程组数⽬
ThreadGroup groups[]; // 线程组数组
}
构造函数:
// 私有构造函数
private ThreadGroup() {
this.name = "system";
this.maxPriority = Thread.MAX_PRIORITY;
this.parent = null;
}
// 默认是以当前ThreadGroup传⼊作为parent ThreadGroup,新线程组的⽗线程组是⽬前正在运⾏线
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
// 构造函数
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}
// 私有构造函数,主要的构造函数
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
this.name = name;
this.maxPriority = parent.maxPriority;
this.daemon = parent.daemon;
this.vmAllowSuspension = parent.vmAllowSuspension;
this.parent = parent;
parent.add(this);
}
第三个构造函数⾥调⽤了 checkParentAccess ⽅法,这⾥看看这个⽅法的源码:
// 检查parent ThreadGroup
private static Void checkParentAccess(ThreadGroup parent) {
parent.checkAccess();
return null;
}
// 判断当前运⾏的线程是否具有修改线程组的权限
public final void checkAccess() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkAccess(this);
}
}
知识点
这⾥涉及到 SecurityManager 这个类,它是Java的安全管理器,它允许应⽤
程序在执⾏⼀个可能不安全或敏感的操作前确定该操作是什么,以及是否是
在允许执⾏该操作的安全上下⽂中执⾏它。应⽤程序可以允许或不允许该操
作。
⽐如引⼊了第三⽅类库,但是并不能保证它的安全性。
其实Thread类也有⼀个checkAccess()⽅法,不过是⽤来当前运⾏的线程是
否有权限修改被调⽤的这个线程实例。(Determines if the currently running
thread has permission to modify this thread.)
总结来说,线程组是⼀个树状的结构,每个线程组下⾯可以有多个线程或者线程
组。线程组可以起到统⼀控制线程的优先级和检查线程的权限的作⽤。
多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步。同步的措施是加锁,这就需要使用者对锁有一定的了解。
问题:有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己的变量?
ThreadLocal可以做这个事情,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。
如果开发者希望将类的某个静态变量(UserID或者transaction ID )与线程状态关联,则可以考虑使用ThreadLocal。最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等,数据库连接和Session管理涉及多个复杂对象的初始化和关闭,如果在每个线程中声明一些私有变量来进行操作,那这个线程就变得不那么轻量了,需要频繁的创建和关闭连接。