java并发基础(一)

java并发基础(一)

本文主要讲述线程的创建及相关知识,参考《thinking in java》一书第21章,记录本人在学习过程中的笔记

定义任务

  1. 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供,并且实现run方法,例如

    public class RunnableItem implements Runnable{    
        
    	protected int countDown = 10;
    
        private static int taskCount = 0;
    
        private int taskId = taskCount++;
    
        public RunnableItem() {
        }
    
        public RunnableItem(int countDown) {
            this.countDown = countDown;
        }
    
        @Override
        public String toString() {
            return "#" + taskId + "(" + countDown + ")";
        }
    
        @Override
        public void run() {
            while (countDown-- > 0){
                System.out.println(this);
                Thread.yield();
            }
        }
    }
    	
    
  2. Thread.yield():表明将CPU时间让给该进程的其它线程来执行,该选择完全是自愿的,但这只是一个暗示,并没有机制保证它会被采纳。

执行任务

  1. 传统方式,将一个任务提交给一个Thread构造器

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) new Thread(new RunnableItem()).start();
        System.out.println("the end?");
    }
    
  2. 分析结果:具体的运行结果不再说明,需要注意的是在程序一开始就打印了“the end?”,这是因为RunnableItem.run是由不同的线程执行的,此时你仍可以执行main()线程中的其它操作

  3. 使用Executor(java.util.concurrent)管理Thread对象:

    public static void main(String[] args) {
    	ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) 
    		service.execute(new RunnableItem());
        System.out.println("the end?");
    	service.shutdown();
    }
    
  4. shutdown()方法:防止新任务提交给这个Executor,当前线程将继续运行调用shutdown()之前提交的所有任务,该程序将在所有任务完成之后尽快退出

  5. 各种ExecutorService

    名称描述
    newCachedThreadPool在程序执行过程中会创建与所需线程数量相同的线程,然后在它回收旧线程时停止创建新线程
    newFixThreadPool一次性预先执行代价高昂的线程分配,也可以限制线程的数量
    SingleThreadExecutor相当于线程数量为1的newFixThreadPool
  6. 还可以直接继承Thread,调用start()方法即可运行任务

    public class ThreadTest extends Thread{
        
        private static int count = 10;
    
        private final int id = count--;
    
        @Override
        public void run() {
            System.out.println(this);
        }
    
        @Override
        public String toString() {
            return Thread.currentThread().getName();
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++)
                new ThreadTest().start();
        }
    }
    

