梳理Java中关于线程的知识

本文详细介绍了Java中线程的创建方式,包括继承Thread、实现Runnable接口、Callable接口以及线程池。讨论了线程安全问题,指出synchronized和Lock在解决线程同步上的异同。此外,还探讨了死锁的概念及其产生原因,以及线程通信的wait和sleep方法。最后,通过实例展示了线程池的使用,包括newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor和newScheduledThreadPool的区别和应用场景。
摘要由CSDN通过智能技术生成

线程的创建 - 继承Thread方式

public class PrimeThread extends Thread {
    @Override
    public void run() {
        method();
    }

    public static void method() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {// 遍历100以内的偶数
                System.out.println(i + ">\t" + Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) {
        PrimeThread primeThread = new PrimeThread();
        primeThread.start();
        method();
    }
}

线程类的方法

public class PrimeThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {// 遍历100以内的偶数
                System.out.println(i + ">\t" + Thread.currentThread().getName());
            }
            if (i % 20 == 0) {
                Thread.yield();// 释放CPU执行权
            }
        }
    }

    public static void main(String[] args) {
        Thread.currentThread().setName("主线程");
        PrimeThread primeThread = new PrimeThread();
        String defaultName = primeThread.getName();
        System.out.println("defaultName = " + defaultName);
        primeThread.setName("新线程");
        primeThread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println(i);
            if (i == 1) {
                try {
                    primeThread.join();// 在主线程执行过程中,使得primeThread线程执行完再继续执行。(插队)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
//        primeThread.stop();//过时了,不建议使用了。
//        primeThread.sleep(1000L);//睡眠1000毫秒,不释放锁
//        primeThread.isAlive();
    }
}

yield()方法 释放当前CPU执行权
join()方法 插队执行,被插队线程被阻塞
stop()方法 结束线程,过时了,不建议使用了。
sleep(1000L) 睡眠1000毫秒,不释放锁
isAlive() 获取线程是否活着


线程优先级

public class PrimeThread extends Thread {
    @Override
    public void run() {
        method();
    }

    public static void method() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {// 遍历100以内的偶数
                System.out.println(i + ">\t" + Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) {
        PrimeThread prime1 = new PrimeThread();
        prime1.setName("线程1");
        PrimeThread prime2 = new PrimeThread();
        prime2.setName("线程2");
        prime1.setPriority(MIN_PRIORITY);
        prime2.setPriority(MAX_PRIORITY);
        prime1.start();
        prime2.start();
    }
}

源码中看出,优先级分3种MIN_PRIORITYNORM_PRIORITYMAX_PRIORITY

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

优先级越高,代表获取CPU分配的几率越大,但是不是100%分配到。

线程的创建 - 实现Runnable接口方式

public class CanRun implements Runnable {

    public void method() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {// 遍历100以内的偶数
                System.out.println(i + ">\t" + Thread.currentThread().getName());
            }
        }
    }

    @Override
    public void run() {
        method();
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new CanRun());
        thread.start();
    }
}

线程安全问题

public class TicketThread implements Runnable {

    private static int ticket = 100;

    public static void main(String[] args) {
        Thread window1 = new Thread(new TicketThread());
        Thread window2 = new Thread(new TicketThread());
        window1.start();
        window2.start();
    }

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(ticket + " - " + Thread.currentThread().getName());
                ticket--;
            } else {
                break;
            }
        }
    }
}

在代码中,两个窗口同时卖票(一个线程代表一个窗口),private static int ticket = 100;就是共享的资源(一百张票),代码运行后:

100 - Thread-1     --> 与Thread-0卖出了相同的票
99 - Thread-1
98 - Thread-1
100 - Thread-0     --> 与Thread-1卖出了相同的票
...

可以看出,在输出票号票数减一这两步的时候,不同的线程交叉在执行,那么带来的问题就是,有可能两个窗口卖出了同一张票。因此,解决问题的关键就是如何保证在同一时间只能由一个线程进行票的打印和减一操作?那么答案就是

public class TicketThread implements Runnable {

    private static int ticket = 100;

