Java中的多线程

Java中的多线程

1、线程的创建方式

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class Section_VI {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            MyThread mt = new MyThread();
            MythreadImplRunnable mir = new MythreadImplRunnable();
            MyThreadImplCallable mic = new MyThreadImplCallable();
            FutureTask<String> task = new FutureTask<String>(mic);
            new Thread(mt,"线程1").start();
            new Thread(mir,"线程2").start();
            new Thread(task,"线程3").start();
            Thread.sleep(1000);
            if (task.isDone()){
                System.out.println(task.get());
            }
        }
    }
    
    /**
     * 继承Thread类创建线程任务
     */
    class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
            }
        }
    }
    
    /**
     * 实现Runnable接口创建线程任务
     */
    class MythreadImplRunnable implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
            }
        }
    }
    
    /**
     * 实现Callable接口创建带返回值的线程任务
     */
    class MyThreadImplCallable implements Callable<String> {
    
        @Override
        public String call() throws Exception {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
            }
            return "MyThreadImplCallable线程任务已完成";
        }
    }

以上三种实现方式,都是可以创建出一个线程去执行相应的任务但也有不同点。

继承Thread类、实现Runnable接口、实现Callable接口这三种方法的区别在于:由于Java中不支持类之间多继承,当使用继承Thread类的方式创建线程时,就会导致该类无法继承其它类,去使用其它类中的方法。而实现Runnable接口的方式就可以做到,Java中接口之间支持使用多继承,如果一个类继承了另一个类,那么它可以通过实现Runnable接口从而做到既可以继承一个类,又能作为线程对象去使用。

Callable接口方式去创建的线程对象的特点在于,它可以在子线程运行时或运行结束后,有一个返回值返回到调用线程,调用线程可以根据这个返回值去做一些事情。这是以上两种方式都做不到的。

2、lambda表达式创建线程

JDK8特性之Lambda表达式实现线程创建

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * Lambda表达式简化线程的创建以及使用
     */
    public class Section_VI {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            new Thread(()-> {
            for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
                }
            },"线程1").start();
    
            new Thread(()->{
            for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
                }
            },"线程2").start();
    
            FutureTask<String> task = new FutureTask<String>(()-> {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
                }
                return "MyThreadImplCallable线程任务已完成";
            });
            new Thread(task,"线程3").start();
    
            Thread.sleep(1000);
            if (task.isDone()){
                System.out.println(task.get());
            }
        }
    }

3、线程的状态

线程的整个生命周期经历的不同使用方式,可以定义为线程的状态,共分为六种状态。就绪、运行、阻塞、等待、计时等待、死亡
可以通过Thread.Stat进入源码中查看得出。

     public enum State {
            NEW, //就绪
            RUNNABLE,//运行
            BLOCKED,//阻塞
            WAITING,//等待
            TIMED_WAITING,//计时等待
            TERMINATED;//死亡/停止
        }
  • 就绪:该状态表示,线程对象已经调用了start()方法,但是还没有抢到cpu时间片
  • 运行:线程抢到了cpu时间片,进入cpu中运行
  • 阻塞:由于某些原因,导致线程无法继续往下执行,线程就会被放置到等待区,这种情况大致有:线程往下执行时遇到了synchronized代码块而没有获得锁
  • 等待:调用了wait()方法
  • 计时等待:调用了sleep()方法时
  • 死亡:线程的任务完成或者进行了return ; 操作。

4、线程的名称设置方式

通过查看Thread类的构造方法API,可以了解到在使用new Thread()创建线程对象时,该方法有多个重载可供选择,当我们需要给一个线程对象指定名称时,可以使用public Thread(Runnable target,String name)构造方法,target为线程任务对象,name就是线程名称,可以任意指定一个字符串为线程的名称。

    new Thread(()->{
                System.out.println("线程任务");
            },"线程1").start();

5、线程的休眠

线程的休眠方法为静态方法,主要有两个:

  • Thread.sleep(long millis) 当前正在执行的线程暂停执行指定的毫秒数
  • Thread.sleeo(long millis,int nanos) 当前正在执行的线程暂停执行指定的毫秒数,加上指定的纳秒数

6、线程的阻塞

