Java之多线程基础

进程与线程

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

线程:进程中运行的单元,是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程至少有一个线程

线程调度

1.分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间

2.抢占式调度:优先让优先级高的线程使用cpu,如果优先级相同则随机选择一个,java使用的就是抢占式调度

注意:cpu使用抢占式调度模式在多个线程间进行着高速的切换。对于cpu的一个核心而言,某个时刻只能执行一个线程,而cpu在多个线程间的切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实多线程并不能提高程序的运行速度,但能够提高运行效率,让cpu的使用率更高。老的说法是并没有真正的多线程,所谓多线程是cpu快速的切换来实现的,但现在有多核,存在真正意义上的多线程。

线程分类

用户线程:是独立存在的,不会因为其他用户线程退出而退出。

守护线程(后台线程):daemon :等待用户线程都执行完了,再自动结束

设置线程为守护线程:Thread.setDaemon(true);

注意:如果要将一个线程设置成守护线程,需要在start之前进行设置,若在start之后设置就没有效果了

异步与同步

异步:同时执行,效率高但是不安全

同步:排队执行,效率低但是安全

并发与并行

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

并行:指两个或多个事件在同一时刻发生(同时发生)

创建线程的三种方式

1.继承Thread类,重写run方法

使用步骤:

  1. 编写类继承Thread类 , 重写run方法 class MyThread extends Thread { @Override public void run() { xxxxxxxxxxxx } }

  2. 创建MyThread对象 , MyThread thread = new MyThread();

  3. 通过Thread,启动线程 thread().start();

2.实现Runnable接口,重写run方法

使用步骤:

  1. 编写类实现Runnable接口 , 实现run方法 class MyTask implements Runnable{ @Override public void run() {xxxxxxxxxxxxxxxxxx } }

  2. 创建MyTask 对象 , 并传入第一步编写的Callable类对象 MyTask myTask = new MyTask();

  3. 通过Thread,启动线程 new Thread(myTask ).start();

3.实现Callable接口,重call方法

使用步骤:

  1. 编写类实现Callable接口 , 实现call方法 class CallableTask implements Callable { @Override public call() throws Exception { return T; } }

  2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象 FutureTask future = new FutureTask<>(new CallableTask());

  3. 通过Thread,启动线程 new Thread(future).start();

注意:Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

Runnable和Callable接口的比较

1.都是接口,都可以编写多线程程序,都采用start方法启动线程

2.Runnable没有返回值,Callable可以返回执行的结果

3.Callable接口的cal方法允许抛出异常,Runnable中的run方法不能抛出

Thread和Runnable创建线程的区别

1.继承Thread是单继承,不适合资源共享;Runnable是接口,可同时实现多个接口,且容易实现资源共享

2.实例化线程方式不同:

Thread: new MyThread().start()

Runnable: Runnable myThread = new MyThread(); new Thread(myThread).start()

3.使用Runnable,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4.线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

Thread类常用方法

1、start()

启动线程 调用此方法后,系统才会创建线程执行任务,并为之分配相应的资源

2、run()

继承Thread类必须重写run方法,在run方法中定义具体要执行的任务,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务

3、sleep(longmillis)

sleep(longmillis)*//参数为毫秒*
sleep(longmillis,intnanoseconds)*//第一个参数为毫秒,第二个参数为纳秒*

4、yield()

调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程;它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

5、join()

join()
join(longmillis)*//**参数为毫秒*
join(longmillis,intnanoseconds)*//**第一个参数为毫秒,第二个参数为纳秒*

假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间

6、interrupt()(中断)

以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。

7、stop()(不安全)

调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致。所以stop方法基本是不会被用到的。

8、 setDaemon(boolean on)

将此线程标记为daemon(守护)线程或用户线程。

9、currentThread()

Thread类有一个比较常用的静态方法currentThread()用来获取当前线程。

关系到线程属性的几个方法

1)getId

用来得到线程ID

2)getName和setName

用来得到或者设置线程名称。

3)getPriority和setPriority

用来获取和设置线程优先级。

4)setDaemon和isDaemon

用来设置线程是否成为守护线程和判断线程是否是守护线程。

线程优先级与调度

线程优先级可用数字1-10表示,默认是5

Thread.MIN_PRIORITY=1//最小优先级

Thread.MAX_PRIORITY=10//最大优先级

Thread.NORM_PRIORITY=5//默认优先级

Thread.getPriority()获取线程对象优先级

Thread.setPriority()方法设置优先级