    public static void main(String[] args) {
        Thread window1 = new Thread(new TicketThread());
        Thread window2 = new Thread(new TicketThread());
        window1.start();
        window2.start();
    }

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                synchronized (TicketThread.class) {
                    System.out.println(ticket + " - " + Thread.currentThread().getName());
                    ticket--;
                }
            } else {
                break;
            }
        }
    }
}

加上代码块锁,如上,就解决的这个问题,在同一时间只能由一个线程来操作票。
也可使用方法锁,代码如下:

public class TicketThread implements Runnable {

    private static int ticket = 100;

    public static void main(String[] args) {
        Thread window1 = new Thread(new TicketThread());
        Thread window2 = new Thread(new TicketThread());
        window1.start();
        window2.start();
    }

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                ticketDo();
            } else {
                break;
            }
        }
    }

    public static synchronized void ticketDo() {
        System.out.println(ticket + " - " + Thread.currentThread().getName());
        ticket--;
    }
}

synchronized关键字修饰静态方法:那么锁的对象就是类的字节码对象,如TicketThread.class
synchronized关键字修饰成员方法:那么锁的对象就是类的实例对象,如window1、window2
因此,这里需要特别注意,一旦锁的对象不同,那么将有可能达不到线程同步的效果!


JDK1.5后java.util.concurrent.locks.Lock的使用

public class TicketThread implements Runnable {

    private static int ticket = 100;

    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread window1 = new Thread(new TicketThread());
        Thread window2 = new Thread(new TicketThread());
        window1.start();
        window2.start();
    }

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                try {
                    lock.lock();
                    System.out.println(ticket + " - " + Thread.currentThread().getName());
                    ticket--;
                } finally {
                    lock.unlock();
                }
            } else {
                break;
            }
        }
    }

}

通过Lock lock = new ReentrantLock();来锁住代码块,那么Locksynchronized的区别是什么呢?
共同点:都可以实现线程的同步。
不同点:

  • synchronized 是Java的关键字,在底层由jvm来控制。而Lock则是接口,需要手动释放锁。
  • synchronized是在被锁住代码开始的时候获取锁,被锁住代码结束的自动释放锁。而Lock则是需要手动释放锁。
  • synchronized是非公平锁,而Lock可以通过参数控制是否是公平锁。

关于死锁

public class DemoThread {

    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        new Thread(() -> {
            synchronized (lock1) {
                System.out.println(Thread.currentThread().getName() + " - 1");
                synchronized (lock2) {
                    System.out.println(Thread.currentThread().getName() + " - 2");
                }
            }
        }, "线程1").start();
        new Thread(() -> {
            synchronized (lock2) {
                System.out.println(Thread.currentThread().getName() + " - 1");
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName() + " - 2");
                }
            }
        }, "线程2").start();
    }

}

以上代码就有可能产生死锁!

原因分析:当线程1和线程2启动后,线程1拿到了lock1,线程2拿到了lock2;此时线程1若要继续拿lock2,就必须等待线程2释放lock2;但是,线程2若要释放lock2,就必须拿到lock1;因此就产生了一个循环,线程1和线程2谁也拿不到想要的锁,就产生了死锁。

分析可知,产生死锁的原因是多把锁未能合理的分配。当线程拿到一把锁后,再去拿其他锁的时候,就要考虑已经拿到的第一把锁,是不是也是其他线程需要的锁,若其他线程需要第一把锁,同时其他线程还拥有需要的锁,那么就可能产生了无限取锁的循环,即死锁。

线程通信

public class Print100 implements Runnable {