从任务中产生返回值

  1. Runnable是执行工作的独立任务,但是不返回任何值,需要返回值的话可以实现Callable接口

    public class CallableTask implements Callable<String> {
    
        private int taskId;
    
        public CallableTask(int taskId) {
            this.taskId = taskId;
        }
    
        @Override
        public String call() throws Exception {
            return Integer.toString(taskId);
        }
    }
    
  2. 测试Callable任务:

    public static void main(String[] args) {
    
        ExecutorService service = Executors.newCachedThreadPool();
        ArrayList<Future<String>> results = new ArrayList<>();
        for (int i = 0; i < 10; i++) results.add(service.submit(new CallableTask(i)));
    
        for (Future<String> fs : results) {
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        service.shutdown();
    }
    
  3. submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化,可以用isDone()方法来查询Futrue是否完成。当任务完成时,可以用get()方法获取结果。如果不采用isDone()方法直接使用get(),那么在任务没有结束前,一直会阻塞着。

线程休眠

  1. Thread.sleep()和TimeUnit的sleep都可以进行线程的睡眠,其中TimeUnit是一个枚举量,可以选择不同的单位,例如TimeUnit.SECONDS.sleep(1),表示睡眠一秒。
  2. 对sleep调用可以抛出InterruptedException异常,在run()方法中捕获,因为异常不可以跨线程传播到其它线程(例如main()),所以你必须本地处理任务内部产生的异常。

线程的优先级

  1. 调度器倾向于让优先级高的线程先执行,并不是说优先级低的线程得不到执行,只是执行的频率比较低。
  2. 调用Thread.currentThread的getPriority()和setPriority()可以修改当前线程的优先级。

后台线程

  1. 后台线程(daemon)指的是程序运行的时候后台提供一种通用服务的线程,但种线程并不属于程序中不可或缺的一部分。所以所有的非后台线程结束的时候,程序也会结束,同时会杀死所有的后台线程。反过来说,只要有任何的非后台线程还在运行,那么程序就不会终止

  2. 线程在启动之前调用setDaemon()方法即可设置为后台线程

  3. 通过编写定制ThreadFactory可以定制由Executor创建的线程的属性(后台、优先级、名称)

    public class DaemonThreadFactory implements ThreadFactory {
    	    @Override
    	    public Thread newThread(Runnable r) {
    	        Thread thread = new Thread(r);
    	        thread.setDaemon(true); // 设置为后台线程
    	        return thread;
    	    }
    	}
    
  4. 测试后台线程

    public class DaemonFromFactory implements Runnable {
        @Override
        public void run() {
            while (true){
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                    System.out.println(Thread.currentThread() + " " + this);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            ExecutorService service = Executors.newCachedThreadPool(new DaemonThreadFactory());
    
            for (int i = 0; i < 10; i++) service.execute(new DaemonFromFactory());
    
            System.out.println("all daemons started");
            TimeUnit.MILLISECONDS.sleep(500);
        }
    }
    
  5. 一个由后台线程创建的所有线程都将会自动设置为后台线程。

  6. 后台线程不会运行finally语句,例如一个将会被设置为后台运行的任务的run()方法如下,实践证明不会执行

    @Override
    public void run() {
        System.out.println("starting ADaemon");
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("run run ??");
        }
    }
    
  7. 当最后一个非后台线程终止时,后台线程会突然终止。一旦main()退出,JVM就会关闭所有的后台线程

加入一个线程

  1. 先看下面代码

    class Sleeper extends Thread{
    
        private int duration;
    
        public Sleeper(String name, int duration){
            super(name);
            this.duration = duration;
            start();
        }
    
        @Override
        public void run() {
            try {
                sleep(duration);
            } catch (InterruptedException e) {
                System.out.println(getName() + " is interrupted ? " + isInterrupted());
            }
            System.out.println(getName() + " has awakened");
        }
    }
    
    class Joiner extends Thread {
    
        private Sleeper sleeper;
    
        public Joiner(String name, Sleeper sleeper){
            super(name);
            this.sleeper = sleeper;
            start();
        }
    
        @Override
        public void run() {
            try {
                sleeper.join(); // 需要等待sleeper执行完才可以继续执行
            } catch (InterruptedException e) {
                System.out.println("interrupted");
            }
            System.out.println(getName() + " join completed");
        }
    }
    
    public class Joining{
        public static void main(String[] args) {
            Sleeper sleepy = new Sleeper("sleepy", 1500);
            Sleeper grumpy = new Sleeper("grumpy", 1500);
            Joiner dopey = new Joiner("dopey", sleepy);
            Joiner doc = new Joiner("doc", grumpy);
            grumpy.interrupt();
        }
    }
    
  2. 一个线程可以在其它线程上调用join()方法,作用是这个线程会被挂起,直到指定的其它线程执行完毕才会继续执行。如果在调用join()时加上一个超时参数,即使目标线程在这段时间到期时还没有结束,join()方法总可以返回

  3. 一个线程在该线程上调用interrupt()时,将给该线程一个标志,表明该线程已经被中断,但是异常被捕获时将会清理这个标志(运行上面的代码即可观察到)。

捕捉异常

  1. 运行如下代码,你会发现main中不可以捕获子线程逃逸的异常

    public class ExceptionThread implements Runnable {
    
        @Override
        public void run() {
            throw new RuntimeException("test");
        }
    
        public static void main(String[] args) {
            try {
                ExecutorService executorService = Executors.newCachedThreadPool();
                executorService.execute(new ExceptionThread());
            } catch (RuntimeException e) {
                System.out.println(e.getMessage());
                System.out.println("exception has been handled");
            }
        }
    }
    
  2. 要解决上述问题,需要在创建线程池时,传递一个生成线程的工厂,该工厂实现ThreadFactory类,重写newThread方法,此方法内还需要为新生成的线程附着一个自定义的异常处理器,该处理器需要实现Thread.UncaughtExceptionHandler类

    class ExceptionThread2 implements Runnable {
        @Override
        public void run() {
            Thread t = Thread.currentThread();
            throw new RuntimeException("test"); // 抛出运行时异常,查看是否能够捕获
        }
    }
    
    class MyUncaughtExceptionFactory implements Thread.UncaughtExceptionHandler {
    
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("caught " +  e);
        }
    }
    
    class HandlerThreadFactory implements ThreadFactory{
    
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(new MyUncaughtExceptionFactory());
            return thread;
        }
    }
    
    public class CaptureUncaughtException {
    
        public static void main(String[] args) {
            ExecutorService service = Executors.newCachedThreadPool(new HandlerThreadFactory());
            service.execute(new ExceptionThread2());
            service.shutdown();
        }
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java并发编程是指在Java程序中使用多线程实现并发执行的编程技术。它能有效利用多核处理器的优势,提升程序的性能和响应能力。以下是Java并发编程的基础知识: 1. 线程与进程:Java中的线程是程序中执行的最小单位,线程共享进程的资源,包括内存空间和文件等。多线程可以同时执行不同的任务,相比单线程能更高效地利用系统资源。 2. 线程创建:Java中创建线程有两种方式,一种是继承Thread类,实现run()方法;另一种是实现Runnable接口,重写run()方法。通过调用start()方法启动线程。 3. 线程同步:多个线程在访问共享资源时可能会产生竞争条件,可能会导致数据不一致或者出现死锁等问题。通过使用同步机制来保证线程安全,例如使用synchronized关键字实现对共享资源的互斥访问。 4. 线程通信:线程之间可以通过共享变量来进行通信。使用wait()、notify()和notifyAll()方法实现线程的等待和唤醒。 5. 线程池:线程池是一种管理线程的机制,可以有效控制线程的数量和复用线程资源,避免频繁创建销毁线程的开销。 6. 并发容器:Java提供了一些线程安全的数据结构,如ConcurrentHashMap和ConcurrentLinkedQueue等,用于在多线程环境下安全地操作数据。 7. 原子操作:Java提供了一些原子操作类,如AtomicInteger和AtomicLong等,它们能够保证对共享数据的操作是原子的,不会发生数据不一致的情况。 8. 同步工具类:Java提供了一些同步工具类,如CountDownLatch和CyclicBarrier等,用于控制线程的执行顺序和线程之间的同步。 以上是Java并发编程的基础知识,掌握了这些知识可以更好地利用多线程来提高程序的性能和并发能力。同时也需要注意并发编程可能带来的线程安全问题,合理使用同步机制和并发容器等工具类来保证程序的正确运行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值