上一篇文章java多线程基础知识(一)讲到了线程的优先级,状态,Daemon线程等,这里继续线程的基础知识.
线程的启动和结束
上篇文章的demo中,都是用start方法启动线程,当run方法运行结束后,线程自动结束,当我们new一个Thread的时候
java Thread daemonThread = new Thread(daemonRunner, "daemonThread");
可以去看他的构造方式,都做了一些什么事儿
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
最终的init方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
//获取其父线程,即实当前线程
Thread parent = currentThread();
//获取java安全管理器
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* 分配线程ID */
tid = nextThreadID();
}
上面过程中,新初始化的线程是由其父线程来配置的,子线程继承了他的一些属性.
线程中断
中断时线程的一个标示位,表示一个线程是否被其他线程终端过.
- 调用该线程的interrupt()进行终端操作
- 检查自身是否被中断isInterrupted()
- 调用静态方法interrupted()对当前线程的中断标示位进行复位
先看代码示例:
/**
* 线程终端
* Created by gzd on 2017/1/11.
*/
public class ThreadInterrupted {
//一直睡觉的线程
private static Runnable sleep = () ->{
while (true){
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//一直运行的线程
private static Runnable go = () ->{
while (true){
}
};
public static void main(String[] args) throws InterruptedException {
Thread sleep = new Thread(ThreadInterrupted.sleep, "sleepThread");
sleep.setDaemon(true);
Thread goThread = new Thread(go, "goThread");
goThread.setDaemon(true);
sleep.start();
goThread.start();
TimeUnit.SECONDS.sleep(5);
sleep.interrupt();
goThread.interrupt();
System.out.println("sleep是否被中断 "+sleep.isInterrupted());
System.out.println("go是否被中断 "+goThread.isInterrupted());
TimeUnit.SECONDS.sleep(3);
}
打印结果是:
sleep是否被中断 false
go是否被中断 true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at cn.gzd.concurrency.base.ThreadInterrupted.lambda$static$0(ThreadInterrupted.java:14)
at cn.gzd.concurrency.base.ThreadInterrupted$$Lambda$1/1096979270.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
因为sleep调用了sleep方法,sleep方法被中断会抛出InterruptedException异常,而jvm在抛出这个异常后,会清除中断位,所以结果是false,而go线程被中断,标识位正常显示为true!
//sleep被中断,抛出异常
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
线程的暂停,恢复和停止
概念很简单,不多说,直接看代码:
/**
* 线程的暂停,恢复和通停止
* Created by gzd on 2017/1/11.
*/
public class PrintThread {
private static Runnable print = ()->{
while (true){
System.out.println("打印线程运行时间是: "+ LocalDateTime.now());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
public static void main(String[] args) throws InterruptedException {
Thread printThread = new Thread(print, "printThread");
printThread.setDaemon(true);
printThread.start();
TimeUnit.SECONDS.sleep(3);
printThread.suspend();
System.out.println("线程在"+LocalDateTime.now()+"暂停");
TimeUnit.SECONDS.sleep(3);
printThread.resume();
System.out.println("线程在"+LocalDateTime.now()+"恢复");
TimeUnit.SECONDS.sleep(3);
printThread.stop();
System.out.println("线程在"+LocalDateTime.now()+"停止");
}
}
可以看到suspend(),resume(),stop()这三个方法,暂停,恢复,停止.其实他们已经过期了,不建议使用了.因为他们在执行的时候,会占着锁而睡眠状态,所以会造成死锁.至于不用这个那用什么?后面会提到的!
终止线程
终止线程,可以用Thread.Interrupted(),也可以用一个变量来控制线程的结束.
public class StopThread {
public static void main(String[] args) throws InterruptedException {
Runner runner = new Runner();
Thread thread = new Thread(runner);
Runner runner1 = new Runner();
Thread thread1 = new Thread(runner1);
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
thread1.start();
TimeUnit.SECONDS.sleep(1);
runner1.stop();
System.out.println(runner.count);
System.out.println(runner1.count);
}
private static class Runner implements Runnable{
private long count = 1L;
private volatile boolean ifStop = Boolean.TRUE;
@Override
public void run() {
while (ifStop && !Thread.currentThread().isInterrupted()){
count++;
}
}
public void stop(){
ifStop = Boolean.FALSE;
}
}
}
线程间通讯
volatile和synchronized
多个线程可以对同一个对象操作,其实,每个线程操作的并不一定就是这个目标对象本身,因为每个线程都可以有这个变量的拷贝,这样的目的是加快速度,所以当前线程看到的并不是最新的对象.如果要保持每个线程对此对象的操作都是最新的,那么在对象这里,就要设置同步.
- volatile关键字的作用,就是告诉程序,这个变量,必须从内存中获取,操作之后,再刷新到内存.滥用会降低效率!
- synchronized关键字可以修饰方法,或者代码块儿,功能就是在同一时间,只有一个线程可以操作这块区域.
查看java字节码文件,可以看到,同步快这里使用可monitorenter和monitorexit两个命令,核心就是对对象的监控器monitor获取,这个过程是排他的.所有的java对象都会有自己的监控器,程序执行到同步代码这的时候,必须获得这个对象的监控器才能进去同步代码块中,没有获取的话,在排在队列中,状态更改为BLOCKED状态
下图描述了对象监控器,同步队列,对象,线程之间的关系:
等待/通知
如果一个线程修改了一个对象之后,需要把结果通知给另一个线程,这样的需求该怎么做,如果采用轮训的办法,那就不太好了,其实java的OBject对象,已经有了这些方法:
/**
* 当一个线程在对象上等待,调用这个方法,就会让线程从wait()方法返回
* 前提是该线程获取到了锁
*/
public final native void notify();
/**
* 同上,区别就是它会通知在该对象上等待的所有线程
*/
public final native void notifyAll();
/**
* 让该线程进入WAITING状态,调用这个方法后,该线程会释放锁
* 该方法还有其他重载方法,如果传入long参数,那么就是等待这么久
*/
public final native void wait() throws InterruptedException;
总结一下,所谓的等待/通知,无非围绕这几个方法,当A线程调用了对象obj的wait方法进入等待状态,另一个线程B调用了obj的notify()或notifyAll()方法,线程A会受到通知后,从wait()方法返回,继续执行下面的逻辑.
Thread.join()
如果一个线程执行了这个方法,意义就是:当前线程等待thread线程停止后,才从join()返回并继续执行.例如:有两个线程A和B,A线程启动中,B调用了A.join(),那么B久得等A执行完,B才会继续执行,下面代码示例,给出了main线程和A线程的执行顺序:
public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
Thread main = Thread.currentThread();
for (int i = 0; i < 5; i++) {
Thread a = new Thread(new A(main));
a.start();
}
//sleep更直观,给for循环充分的时间,否则直接往下执行了,看不出效果
TimeUnit.SECONDS.sleep(5);
System.out.println("我是main线程,你调用了我的join,就算我睡着了,你也得等我执行完你再执行");
}
private static class A implements Runnable {
private Thread thread;
public A(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
}
System.out.println("我是A线程,我调用了main线程的join方法");
}
}
}
可以复制下来,自己去执行感受一下~
线程的一些基础知识就先写到这里,不一定是全的,后面还会写线程的高级一点儿的东西!