Java-多线程

进程

  • 是指内存中运行的引用程序,每个进程都有一个独立的内存空间。

线程

  • 是进程中的一个执行路径,共享一个内存空间,线程之间可以自己切换,并发执行,一个进程最少有一个线程。
  • 线程实际上是在进程基础上的进一步的划分,一个进程启动以后,里面的若干执行路径又可以划分为若干个线程。

线程的状态

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

线程调度

分时调度

  • 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。

抢占式调度

  • 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)java的使用为抢占式调度。
  • 多线程并不能提高程序的运行速度,但能提高程序的运行效率,使CPU的使用率更高。

同步与异步

同步:排队执行,效率低但是线程安全
异步:同时执行,效率高但是线程不安全

并发与并行

并发:两个或多个时间在同一时间段内发生。
并行:两个或多个事件在同一时刻发生。

继承Thread

每个线程都拥有自己的栈空间,共用一份堆内容

public class Demo1 {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        m.start();
        for(int i =0;i<10;i++){
            System.out.println("main"+i);
        }
    }

public class MyThread extends Thread{
    /*
    *run 方法就是线程要执行的任务方法
    *子线程运行的方法都在子线程中
    *
     */
    @Override
    public void run() {
        //新的执行路径,触发方式不是调用run方法,而是通过Thread对象的start()方法来启动任务
        for(int i =0;i<10;i++){
            System.out.println("run"+i);
        }
    }
}

运行结果:
在这里插入图片描述
通过匿名内部类的方式

public class Demo3 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for(int i = 0;i<10;i++){
                    System.out.println("inner"+i);
                }
            }
        }.start();

        for(int j = 0;j<10 ;j++){
            System.out.println("main"+j);
        }
    }
}

运行结果:
在这里插入图片描述

实现Runnable

相比继承Thread,实现Runnable更具有优势:
1.通过创建任务。给线程分配任务的方式来实现多线程,更适合多个线程同时执行相同的任务的情况。
2.可以避免单继承所带来的局限性。
3.任务与线程本身是分离的,提高了程序的健壮性。
4.线程池技术,接受Runnable类型的任务不接受Thread类型的线程。

public class Demo2 {
    public static void main(String[] args) {
        //1. 创建一个任务对象
        MyRunnable mr = new MyRunnable();
        //2.    创建一个线程,并分配一个任务
        Thread t = new Thread(mr);
        //3.    执行这个线程
        t.start();
        for(int i = 0;i<10;i++){
            System.out.println("main"+i);
        }
    }
}

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            System.out.println("MyRunnable"+i);
        }
    }
}

运行结果:
在这里插入图片描述

线程阻塞

又称为耗时操作。所有比较消耗时间的操作,比如文件读取、等待用户输入

线程的中断

一个线程是一个独立的执行路径,他是否应该结束,应该由自身决定。
应避免外部中断线程,导致线程内的缓存没有来得及释放,致使内存一直被占用。
Thread.interrupted()中断标记,使线程进入catch块,程序员来决定是否使线程死亡。

守护线程

线程分为守护线程和用户线程
用户线程:当一个进程不包含任何一个存活的线程时死亡。
守护线程:当最后一个用户线程死亡时,所有守护线程死亡。
在启动之前设置守护线程。
Thread.setDaemon(true);

线程安全

采用同步代码块
解决方案一:同步代码块(隐示锁)
格式:synchronized(锁对象){}
注意:当一个任务由多线程处理时,锁对象需保持一致

//线程同步synchronized
public class Demo4 {
    public static void main(String[] args) {
        Object o = new Object();
        //线程不安全
        Runnable r = new Ticket();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
    }

    static class Ticket implements Runnable{
        //总票数
        private int count = 10;
        private Object o = new Object();
        @Override
        public void run() {
            //Object o = new Object();    //这里不是同一把锁,所以锁不住
                while (true) {
                    synchronized (o) {
                        if (count > 0) {
                         //卖票
                            System.out.println("正在准备售票");
                            try {
                            Thread.sleep(1000);
                            } catch (InterruptedException e) {
                            e.printStackTrace();
                            }
                            count--;
                            System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
                        }else {
                            break;
                        }

                }
            }
        }
    }
}

解决方案二:使用同步方法(隐示锁)
给方法添加synchronized修饰符来实现同步。
在方法内部操作this的是谁,锁对象是谁
如果同步方法被静态修饰,那就是 类名.class Thread.class
例如:public synchronized boolean sale(){}

public class Demo5 {
    public static void main(String[] args) {
        Runnable r = new Ticket();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();


    }
    static class Ticket implements Runnable{
        