java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪些线程来执行。多数线程的调度时抢先式的 ,即如果在当前线程执行过程中,一个更高优先级的线程进入运行状态,则这个线程立即被调度执行。

线程的状态(生命周期)

创建(new) 就绪(runnable) 运行(running) 阻塞(blocked)

睡眠(timewaiting) 等待(waiting) 消亡(dead)

图片11111111111111

线程安全问题

情景:火车站几个窗口同时进行卖票,票一共10张

public class ThreadTest {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
    }
}
class Ticket implements Runnable{
    int countTicket = 10;
    @Override
    public void run() {
        while(true){
            if(countTicket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countTicket--;
                System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + countTicket);
            }else{
                break;
            }
        }
    }
}

在这里插入图片描述

出现了线程安全问题,余票出现了负数

线程安全问题是怎么造成的?

  1. 多个线程访问共享(资源)变量

  2. 对操作共享变量的代码有多条(在两个操作代码之间可能有多个线程,就会造成问题)

解决:

1.同步代码块

class Ticket implements Runnable{
    int countTicket = 10;
    Object o = new Object();
    @Override
    public void run() {
        while(true){
            synchronized (o){
                if(countTicket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    countTicket--;
                   System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + countTicket);
                }else{
                    break;
                }
            }
        }
    }
}

此处的o为线程安全锁(可以看成钥匙,将代码块看成锁)保证了代码中只有一块执行,处理共享变量的代码是单线程
在这里插入图片描述

2.同步方法

class Ticket implements Runnable{
    private int countTicket = 10;
    Object o = new Object();
    @Override
    public void run() {
        while(true){
            boolean flag = sale();
            if(!flag){
                break;
            }
        }
    }
    private synchronized boolean sale(){
        if(countTicket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countTicket--;
            System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + countTicket);
            return true;
        }
        return false;
    }
}

在这里插入图片描述

注意:

1.成员实例方法的锁默认为当前实例(对象)或是this

2.静态方法的锁是class对象

3.显式锁Lock

class Ticket implements Runnable{
    private int countTicket = 10;
    private Lock lo = new ReentrantLock();//显式锁
    @Override
    public void run() {
        while(true){
            lo.lock();
            if(countTicket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countTicket--;
                System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + countTicket);
            }else{
                break;
            }
            lo.unlock();
        }
    }
}

在这里插入图片描述

显式锁和隐式锁

隐式锁:synchronized修饰的对象或方法就是隐式锁

显式锁:由Lock定义的锁,更符合面向对象,有上锁、解锁的步骤

公平锁和非公平锁

公平锁:线程以抢占式的方式随机获取锁,synchronized的就是公平锁

非公平锁:Lock类锁,按线程申请顺序排队进入

synchronized锁和Lock锁的区别

1.synchronized锁是公平锁,Lock锁是非公平锁

2.synchronized底层依赖于软件层面上的JVM,而Lock类依赖于特殊的CPU指定,可以认为不受JVM的约束,并可以通过其他语言平台来完成底层的实现。在并发量较小的多线程应用程序中,Lock类与synchronized性能相差无几,但在高并发量的条件下,synchronized性能会迅速下降几十倍,而ReentrantLock的性能却能依然维持一个水准,因此我们建议在高并发量情况下使用ReentrantLock。

3.synchronized在发生异常时会自动释放锁,不会发生异常死锁;Lock在发生异常时不会自动释放锁,需要在finally中释放锁

4.Lock锁可以中断,synchronized锁必须等待线程任务执行完在释放锁

死锁

定义:当一个进程申请资源时,如果这时没有可用资源,那么这个进程进入等待状态。有时,如果所申请的资源被其他等待线程占有,那么该等待进程有可能再也无法改变状态,这种情况成为死锁。

死锁的必要条件
1.互斥:至少有一个资源处于非共享模式,即一次只有一个进程可以使用。如果另一个进程申请资源,就必须等待该资源被释放为止。

2.请求和保持:一个线程应占有至少一个资源,并等待另一个资源,而该资源为其他线程所占有,等待该资源被释放,并保持自己的资源不被释放。

3.不剥夺:资源不能被抢占,只能等待被释放。

4.循环等待:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有。

如何避免死锁:

1.加锁顺序(线程按照一定的顺序加锁)

2.加锁时限(线程尝试获取锁的时候加上一定的时限,超时则放弃对该锁的请求,并释放自己占有的资源)

3.死锁检测

