今天来说一说多线程,首先说一下什么是多线程.多线程就好比你开了一个餐厅,餐厅里就一个服务员,每当有菜做好的时候,就需要服务员随机端两盘菜去上,按理来说应该是越早做好的菜越先上,当然也不排除服务员会先上其他菜.此时问题就来了,如果只有一两个客人还好,如果客人多起来了那还得了.对多线程来说也是这样,不过随着我们的cpu越来越强大,这些问题似乎变得不是问题,但线程安全的问题还是尤为重要.
实现多线程的方式有三种:
- 直接继承Thread类,然后重写run()方法
public class ThreadTest extends Thread {
@Override
public void run() {
System.out.println("run方法被重写");
}
}
class Demo1{
public static void main(String[] args) {
ThreadTest t = new ThreadTest();
t.start();
}
}
- 实现Runnable接口,在实现类中重写run()方法,然后调用Thread类的有参构造方法,把实现类对象作为参数传递进去从而创建Thread对象.
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println("run方法");
}
}
class Test2{
public static void main(String[] args) {
RunnableImpl runnable = new RunnableImpl();
Thread thread = new Thread(runnable);
thread.start();
}
}
- 实现Callable接口,并在实现类中重写call()方法,然后创建实现类的对象,并把这个实现类对象作为构造方法的参数,创建一个FutureTask对象,最后把FutrueTask对象作为构造方法的参数创建Thread类对象.除此之外call()方法还有返回值,可以在启动线程后通过get方法获取这个返回值
public class CallableImpl implements Callable<String> {
@Override
public String call() throws Exception {
return "call()方法";
}
}
class Test3{
public static void main(String[] args) {
CallableImpl callable = new CallableImpl();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
try {
String res = futureTask.get();
System.out.println(res);
} catch (Exception e) {
}
}
}
这三种方法相对而言最简单的是第一种方法,最常用的是第二种方法,因为实现接口的话还可以继承其他类,可扩展性好.
对我们的java而言,对于线程是抢占式调度,每个线程都有优先级,范围是0-10,默认是5,优先级越高的线程执行的概率越大,但不是一定会执行.查看优先级的方法是final int getPriority()
如果一个线程一直没有获取到cpu的执行权,那么jvm会提高这个线程的优先级.
再来说说多线程中的安全问题,首先要知道为什么会产生线程安全问题,它是因为有多个线程操作一个对象的成员变量,也就是说在多线程的情况下有共享数据并会对共享数据进行操作,线程就是不安全的.这里提一下对我们的servlet也一样,对于单实例的servlet而言,它也是线程不安全的,所以它就必须少设或者不设成员变量.
那么怎么解决线程安全问题呢,很简单,就是加锁.加锁的方式有两种,一种是实用synchronized同步代码块的形式,它同样也可以修饰方法,修饰的方法如果是静态方法,那么锁对象就是该类的class对象,锁的范围就是整个方法
static synchronized void show(){
System.out.println("show方法"); //此时锁对象为Demo1.class
}
如果是成员方法那么锁对象就是该类的对象.
synchronized void say(){
System.out.println("say方法"); //此时锁对象为Demo2的实现类对象
}
关于同步,好处就是保证了线程安全,这没什么多说的,不好的地方就是它降低了程序运行的效率(HashTable表示赞同)
另外一种加锁方式是创建一个ReentrantLock的实例,调用里面的方法 void lock()
,void unlock()
来进行加锁,释放锁.它是JDK1.5之后才出现的,为什么要创建这个类的实例呢,因为它继承了Lock接口,通过它来加锁我们就能清楚的知道锁在哪里加上,又在哪里释放.
既然现在有了锁,就可以保证完事大吉了吗,不一定!玩锁,就会产生一些问题,比如死锁.什么叫死锁呢?比如我和你拿错了手机,现在我不知道你在哪,你也不知道我在哪,但是我手里的手机需要你的指纹才能打开,你手里的手机需要我的指纹才能打开.可是手机不打开我又不能知道你在哪,你说这怎么办?这就是死锁,死锁的产生多半是锁的嵌套或者资源有限,那怎么才能解决呢?很简单就是不要进行锁嵌套…如果非要嵌套就一定要小心.
聊完了锁,再回到线程来说说,大伙想一想,线程每次我要用,我就创建一个线程,用完了我就关闭,是不是有点太浪费资源啦?线程表示我还能行,你别把我当一次性的呀.这时候怎么办呢,我们会想如果有一个地方专门放线程对象,我要用我就拿,用完了我再放回去,是不是可以达到循环利用的情况从而节约了系统资源呢.这时候就要提一个新的概念,线程池.
顾名思义,线程池就是一个专门装线程的容器,在Executors源代码里面可以看出java里面有四种常用的线程池,分别是
- newCachedThreadPool,它可以创建一个巨大的线程池,几乎没有容量上限,当然最大也只能是
Integer.MAX_VALUE
,它是一个可缓存的线程池,当线程数量超过了处理任务需要的线程数时,就会回收空闲线程(60秒以内未执行任务的线程).,当线程数不足时又会自动增加线程数.
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- newFixedThreadPool,它可以创建一个指定长度的线程池,然后可以看出有一个阻塞队列专门来存放超出容量的线程.
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newScheduledThreadPool,它也是创建一个指定长度的线程池,从名字就可以看出来它可以周期性的执行任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
newSingleThreadExecutor,见名知意,它是只存放一个线程的线程池,
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
其实从源码中就可以看出一些端倪了,除了newScheduledThreadPool,其他三个线程池返回的格式都是那么的像,其实newScheduledThreadPool也一样,再往里面走,它仍然是调用这个方法来创建的线程池,这里我也把他贴出来
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
好吧,都到这里了不用我说你们肯定也能知道,还有一种方法可以返回一个线程池,没错,那就是自定义线程池.
我们一起去看一下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
很多小伙伴看到这里一定感觉有点头晕,不要慌,我们只关心传进去的参数,从上图可以看到创建一个自定义线程池总共需要穿七个参数,我一一解释一下.
- int corePoolSize : 核心线程数量,就是最低也要有这么多线程在线程池中
- int maximumPoolSize 最大线程数量,池子最大只能放这么多线程
- long keepAliveTime 超时时间,如果有线程执行完任务了,并且超过了超时时间还一直处于空闲状态,那我们不能让它无所事事,直接杀死线程
- TimeUnit unit 时间单位,你是超过1秒就杀死空闲线程呢,还是超过1年杀死空闲线程呢,总要说清楚吧
- BlockingQueue workQueue 阻塞队列,如果你给了我超出容量的任务,没关系,我把多的放到阻塞队列里面去,如果我有空闲的线程了,我就从阻塞队列里面拿任务来执行
- ThreadFactory threadFactory 线程工厂,我线程总不能凭空产生吧,所以我通过它来创建线程
- RejectedExecutionHandler handler 拒绝策略,拒绝策略大概分为四种
1. AbortPolicy 默认拒绝策略,丢弃线程任务并抛出异常(RejectedExecutionException),一般使用此策略
2. DiscardPolicy 丢弃任务但不抛异常,这就有点尴尬了,你怎么知道它是执行了还是丢弃了呢…所以一般不推荐
3. DiscardOldestPolicy 抛弃阻塞队列中等待最久的任务,然后加入新任务
4. CallerRunsPolicy 直接调用run()方法来绕过线程池执行此任务
如果我们想要一个线程停下来,除了等它的run方法之外,还可以调用sleep(),wati()方法,这两方法会抛出一个InterruptedException
异常,大概说一下为什么要抛这个异常,话不多说先贴源码
首先每个线程都有一个自己的interrupt状态,默认是false,为什么会有这个东西呢,就是有些线程它在里面放一个死循环一直执行下去,浪费系统资源,那么我们就想要停止这种线程,interrupt状态默认是false,代表不中断,如果调用了Thread中的interrupt方法,代表我想让你停下来,当然不代表你一定会停下来,这时候就到了线程选择了,线程不想停也可以不停,至于线程想停要怎么停,看另一段代码
* @throws InterruptedException if any thread interrupted the current thread before or
* while the current thread was waiting. The <em>interrupted status</em> of the
* current thread is cleared when this exception is thrown.
如果interrupted为false则直接返回,如果为true则变为false
public static boolean interrupted() {
Thread t = currentThread();
boolean interrupted = t.interrupted;
// We may have been interrupted the moment after we read the field,
// so only clear the field if we saw that it was set and will return
// true; otherwise we could lose an interrupt.
if (interrupted) {
t.interrupted = false;
clearInterruptEvent();
}
return interrupted;
}
这里则是方法调用后修改interrupt状态
public void interrupt() {
if (this != Thread.currentThread()) {
checkAccess();
// thread may be blocked in an I/O operation
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupted = true;
interrupt0(); // inform VM of interrupt
b.interrupt(this);
return;
}
}
}
interrupted = true;
// inform VM of interrupt
interrupt0();
}
判断一个线程是否死亡的方法isAlive();
线程死亡后不可复活,当线程run方法执行完或者main方法执行完,或者因为其他原因退出虚拟机,线程就会死亡.
/**
* Tests if this thread is alive. A thread is alive if it has
* been started and has not yet died.
*
* @return <code>true</code> if this thread is alive;
* <code>false</code> otherwise.
*/
public final native boolean isAlive();
使线程停止还有两个过时方法stop,suspend;这哥俩都被Deprecated标记,意思是不赞成使用.
@Deprecated(since="1.2")
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
// A zero status value corresponds to "NEW", it can't change to
// not-NEW because we hold the lock.
if (threadStatus != 0) {
resume(); // Wake up thread if it was suspended; no-op otherwise
}
// The VM can handle all thread states
stop0(new ThreadDeath());
}
@Deprecated(since="1.2", forRemoval=true)
public final void suspend() {
checkAccess();
suspend0();
}