        private int count = 10;
        @Override
        public void run() {
            while(true) {
                 boolean flag = sale();

                    if(flag == false){
                        break;
                    }
            }
        }
        public synchronized boolean sale(){
            if (count > 0) {

                System.out.println("正在售票");
                try {
                    Thread.sleep(1000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + "出票成功,剩余票数为" + count);
            } else {
                return false;
            }
            return true;
        }
    }
}

**解决方案三:**使用显示锁

public class Demo6 {
    public static void main(String[] args) {


        Runnable r = new Ticket();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();


    }
    static class Ticket implements Runnable{
        //显示锁l  ReentrantLock()这里构造方法参数可以传入一个true,为公平锁,默认为false.
        private Lock l = new ReentrantLock();
        private Object o = new Object();
        private int count = 10;
        @Override
        public void run() {
            while(true) {
            		//上锁
                    l.lock();
                    if (count > 0) {

                        System.out.println("正在售票");
                        try {
                            Thread.sleep(1000);

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName() + "出票成功,剩余票数为" + count);
                    }else{
                        break;
                    }
                    //解锁
                    l.unlock();

            }

        }
    }
}

公平锁和非公平锁

公平锁:根据代码执行顺序,先执行的先抢占。
非公平锁:谁抢占的快,谁先执行。

线程死锁

在任何有可能产生锁的方法里,尽可能避免调用另一个产生锁的方法。
如何避免线程死锁:
加锁顺序(线程按照一定的顺序加锁)
加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
死锁检测

线程的状态

线程状态。 线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。
RUNNABLE
在Java虚拟机中执行的线程处于此状态。
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
TERMINATED
已退出的线程处于此状态。

线程的第三种创建方式Callable

主线程给Callable分配一个任务,主线程可以通过.get()拿到Callable的结果

public class Demo8 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> c = new MyCallable();
        FutureTask<Integer> task = new FutureTask<Integer>(c);
        new Thread(task).start();
        //主线程等待子线程完成,获取子线程结果
        int result = task.get();
        System.out.println("子线程返回结果为"+result);
        for(int i =0 ;i<10;i++){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程"+i);
        }
    }
    static class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() {
            for (int i = 0;i<10;i++){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程"+i);
            }

            return 100;
        }
    }
}

线程池

一种数组类型的池,存储多个线程。因为线程在创建和终止时需要耗费时间,为了线程可以反复使用,可以创建一个线程池。

1.缓存线程池.
(长度无限制)
执行流程:
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在,则创建线程 并放入线程池, 然后使用

/*
 * 缓存线程池
 * 长度无限制
 *
 */
public class Demo9 {
    public static void main(String[] args) {
        //向线程池中加入并执行新的任务
        ExecutorService service = Executors.newCachedThreadPool();

        service.execute(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务"+Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务"+Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务"+Thread.currentThread().getName());
            }
        });
        //睡眠一秒钟
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //再次使用线程1,缓存线程被使用
        service.execute(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务"+Thread.currentThread().getName());
            }
        });

    }
}

定长线程池

(长度是指定的数值)
执行流程:
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

/*
*定长线程池
 */
public class Demo10 {
    public static void main(String[] args) {
        //创建并执行长度为2的定长线程池
        ExecutorService service = Executors.newFixedThreadPool(2);

        service.execute(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务"+Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        service.execute(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务"+Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        service.execute(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务"+Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
    }
}

单线程线程池(效果与定长线程池长度设为1一样)
执行流程:
1. 判断线程池 的那个线程 是否空闲
2. 空闲则使用
3. 不空闲,则等待 池中的单个线程空闲后 使用

ExecutorService service = Executors.newSingleThreadExecutor();

周期任务定长线程池.
执行流程:
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

周期性任务执行时:定时执行, 当某个时机触发时, 自动执行某任务 .

/**
 *周期定长线程池
 */
public class Demo11 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
        /**
         * 定时执行一次
         * 参数1. 定时runnable类型的任务
         * 参数2. 定时数字
         * 参数3. 时长数字的单位,TimeUnit的常量指定
         */
        service.schedule(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务"+Thread.currentThread().getName());
            }
        },5, TimeUnit.SECONDS);

    }
}

Lambda表达式

函数式编程思想
在接口只存在一种方法时,删除匿名内部类的类部分,保留参数和方法体。

public class Demo12 {
    /*
    lambda表达式
    函数式编程思想
    **/
    public static void main(String[] args) {
        //冗余的Runnable编写方式
       /* Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Lambda表达式");
            }
        });
        t.start();*/

        Thread t = new Thread(() -> System.out.println("Lambda表达式"));
        t.start();
    }


}

©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页