多线程闲聊

今天来说一说多线程,首先说一下什么是多线程.多线程就好比你开了一个餐厅,餐厅里就一个服务员,每当有菜做好的时候,就需要服务员随机端两盘菜去上,按理来说应该是越早做好的菜越先上,当然也不排除服务员会先上其他菜.此时问题就来了,如果只有一两个客人还好,如果客人多起来了那还得了.对多线程来说也是这样,不过随着我们的cpu越来越强大,这些问题似乎变得不是问题,但线程安全的问题还是尤为重要.
实现多线程的方式有三种:

  1. 直接继承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(); 
    }
}
  1. 实现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();
    }
}
  1. 实现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里面有四种常用的线程池,分别是

  1. newCachedThreadPool,它可以创建一个巨大的线程池,几乎没有容量上限,当然最大也只能是Integer.MAX_VALUE,它是一个可缓存的线程池,当线程数量超过了处理任务需要的线程数时,就会回收空闲线程(60秒以内未执行任务的线程).,当线程数不足时又会自动增加线程数.
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  1. 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;
    }

很多小伙伴看到这里一定感觉有点头晕,不要慌,我们只关心传进去的参数,从上图可以看到创建一个自定义线程池总共需要穿七个参数,我一一解释一下.

  1. int corePoolSize : 核心线程数量,就是最低也要有这么多线程在线程池中
  2. int maximumPoolSize 最大线程数量,池子最大只能放这么多线程
  3. long keepAliveTime 超时时间,如果有线程执行完任务了,并且超过了超时时间还一直处于空闲状态,那我们不能让它无所事事,直接杀死线程
  4. TimeUnit unit 时间单位,你是超过1秒就杀死空闲线程呢,还是超过1年杀死空闲线程呢,总要说清楚吧
  5. BlockingQueue workQueue 阻塞队列,如果你给了我超出容量的任务,没关系,我把多的放到阻塞队列里面去,如果我有空闲的线程了,我就从阻塞队列里面拿任务来执行
  6. ThreadFactory threadFactory 线程工厂,我线程总不能凭空产生吧,所以我通过它来创建线程
  7. 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();
    }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值