    private int number = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                this.notify();//进入房间,唤醒房间外的线程。
                if (number < 100) {
                    System.out.println(number + " \t==\t " + Thread.currentThread().getName());
                    number++;
                    try {
                        this.wait();//离开房间,释放锁,进入阻塞状态,等待唤醒。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }

    // notify、notifyAll、wait必须使用在synchronized代码块或方法中。
    // notify、notifyAll、wait调用者必须是锁对象。
    public static void main(String[] args) {
        Print100 print100 = new Print100();
        Thread thread1 = new Thread(print100, "线程1");
        Thread thread2 = new Thread(print100, "线程2");
        thread1.start();
        thread2.start();
    }
}

notify()wait()的调用者一定是synchronized (this)中的锁对象。因为锁对象是Print100实例本身,所以notify()wait()可以省略this

wait方法和sleep方法的异同

相同:一旦调用,线程均进入阻塞状态。
不同:

  • 声明位置不同,wait声明在Object中,sleep声明在Thread中。
  • 调用位置不同,sleep可以在任何需要的位置,wait则必须在synchronized同步代码块或者同步方法中调用。
  • 锁的释放不同,在同步代码块或方法中被调用,sleep不会释放锁,wait会释放锁。

线程的创建 - 实现Callable接口方式

public class CallThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int result = 0;
        for (int i = 0; i < 10; i++) {
            result += i;
        }
        return result;
    }

    public static void main(String[] args) {
        CallThread callThread = new CallThread();
        FutureTask<Integer> futureTask = new FutureTask<>(callThread);
        new Thread(futureTask).start();
        try {
            Integer result = futureTask.get();
            System.out.println("result = " + result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

实现java.util.concurrent.Callable接口,构造java.util.concurrent.FutureTask类实例并作为参数创建线程new Thread(futureTask),可以在合适的位置通过get()方法获取线程执行结果。

线程的创建 - 线程池方式

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.execute(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(i);
            }
        });
        executorService.shutdown();
    }
}

