【Java基础】多线程

线程及与进程的区别

线程也被称为是轻量级的进程,是程序执行的最小单元。有四种状态:运行,就绪,挂起和结束。一个进程拥有多个线程,这些线程共享进程的一些资源如打开的文件,代码段,数据段和堆空间。

使用线程的优点在于:
1. 更好的交互性,GUI程序中可以将耗时操作单独一个线程,从而使程序具有更好的交互性。
2. 和进程相比,线程的开销更小,而且多线程在数据共享方面更加方便,效率更高
3. 多线程可以充分利用多CPU或者多核的并行执行能力
4. 多线程可以更好的简化程序的结构,使得程序更加利用理解和维护。将复杂任务的进程分解为多个线程

同步和异步

简单来说,同步存在等待,而异步不需要等待结果。因此为了实现同步,必须获得线程对象的锁,而其他想要同时获得该锁的对象只能等待,直到锁被释放。
实现同步可以利用同步代码块来实现也可以利用同步方法来实现。同步往往造成系统的瓶颈,所以尽量避免无谓的同步。

异步可以按非阻塞的思路来理解,例如每个线程都有运行时所需要的数据,在进行IO时可以不必等待其他线程的结果或者状态,也不必等待IO完毕之后才返回。

Java多线程实现的方式

主要有三种,继承Thread,实现Runnable,实现Callable
1. 继承Thread并重写run()方法:新建线程类之后,新建一个线程并调用其start()方法(该方法仅仅使线程变为就绪)。调用之后并不是立即执行的,而是等待OS的调度
2. 实现Runnable接口:常用的方法,和实现Thread类似,只是多了一步(将实现该Runnable接口的类作为Thread的参数实例化一个线程,剩下的和1一样)
3. 实现Callable接口重写call方法:Callable实际是Executor框架中的类,功能比Runnable强大。

实现Callable接口的功能体现在:
1. Callable接口的call方法在执行完成后会返回一个结果
2. call方法可以抛出异常
3. 通过Callable可以得到一个Future对象,Future是一个异步计算结果的对象,可以用来检查计算是否完成。

实例:


    class MyThread1 extends Thread{
    public void run(){
        System.out.println("hello world 1");
        }
    }

    class MyThread2 implements Runnable{

        @Override
        public void run() {
            System.out.println("hello world 2");
        }
    }

    class MyThread3 implements Callable<String>{

        @Override
        public String call() throws Exception {

            return "hello world 3";
        }

    }

    public class hello {

        public static void main(String[]  args){
            // Thread
            MyThread1 thread1 = new MyThread1();
            thread1.start();

            // Runnable
            MyThread2 thread2 = new MyThread2();
            Thread t = new Thread(thread2);
            t.start();

            // Callable
            ExecutorService threadPool = Executors.newSingleThreadExecutor();
            Future<String> future = threadPool.submit(new MyThread3());
            try {
                System.out.println(future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }

        }
    }

run()和start()

Thread类的对象既可以调用start()也可以调用run(),但是两者有本质的不同。如果仅仅是调用run()那么就和普通的成员方法调用是没有区别的,并不会开启多线程。而调用start()方法会异步的开启一个新的线程并等待被JVM调度执行,调度执行过程中会调用run()执行逻辑,run()执行完之后线程结束。

可以通过简单的例子验证,刚开始做多线程的时候总是分不清彼此

线程同步方法

Java提供了三种线程同步机制:
1. synchronize关键字:既可以修饰代码块也可以修饰方法。Java中每一个对象都有一个对象锁,在被synchronize修饰之后,如果想要执行同步代码块,就必须先获取该对象的对象锁。
2. wait()和notify():在同步代码块被执行期间,其他想获取同一个对象锁的线程可以调用wait()进入等待,并可以被notify()或者notifyAll()唤醒,然后去获取被释放的锁。
3. Lock接口和实现类ReentrantLock实现类:Lock提供了多种方法实现多线程的同步。 1)lock()阻塞方式获取锁,如果获取了锁就直接返回,否则等待直到获得;2)tryLock():非阻塞的方式获取锁,如果尝试获取成功,返回true否则返回false; 3)tryLock(long timeout, TimeUnit unit);给定时间内尝试获取锁,在指定时间之内获得就返回true否则超时false;4)lockInterruptibly():如果获取锁就立刻返回,如果没有那么线程状态变为休眠,直到获取锁或者当前线程被别的线程中断(也就是在lock的基础上加了中断唤醒)。

sleep()和wait()

两个方法都是将当前线程暂停执行一段时间的方法。区别如下:
1. 原理方面:sleep()是Thread的静态方法,是线程自身进行流程控制的方法,它会暂停本线程,把执行机会给其他线程,而且当睡眠时间到之后,自动唤醒。wait()属于是Object的方法。用于进程间同步或者通信,该方法使当前拥有该对象锁的线程等待,直到被notify或者notifyAll唤醒。
2. 对锁的处理方面:由于sleep只是暂停本线程执行,不涉及线程间的同步和通信,因此不会释放锁。而wait()调用后,调用线程就释放了锁,其他线程就可以执行同步代码块
3. 使用区域:sleep()就像一个特殊功能的方法,可以用在任何地方,而wait()是用于线程同步的,所以用在同步方法或者同步语句块中。

注意:
sleep()调用的时候必须捕获异常,因为可能会被interrpt()中断产生InterruptException;

补充:sleep()和yield()区别
1. sleep暂停线程之后,会把执行机会让给所有线程而不考虑优先级;yield只把执行机会给同优先级或者更高优先级的线程。
2. sleep会让当前线程进入阻塞状态,所以sleep()后的线程在指定时间内是不会执行的,而yield()只是使当前线程让步到可执行状态,因此可能被立刻调度执行
3. sleep()会抛出异常,而yield()不会,同时yield由于是和系统相关,所以移植性差一些

终止线程的方法

Java中终止线程的方法有stop()和suspend()。调用stop停止的时候,会释放所有的已经锁定的资源,使用stop的时候可能会造成不一致状态,导致程序不稳定;使用suspend由于不会释放锁,因此可能造成死锁。
一种安全的结束线程的方式就是让线程自然终止,结束run()的执行,线程就结束了。例如常用的重写run()时会在方法中写while(flag),那么可以通过其他线程修改该flag来控制该run的执行。如果需要被终止的线程当前是休眠状态,那么修改flag并没有什么用,那么可以通过interrut()中断唤醒,相应的run()中需要实现对该中断事件的捕获,然后执行结束逻辑。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值