当多个线程并发执行,遇到了同一个同步代码块时,如果其中一个线程获得了该代码块的锁,那么,其它线程就会进入阻塞状态,一起等待占有锁的线程释放锁。

阻塞一般发生在线程阻塞于锁。

7、线程的中断

使用Thread.interrupted();方法时,会让当前线程中断进入阻塞状态
对于处于sleep,join等操作的线程,如果被调用interrupt()后,会抛出InterruptedException异常。

不可中断的操作,包括进入synchronized段以及Lock.lock(),inputSteam.read()等,调用interrupt()对于这几个问题无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去。

对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)。

对于inputStream等资源,有些(实现了interruptibleChannel接口)可以通过close()方法将资源关闭,对应的阻塞也会被放开。

一般情况下,线程退出可以使用while循环判断共享变量条件的方式,当线程内有阻塞操作时,可能导致线程无法运行到条件判断的地方而导致一直阻塞下去,这个时候就需要中断来帮助线程脱离阻塞。因此比较优雅的退出线程方式是结合共享变量和中断。

 thread = new Thread(new Runnable() {
        @Override
        public void run() {
            /*
             * 在这里为一个循环,条件是判断线程的中断标志位是否中断
             */
            while (flag&&(!Thread.currentThread().isInterrupted())) {
                try {
                    Log.i("tag","线程运行中"+Thread.currentThread().getId());
                    // 每执行一次暂停40毫秒
                    //当sleep方法抛出InterruptedException  中断状态也会被清掉
                    Thread.sleep(40);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //如果抛出异常则再次设置中断请求
                    Thread.currentThread().interrupt();
                }
            }
        }
    });
    thread.start();

8、守护线程