通过java.util.concurrent.Executors类的静态方法newFixedThreadPool创建线程池,返回java.util.concurrent.ExecutorService方法,通过ExecutorService对象的executesubmit方法执行线程任务。

  • newCachedThreadPool()
    /**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

通过Executors类的静态方法newCachedThreadPool()可以创建一个线程池,当有任务需要线程时这个线程池会创建一个新的线程,但是如果以前创建的线程是可以重复使用的,那么将会使用以前创建的线程。这些线程池通常会提高执行许多短期异步任务程序的性能。如果之前构造的线程可用,对{@code execute}的调用将重用之前构造的线程。如果没有可用的现有线程,将创建一个新线程并加到池中。未使用60秒的线程将被终止并从缓存中删除。因此,一个足够长时间保持空闲的池将不会消耗任何资源。注意,具有相似属性但细节不同(例如,超时参数)的池可以使用{@link ThreadPoolExecutor}构造函数创建。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

newCachedThreadPool()方法创建的线程池,根据ThreadPoolExecutor的构造方法可以看出,核心线程大小是0个,换句话说,当没有任务时,池中将没有一个线程运行着;线程池中的最大线程数大小是Integer.MAX_VALUE意为近似无限制;线程的保持超时时间是60秒,也就是不难理解未使用60秒的线程将被终止并从缓存中删除这句话了;

总结:newCachedThreadPool()适用于性能较高的服务器,当任务非常的多的时候,可以创建近似无限制的线程来处理任务。

案例测试:

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int x = 0; x < 2; x++) {
            for (int i = 0; i < 4; i++) {
                executorService.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "Hello World");
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "Thank You");
                });
            }
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

pool-1-thread-1Hello World
pool-1-thread-4Hello World
pool-1-thread-2Hello World
pool-1-thread-3Hello World
pool-1-thread-2Thank You
pool-1-thread-3Thank You
pool-1-thread-4Thank You
pool-1-thread-1Thank You
pool-1-thread-4Hello World
pool-1-thread-3Hello World
pool-1-thread-1Hello World
pool-1-thread-2Hello World
pool-1-thread-2Thank You
pool-1-thread-1Thank You
pool-1-thread-3Thank You
pool-1-thread-4Thank You

不难看出,当有4个任务来了线程池创建了4个线程;两秒后又来了4个任务,因为之前的线程被缓存了起来了并且未超60秒所以未被终止销毁,这4个线程继续执行新来的4个任务。

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

submit方法有3个重载的方法,接受Runnable或者Callable类型的任务,返回Future类型对象,Future对象可通过get()方法获取线程运行返回值。

  • newCachedThreadPool(ThreadFactory threadFactory)

(ThreadFactory threadFactory)参数的newCachedThreadPool与不带的区别是可以自定义实现ThreadFactory 接口的类,来控制线程的创建过程。

        ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
            private int count = 1;

            @Override
            public Thread newThread(Runnable runnable) {
                return new Thread(runnable, "线程" + count++);
            }
        });

newCachedThreadPool(new ThreadFactory())用法和newCachedThreadPool()类似。

  • newFixedThreadPool(int nThreads)
    /**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

创建一个线程池,该线程池重用固定数量的线程,而且任务队列是无边界的。在任何时候,最多有nThreads个线程在活动地处理任务。如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到有一个线程可用为止。如果任何线程在关闭前的执行过程中由于失败而终止,那么在需要执行后续任务时,将有一个新的线程替代它。池中的线程将一直存在,直到池显式关闭。

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int x = 0; x < 2; x++) {
            for (int i = 0; i < 4; i++) {
                executorService.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "Hello World");
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "Thank You");
                });
            }
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
pool-1-thread-3Hello World
pool-1-thread-2Hello World
pool-1-thread-1Hello World
pool-1-thread-1Thank You
pool-1-thread-3Thank You
pool-1-thread-1Hello World
pool-1-thread-2Thank You
pool-1-thread-3Hello World
pool-1-thread-2Hello World
pool-1-thread-1Thank You
pool-1-thread-1Hello World
pool-1-thread-3Thank You
pool-1-thread-3Hello World
pool-1-thread-2Thank You
pool-1-thread-1Thank You
pool-1-thread-3Thank You

运行结果显示,无论多久、任务多少,线程一直保持着3个。

  • newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

newFixedThreadPool(int nThreads, ThreadFactory threadFactory)newFixedThreadPool(int nThreads)的区别就是可以自定义线程的创建过程。

  • newSingleThreadExecutor()
    /**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

创建一个执行器,该执行器使用单个工作线程操作一个未绑定队列。但是请注意,如果这个线程在关闭之前由于执行失败而终止,那么在需要执行后续任务时,一个新的线程将取代它。任务保证按顺序执行,并且在任何给定时间内活动的任务不超过一个。与newFixedThreadPool(1)不同的是:返回的执行器保证不会被重新配置以使用额外的线程。

  • newSingleThreadExecutor(ThreadFactory threadFactory)

如果看到这里,newSingleThreadExecutor(ThreadFactory threadFactory)newSingleThreadExecutor()就不上测试代码和运行结果,相信您也一定猜到了结局吧。

  • newScheduledThreadPool(int corePoolSize)
    /**
     * Creates a thread pool that can schedule commands to run after a
     * given delay, or to execute periodically.
     * @param corePoolSize the number of threads to keep in the pool,
     * even if they are idle
     * @return a newly created scheduled thread pool
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

创建一个线程池,该线程池可以安排命令在给定的延迟后运行或定期执行。

示例代码:

public class ThreadPoolTest {
    public static void main(String[] args) {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World");
            }
        }, 2L, TimeUnit.SECONDS);
    }
}

以上代码将实现:程序启动2秒后,将执行任务,输出"Hello World"字符串。

Hello World

示例代码:

public class ThreadPoolTest {
    public static void main(String[] args) {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World");
            }
        }, 2L, 1L, TimeUnit.SECONDS);
    }
}

以上代码将实现:程序启动2秒后,将执行任务,输出"Hello World"字符串,在随后的时间里,将每隔1秒输出一次"Hello World"字符串。

(2秒后) Hello World
(1秒后) Hello World
(1秒后) Hello World
        ...

还有一个方法scheduleAtFixedRate()类似,它与scheduleWithFixedDelay()的区别是什么呢?

scheduleAtFixedRate:以上一个任务开始的时间计时,period时间过去后,检测上一个任务是否执行完毕,①上一个任务执行完毕,立即执行。②上一个任务未执行完毕,等上一个任务执行完毕后立即执行。

scheduleWithFixedDelay:以上一个任务开始的时间计时,period时间过去后,立即执行。

综上所述,scheduleWithFixedDelay不需要考虑上一个任务是否完成,而scheduleAtFixedRate需要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值