线程通信:

 void notify();//唤醒在此对象监视器上等待的单个线程。
 void notifyAll();//唤醒在此对象监视器上等待的所有线程。
 void wait();//导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法
 void wait(longtimeout);//导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。
 void wait(longtimeout, int nanos);//导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量

生产者和消费者

public class Demo1 {
    public static void main(String[] args) {
        Food food = new Food();
        new Cook(food).start();
        new Waiter(food).start();
    }
    //厨师
    static class Cook extends Thread{
        private Food food;
        public Cook(Food food){
            this.food=food;
        }
        @Override
        public void run(){
            for(int i=0;i<100;i++){
                if(i%2==0){
                    food.setNameAndTaste("重庆辣子鸡","麻辣味");
                }else{
                    food.setNameAndTaste("凉拌黄瓜","酸辣味");
                }
            }
        }
    }
    //服务员
    static class Waiter extends Thread{
        private Food food;
        public Waiter(Food food){
            this.food=food;
        }
        @Override
        public void run(){
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                food.get();
            }
        }
    }
    //饭菜
    static class Food{
        private String name;
        private String taste;
        public void setNameAndTaste(String name,String taste){
            this.name=name;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste=taste;
        }
        public void get(){
            System.out.println("服务员端走的饭菜是:" + this.name + "味道是:" + this.taste);
        }
    }
}

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

饭菜味道出现了错乱

解决:

public class Demo1 {
    public static void main(String[] args) {
        Food food = new Food();
        new Cook(food).start();
        new Waiter(food).start();
    }
    //厨师
    static class Cook extends Thread{
        private Food food;
        public Cook(Food food){
            this.food=food;
        }
        @Override
        public void run(){
            for(int i=0;i<100;i++){
                if(i%2==0){
                    food.setNameAndTaste("重庆辣子鸡","麻辣味");
                }else{
                    food.setNameAndTaste("凉拌黄瓜","酸辣味");
                }
            }
        }
    }
    //服务员
    static class Waiter extends Thread{
        private Food food;
        public Waiter(Food food){
            this.food=food;
        }
        @Override
        public void run(){
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                food.get();
            }
        }
    }
    //饭菜
    static class Food{
        private String name;
        private String taste;
        private boolean flag = true;
        public synchronized void setNameAndTaste(String name,String taste){
            if(flag){
                this.name=name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste=taste;
                flag=false;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            if(!flag){
                System.out.println("服务员端走的饭菜是:" + this.name + "    味道是:" + this.taste);
                flag=true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

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

线程池 Executors

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

优势:

1.降低资源消耗

2.提高响应速度

3.提高线程的可管理性

Java中的四种线程池:

1.缓存线程池

        /**
         * 缓存线程池.
         * (长度无限制)
         * 执行流程:
         * 1. 判断线程池是否存在空闲线程
         * 2. 存在则使用
         * 3. 不存在,则创建线程 并放入线程池, 然后使用
         */
        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());
            }
        });

2.定长线程池

/**
 * 定长线程池.
 * (长度是指定的数值)
 * 执行流程:3. 单线程线程池
 4. 周期性任务定长线程池
 * 1. 判断线程池是否存在空闲线程
 * 2. 存在则使用
 * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
 * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
 */
        ExecutorService service = Executors.newFixedThreadPool(2);
        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());
            }
        });

3.单线程线程池

/**
 * 效果与定长线程池 创建时传入数值1 效果一致.
 * 单线程线程池.
 * 执行流程:
 * 1. 判断线程池 的那个线程 是否空闲
 * 2. 空闲则使用
 * 4. 不空闲,则等待 池中的单个线程空闲后 使用
 */
                ExecutorService service = Executors.newSingleThreadExecutor();
        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());
            }
        });

4.周期性任务定长线程池

/**
 * 周期任务 定长线程池.
 * 执行流程:
 * 1. 判断线程池是否存在空闲线程
 * 2. 存在则使用
 * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
 * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
 *
 * 周期性任务执行时:
 * 定时执行, 当某个时机触发时, 自动执行某任务 .*/
            ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
 * 定时执行
 * 参数1. runnable类型的任务
 * 参数2. 时长数字
 * 参数3. 时长数字的单位
 */
/*service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
},5,TimeUnit.SECONDS);
*/
/**
 * 周期执行
 * 参数1. runnable类型的任务
 * 参数2. 时长数字(延迟执行的时长)
 * 参数3. 周期时长(每次执行的间隔时间)
 * 参数4. 时长数字的单位
 */
            service.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            },5,2, TimeUnit.SECONDS);
        }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值