2451 2018-04-11 2020-06-25
前言:多线程一直是Java里面的一个重点,一个难点,这次我们来简单看下。
一、线程的实现
多线程的实现可以通过两种方式,一是继承Thread类,二是实现Runable接口,这两者的本质是一样的,区别在于是否需要规避Java中的单继承机制。
1、Runnable接口
查看代码,如下
public interface Runnable {
// 只有一个抽象方法
public abstract void run();
}
2、Thread类
public class Thread implements Runnable {
private static native void registerNatives();
static {
registerNatives();
}
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
// 省略大部分代码
@Override
public void run() {
if (target != null) {
target.run();
}
}
public Thread() {
// 首先会执行init方法,初始化线程
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
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 (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
}
从上面的代码中,我们就可以看出,当把一个Runnable接口作为参数传进Thread类的构造方法时,Thread内部还是会把当作一个Thread看待,两者只是形式不同。
二、线程的生命周期
线程是程序执行的顺序控制流,它与进程一样,具有生命周期。在Java中,它有6种状态。
1、NEW
初始状态,当创建一个Thread类或其子类的对象时,新生成的线程对象就处于新建状态。这是系统还没有为线程对象分配资源,线程尚未启动。
处于新建状态的线程只能被启动或终止,若试图调用线程的其他处理方法则会提示失败。
2、RUNNABLE
运行状态,运行状态的线程,可在规定时间内(时间片)执行run()方法中的任务。当使用完系统分配的时间后,系统会剥夺该线程占用的CPU资源,让其他线程获得执行的机会。如果任务还没完成,则该线程进入等待状态,等待下次获得CPU资源;如果已完成线程任务,则该线程进入终止状态;如果正在执行的线程遇到特殊情况,也可进入阻塞状态。
3、WAITING
等待状态,当线程对象调用了start()方法后,就进入了等待状态。处于该状态的线程进入就绪线程队列排队等待CPU的使用权。
4、BLOCKED
阻塞状态,处于运行状态的线程在某些特殊情况下,会主动放弃或被动剥夺CPU的使用权,进入阻塞状态。下面列出了会导致运行线程进入阻塞状态的几种情况
- 调用sleep(long millis)方法。
- 当在一个线程中调用另一个线程的join方法时,也会使线程进入阻塞状态。
- 当线程调用wait()方法时,线程进入阻塞状态。
- 当线程调用一个阻塞式的IO方法时,线程会进入阻塞状态。
- 当线程试图获取某个对象的同步锁时,如果该锁被其他线程所持有,则当前线程进入阻塞状态。
进入阻塞状态的线程不能进入就绪队列排队,只有当引起阻塞的原因被消除时,线程才可以转入就绪状态,重新进到线程队列中排队等待CPU的使用权,以便从原来终止处开始继续运行。由于进入阻塞状态的不同,从阻塞状态进入就绪状态也存在下面几种情况。
- 因执行sleep(long millis)方法引起的阻塞,只需等待线程休眠时间到了以后,线程就会自动进入就绪状态。
- 因调用join()方法引起的阻塞,需要等到新加入的线程运行结束后才会结束阻塞状态,进入就绪状态。
- 因调用wait()方法引起的阻塞,需要调用notify()方法唤醒该线程,进入就绪状态。
- 因调用阻塞式IO方法引起的阻塞,只需等到该IO方法结束,就会进入就绪状态。
- 因同步锁被占用而引起的阻塞,只需获取其他线程所持有的锁,就会进入就绪状态。
5、TIME_WAITING
超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的。
6、TERMINATED
终止状态,线程的run()方法正常执行完毕,或者线程抛出一个为捕获的异常(Exception)、错误(Error),被提前强制性地终止,线程就进入终止状态。处于终止状态的线程不具有继续运行的能力,也不能转换到其他状态。
三、线程的调度
1、优先级
Java中默认采用基于抢占式的分时调度策略,即首先让优先级高的线程优先占用CPU,对优先级相同的线程按照分时策略进行调度。
在Java中定义了3个默认的优先级别及设置优先级别的方法,如下
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
需要补充的是,优先级高仅仅是获取CPU使用权的机会更大些,并不是一定的。
2、线程休眠与让步
- 线程休眠:是指让线程放弃执行的权利,进入阻塞状态,给其他线程运行的机会,指定时间后,再次进入就绪队列排队。
- 线程让步:是指让线程放弃执行的权利,进入等待状态,给相同或更高优先级的线程提供运行的机会。
在Java中定义了相关的方法,如下
public static native void sleep(long millis) throws InterruptedException;
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
public static native void yield();
正如所见,都是native方法,这都是操作系统级别的操作。
3、线程插队
线程插队是指让一个线程插入到另外一个线程里面,直到该线程执行完毕,另外一个线程才会继续。
在Java中定义了相关方法,如下
// 等待该线程终止的时间最长为 millis 毫秒,其底层还是依赖于wait
public final void join() throws InterruptedException {
join(0);
}
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
四、线程的同步
线程默认是不同步,因此会出现很多对于数据脏读或脏写的情况。Java提供了两种同步机制,来保证线程同步。
1、同步代码块
所谓的同步代码块,就是将处理共享资源的代码放置于一个代码块中,使用synchronized关键字修饰,外加一个锁对象。
2、同步方法
同步方法和同步代码块类似,只不过是用synchronized关键字来修饰方法。对于单个对象方法,锁对象就是对象本身;针对静态方法,锁对象就是Class对象;
五、示例代码
/**
* 开三个线程,依次循环打印012 345 678,即a打印0,接b打印2,接c打印2,接a打印3....
* 在调用wait、notify方法时,需要获取对象的监视器monitor——即先获取这个对象锁,否则将抛出IllegalMonitorStateException异常。
*/
public class WaitAndNotify {
private static AtomicInteger i = new AtomicInteger(0);
private static final int target = 7;
public static void main(String[] args) throws InterruptedException {
Object lockAB = new Object();
Object lockBC = new Object();
Object lockCA = new Object();
Thread a = new Thread(new Runnable() {
@Override
public void run() {
while (i.get() < target) {
synchronized (lockCA) {
try {
lockCA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lockAB) {
System.out.println(Thread.currentThread() + ":" + i.getAndIncrement());
lockAB.notifyAll();
}
}
}
}, "线程一");
Thread b = new Thread(new Runnable() {
@Override
public void run() {
while (i.get() < target) {
synchronized (lockAB) {
try {
lockAB.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lockBC) {
System.out.println(Thread.currentThread() + ":" + i.getAndIncrement());
lockBC.notifyAll();
}
}
}
}, "线程二");
Thread c = new Thread(new Runnable() {
@Override
public void run() {
while (i.get() < target) {
synchronized (lockBC) {
try {
lockBC.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lockCA) {
System.out.println(Thread.currentThread() + ":" + i.getAndIncrement());
lockCA.notifyAll();
}
}
}
}, "线程三");
c.start();
b.start();
a.start();
Thread.sleep(500);
synchronized (lockCA) {
lockCA.notifyAll();
}
}
}
// 输出如下
Thread[线程一,5,main]:0
Thread[线程二,5,main]:1
Thread[线程三,5,main]:2
Thread[线程一,5,main]:3
Thread[线程二,5,main]:4
Thread[线程三,5,main]:5
Thread[线程一,5,main]:6
Thread[线程二,5,main]:7
Thread[线程三,5,main]:8
更多关于多线程的特性,请查看多线程系列后续章节,里面有更为细致的讨论。
总访问次数: 254次, 一般般帅 创建于 2018-04-11, 最后更新于 2020-06-25
欢迎关注微信公众号,第一时间掌握最新动态!