多线程学习总结

为什么使用多线程

为了更高效的完成任务和利用CPU资源,现在的操作系统设计为多任务操作系统,而多进程和多线程是实现多任务的方式。

进程和线程

程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径

同步和异步&并发和并行

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

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

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

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

工作原理

  1. 多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。
  2. 具体到java内存模型,由于Java被设计为跨平台的语言,在内存管理上,显然也要有一个统一的模型。系统存在一个主内存(Main Memory), Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory)——调用栈,工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。
  3. 多个线程的执行是并发的,也就是在逻辑上“同时”,而不管是否是物理上的“同时”。如果系统只有一个CPU,那么真正的“同时”是不可能的。多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,将会带来线程调度,同步等问题。

线程调度

分时调度

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

抢占式调度

  • 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性), Java使用的为

抢占式调度

  • CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时 刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使 用率更高。

多线程的创建方法

  1. 继承 Thread 类
  2. 实现 runnable 接口

继承 Thread 类

不推荐本方式来创建线程,原因显而易见:java 不支持多继承,如果继承了 Thread 类就不能再继承其他类了。

public class MyThread extends Thread {

    //run方法就是线程要执行的任务方法

    @Override
    public void run() {
        //这里的代码就是一条新的执行路径
        //这个执行路径是触发方式,不是调用run方法,而是通过thread对象的start方法来启动任务
        for (int i = 0; i < 10; i++) {
            System.out.println("锄禾日当午"+i);
        }
    }
}
//创建一个子类对象
MyThread t = new MyThread() ;
//调用start()
t.start();

通过(new MyThread()).start()执行

实现 runnable 接口

实现接口来创建线程是目前推荐的一种方式,原因也很简单:一个类可以实现多个接口。实现 Runnable 接口并不影响实现类再去实现其他接口。

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //线程的任务
        for (int i = 0; i < 10; i++) {
            System.out.println("锄禾日当午"+i);
        }
    }
}
        //1 创建一个任务对象
        MyRunnable r = new MyRunnable();
        //创建一个线程并给他一个任务
        Thread t = new Thread(r);
        //启动线程
        t.start();
        

Thread.currentThread().getName()获取当前线程的名字

线程的分类

用户线程:当一个进程不包含任何的存活的用户线程时,进行结束
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。

线程状态

  1. New(新创建)
  2. Runnable(可运行)
  3. Blocked(阻塞)
  4. Waiting(等待)
  5. Timed waiting(计时等待)
  6. Terminated(被终止)

具体:

  1. 当使用 new 操作符创建一个线程时,如 new Thread(r),线程还未开始运行,就属于新创建状态。
  2. 一旦调用 Thread 类的 start 方法,线程就处于可运行状态。
  3. 当线程处于阻塞或等待状态时,不运行任何代码且消耗最少的资源。直到重新运行。有如下几种途径让线程进入阻塞或等待状态:
  • 当一个线程试图获取一个内部的对象锁,而该锁被其他线程持有
  • 当线程等待另一个线程通知调度器一个条件时,进入等待状态。比如调用 Object.wait 或 Thread.join 方法,或等待 java.util.concurrent 库中的 Lock 或 Condition 时。
  • 当调用计时等待方法时。比如 Thread.sleep,Object.wait,Thread.join,Lock.tryLock 以及 Condition.await

    4. 线程可由以下两种办法进入终止状态:

  • run 方法的结束而自然死亡
  • 未捕获异常中止了 run 方法而意外死亡

线程安全

线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。如课上的例子,多个线程执行卖票操作时,出现了票数余额为负数的情况,原因是在票数还没有自减的时候,其他进程加入卖掉了票。
解决方法:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束,在java中,我们通过同步机制,来解决线程的安全问题。

方法一:同步代码块
Synchronized(同步监视器){
//需要被同步的代码
}

public class Demo {
    public static void main(String[] args) {
        Object o = new Object();
        //线程不安全
        //解决方案1  同步代码块
        //格式:synchronized(锁对象){
        //
        //      }
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).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关键字修饰

将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
而对于继承自Thread方式,需要将同步方法用static和synchronized修饰

//线程同步synchronized

public class Demo {
    public static void main(String[] args) {
        Object o = new Object();
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //总票数
        private int count = 10;
        @Override
        public void run() {

            while (true) {
                boolean flag = sale();
                if(!flag){
                    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);
                return true;
            }
                return false;

        }
    }
}

方法三 :显示锁lock,创建一个锁对象,参数为true表示公平锁,默认是false 不是公平锁

private Lock l = new ReentrantLock(true);
在需要被同步的代码前痛哟锁对象调用lock()方法上锁,在其后用unlock()方法解锁。

public class Demo {
    public static void main(String[] args) {
        Object o = new Object();
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //总票数
        private int count = 10;
        //参数为true表示公平锁    默认是false 不是公平锁
        private Lock l = new ReentrantLock(true);
        @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();
            }
        }
    }
}

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值