守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默完成一些系统性的服务,比如垃圾回收线程,JIT线程就可以理解为守护线程。
与守护线程相对的是用户线程,用户线程可以认为是系统的工作线程,它会完成这个程序要完成的业务员操作。如果用户线程全部结束,则意味着这个程序无事可做。守护线程要守护的对象已经不存在了,那么整个应用程序就应该结束。因此,当一个Java应用内只有守护线程时,Java虚拟机自然退出。
可以通过Thread.setDaemon设置守护线程。

 public class DaemonDemo {
        public static void main(String[] args) {
            Thread daemonThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            System.out.println("i am alive");
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            System.out.println("finally block");
                        }
                    }
                }
            });
            daemonThread.setDaemon(true);
            daemonThread.start();
            //确保main线程结束前能给daemonThread能够分到时间片
            try {
                Thread.sleep(800);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

daemonThread.setDaemon(true)设置daemonThread为守护线程。
注意:守护线程必须在start之前设置,否则会报错。

9、保证线程安全的方式

保证线程安全主要有三种方式:

  • 同步代码块:使用synchronized关键字修饰一个代码块,参数传入需要锁定的范围(类,对象)。一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。从而保证了线程的安全。当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
  • 同步方法:使用synchronized关键字修饰的方法为同步方法,它锁定的范围是该对象。如果该方法同时还是静态方法,那么锁定的就是这个类。
  • 显示锁Lock:比较常用的为Lock类下的ReentrantLock( )类,它通过在需要进行同步的位置调用lock( )方法进行加锁,在同步位置的末尾进行unlock( )进行释放锁,从而保证线程安全。

10、死锁

线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都是等待某个资源被释放,由于线程被无限期的阻塞,那么程序就不可能正常终止,这样的情况,就称为线程死锁。

死锁举例:

    public class Section_VI_II {
        /**
         * 死锁演示 与 避免死锁演示
         * @param args
         */
        public static void main(String[] args) {
            resource1 r1 = new resource1();
            resource2 r2 = new resource2();
            new Thread(()->{
                synchronized (r1){
                    System.out.println(Thread.currentThread().getName()+"获取了r1,等待获取r2");
                    r1.print();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (r2){
                        System.out.println(Thread.currentThread().getName()+"获取了r2");
                        r2.print();
                    }
                }
            },"线程1").start();
            new Thread(()->{
                synchronized (r2){
                    System.out.println(Thread.currentThread().getName()+"获取了r2,等待获取r1");
                    r2.print();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (r1){
                        System.out.println(Thread.currentThread().getName()+"获取了r1");
                        r1.print();
                    }
                }
            },"线程2").start();
        }
    }
    class resource1{
        public void print(){
            System.out.println("此时资源r1被"+Thread.currentThread().getName()+"占用了");
        }
    }
    class resource2{
        public void print(){
            System.out.println("此时资源r2被"+Thread.currentThread().getName()+"占用了");
        }
    }

产生死锁的条件:

  • 互斥条件,该资源任意一个时刻只由一个线程占用。
  • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:线程已获得的资源在未使用完之前,不能被其他线程强行抢夺,只有自己使用完毕才释放资源。
  • 循环等待条件:若干进程之间形成了一种头尾相接的循环等待资源关系

通过以上的死锁条件得出,避免死锁的方法:

  • 破环互斥条件:这个条件我们没有办法破坏,因为我们使用锁,就是想让它们互斥的(临界资源需要互斥访问)
  • 破坏请求与保持条件:一次性申请全部资源
  • 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  • 破坏循环条件:靠顺序申请资源了预防,按某一顺序申请资源,释放资源则反序释放,破坏循环等待的条件。

11、多线程的通信

线程的通信主要使用的方法:wait() 与 notify() botifyAll()

wait( ) : 线程进入等待状态,并让出时间片。

notify( ) : 唤醒任意一个线程

notifyAll( ):唤醒全部阻塞队列中的线程,谁抢到时间片就谁就执行。

wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。

否则,会出现IllegalMonitorStateException异常

12、经典问题:生产者与消费者问题

Synchronized 和 Lock的区别

1、Synchronized 是内置的java关键字,Lock是一个类

2、Synchronized 无法判断锁的状态,Lock可以判断是否获取了锁

3、Synchronized 会自动释放锁,Lock必须要手动释放锁,否则会出现死锁。

4、Synchronized
时,线程1获取了锁,线程2会等待,如果线程1出现了阻塞,那么线程2依然还在继续等待,Lock锁就不一定会等待下去。

5、Synchronized 可重入锁,不可以中断且非公平。lock可重入锁,可以判断锁(有一个tryLock(
)方法,可以尝试获取锁),非公平(可以自己设置为公平)。

6、适合少量的代码同步问题,Lock适合大量的同步代码。

java实现生产者与消费者问题(会出现虚假唤醒)

    public class SynchronizedVersion {
        //模拟生产者与消费者问题
        public static void main(String[] args) {
            resource r = new resource();
            new Thread(()->{
                try {
                    for (int i = 0; i < 30; i++) {
                        r.increment();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"线程A").start();
    
            new Thread(()->{
                try {
                    for (int i = 0; i < 30; i++) {
                        r.decrement();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"线程B").start();
        }
    }
    //资源类
    class resource{
        private int number = 0;
    
        //对number进行+1
    
        public synchronized void increment() throws InterruptedException {
            //如果资源不为0 说明已经加过了,当前线程进入等待状态
            if (number==1){
                wait();
            }
            number ++;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            this.notifyAll();
        }
        //对number进行-1
        public synchronized void decrement() throws InterruptedException {
            if (number==0)
                wait();
            number --;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            this.notifyAll();
        }
    }

以上代码中,如果仅有两个线程时,程序正常运行。当线程数量大于2时,就会出现虚假唤醒的问题。

在代码

    if (number==1){
        wait();
    }

中,如果同时出现两个线程进入if体内,进行wait操作时,此时如果其中一个线程被唤醒后,它并不会去判断此时的number是否依然是等于1的,而是直接进行加1操作,接着,有意思的事情就来了,该线程运行完了之后,如果把同为阻塞的另一个线程唤醒了,另一个线程依然不会进行number==1的判断,而进行了number++,那么,代码多线程中的运行就出现了问题。该问题成为虚假唤醒,那么怎么解决呢?

通过查看jdk文档,wait方法的描述中

    synchronized( obj ){
    	while(条件){
    		obj.wait([毫秒数])
    	}
    }

推荐使用while将wait方法包起来,这样就可以在线程被唤醒时,如果不满足条件,就依然执行wait操作,解决了虚假唤醒的问题。

正确的实现方法,if判断改成while判断:

public class SynchronizedVersion {
        //模拟生产者与消费者问题
        public static void main(String[] args) {
            resource r = new resource();
            new Thread(()->{
                try {
                    for (int i = 0; i < 30; i++) {
                        r.increment();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"线程A").start();
    
            new Thread(()->{
                try {
                    for (int i = 0; i < 30; i++) {
                        r.decrement();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"线程B").start();
    
            new Thread(()->{
                try {
                    for (int i = 0; i < 30; i++) {
                        r.increment();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"线程C").start();
    
            new Thread(()->{
                try {
                    for (int i = 0; i < 30; i++) {
                        r.decrement();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"线程D").start();
        }
    }
    //资源类
    class resource{
        private int number = 0;
    
        //对number进行+1
    
        public synchronized void increment() throws InterruptedException {
            //如果资源不为0 说明已经加过了,当前线程进入等待状态
            while (number==1){
                wait();
            }
            number ++;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            this.notifyAll();
        }
        //对number进行-1
        public synchronized void decrement() throws InterruptedException {
            while (number==0)
                wait();
            number --;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            this.notifyAll();
        }
    }

JUC版实现

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class JUCVersion {
        public static void main(String[] args) {
        resource1 r1 = new resource1();
        new Thread(()->{for (int i = 0; i < 10; i++)r1.increment();},"线程A").start();
        new Thread(()->{for (int i = 0; i < 10; i++)r1.decrement();},"线程B").start();
        }
    }
    class resource1{
        private int number = 0;
        private Lock lock = new ReentrantLock();
        //使用Lock对象,创建监视器
        Condition condition = lock.newCondition();
    
        //对number进行+1
        public void increment() {
            //加锁
            lock.lock();
            try {
                //如果资源不为0 说明已经加过了,当前线程进入等待状态
                while (number==1){
                    //当前线程进入等待,调用Condition.await()将在等待之前以原子方式释放锁,并在等待返回之前重新获取锁。
                    condition.await();
                }
                number ++;
                System.out.println(Thread.currentThread().getName()+"->"+number);
                //唤醒全部等待线程
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //释放锁
                lock.unlock();
            }
    
        }
    
        //对number进行-1
        public void decrement(){
            //加锁
            lock.lock();
            try {
                //如果资源不为0 说明已经加过了,当前线程进入等待状态
                while (number==0){
                    //当前线程进入等待
                    condition.await();
                }
                number --;
                System.out.println(Thread.currentThread().getName()+"->"+number);
                //唤醒全部等待线程
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    }

JUC拓展版,指定多个线程按顺序执行

 import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class JUCPlusVersion {
        public static void main(String[] args) {
            resource2 r2 = new resource2();
            new Thread(() -> { for (int i = 0; i < 10; i++) r2.showA(); }, "线程A").start();
            new Thread(() -> { for (int i = 0; i < 10; i++) r2.showB(); }, "线程B").start();
            new Thread(() -> { for (int i = 0; i < 10; i++) r2.showC(); }, "线程C").start();
        }
    }
    class resource2{
        private int number = 1;
        private Lock lock = new ReentrantLock();
        //使用Lock对象,创建监视器
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();
    
        public void showA(){
            lock.lock();
            try {
                while(number != 1){
                    //condition1进入等待
                    condition1.await();
                }
                System.out.println(Thread.currentThread().getName()+"=>"+number);
                number = 2;
                //唤醒condition2
                condition2.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }
        public void showB(){
            lock.lock();
            try {
                while(number != 2){
                    //condition2进入等待
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName()+"=>"+number);
                number = 3;
                //唤醒condition3
                condition3.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }
        public void showC(){
            lock.lock();
            try {
                while(number != 3){
                    //condition3进入等待
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName()+"=>"+number);
                number = 1;
                //唤醒condition1
                condition1.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }
    }

13、线程池技术

线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。

java中常用的线程池有:

Executors.newFixedThreadPool(nThreads):创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。Executors.newCachedThreadPool():创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,
保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

Executors.newScheduledThreadPool(nThreads):创建一个定长线程池,支持定时及周期性任务执行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值