JAVA学习之多线程

本文详细介绍了Java中的进程与线程的区别,线程调度的两种方式,实现多线程的三种方法,以及Runnable接口与Thread类的比较。同时,讲解了线程通信的wait/notify机制,展示了显示锁与隐式锁在解决线程安全问题中的应用。最后,讨论了线程的状态转换,帮助理解多线程的生命周期。
摘要由CSDN通过智能技术生成

Java进程与线程的区别

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

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

Java线程与线程的区别:

  1. 所有与进程相关的资源,都被记录在PCB(进程控制模块)中。
  2. 进程是抢占处理机的调度单位。
  3. 线程属于某个进程,共享进程的资源。
  4. 线程由堆栈寄存器、程序计数器和TCB(线程控制模块)组成。
  5. 线程是CPU调度的最小单位,进程是资源分配的最小单位。

线程调度

1.分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
2.抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),
Java使用的为抢占式调度,CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

实现多线程的方式

1.实现Runnable接口
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //线程任务
        for (int i =0;i++<10;){
            System.out.println("举头望明月"+i);
        }
    }
}
MyRunnable my = new MyRunnable();
Thread tr = new Thread(my);
tr.start();


2.继承Threadpublic class MyThread extends Thread {
    /**
     * RUN方法就是线程要执行的任务方法
     */
    @Override
    public void run() {
        //这里的代码,就是一条新的执行路径
        //这个执行路径的处罚方式,不是调用run方法,而是通过thread对象的start()来启动
        for (int i =0;i++<10;){
            System.out.println("锄禾日当午"+i);
        }
    }
}
MyThread t = new MyThread().start();

3.实现Callable接口
class mr implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            Thread.sleep(1000);
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
            }
            return 100;
        }
    }
Callable<Integer> integerCallable = new mr();
FutureTask<Integer> task = new FutureTask<>(integerCallable);
new Thread(task).start();


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

当然也包括定长线程池,单线程池,周期性任务定长线程池

实现Runnable接口比继承Thread类所具有的优势

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

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

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

Runnable 与 Callable的相同点
都是接口都可以编写多线程程序
都采用Thread.start()启动线程

Runnable 与 Callable的不同点
Runnable没有返回值;
Callable可以返回执行结果Callable接口的call()允许抛出异常;Runnable的run()不能抛出Callable获取返回值

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

线程的相关API

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

1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活
https://blog.csdn.net/evankaka/article/details/44153709#t3
这篇blog中对于这些方法有较完善的展示。

线程通信方法

wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。

由于wait,notify,以及notifyAll都涉及到与锁相关的操作
wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知
notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程

有点类似于我要拉粑粑,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来拉粑粑还是修厕所不得而知,直到有人通知我厕所好了我再接着用。

所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

**

wait(): 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程
notifyAll(): 一旦执行此方法,就会唤醒所有被wait()的线程

**

显示锁与隐式锁解决线程安全问题

线程的安全问题:
什么是线程安全问题呢?
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

上述例子中:创建三个窗口卖票,总票数为100张票
1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
4.在java中,我们通过同步机制,来解决线程的安全问题。

方式一:同步代码块
使用同步监视器(锁)
Synchronized(同步监视器){undefined
//需要被同步的代码
}

public static void main(String[] args) {
        Runnable runna = new BuyTicket();
        new Thread(runna).start();
        new Thread(runna).start();
        new Thread(runna).start();
        new Thread(runna).start();


    }

    static class BuyTicket implements Runnable {
        private int count = 10;
        private Object o = new Object();
        @Override
        public void run() {
            while (true){
//                Object o = new Object();
                synchronized (o) {
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName() + "正在买票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println("余票为:" + count+Thread.currentThread().getName());
                    } else {
                        break;
                    }
                }
            }
        }
    }

方式二:同步方法
使用同步方法,对方法进行synchronized关键字修饰
将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

public static void main(String[] args) {
        Runnable runna = new BuyTicket();
        new Thread(runna).start();
        new Thread(runna).start();
        new Thread(runna).start();
        new Thread(runna).start();
    }

    static class BuyTicket implements Runnable {
        private int count = 10;
        @Override
        public void run() {
            while (true){
                boolean sell = sell();
                if (!sell) {
                    break;
                }
            }
        }
        public  synchronized boolean sell(){
            //如果不是静态的同步方法就是this,是就是类名+class
            if (count>0){
                System.out.println("正在买票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println("余票为:"+count+Thread.currentThread().getName());
                return true;
            }
            return false;
        }
    }

总结:1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身。继承自Thread。class

方式三:JDK5.0新增的lock锁方法

public static void main(String[] args) {
        Runnable runna = new BuyTicket();
        new Thread(runna).start();
        new Thread(runna).start();
        new Thread(runna).start();
        new Thread(runna).start();


    }

    static class BuyTicket implements Runnable {
        private int count = 10;
        //显示锁对象
        private Lock lock = new ReentrantLock();
        @Override
        public void run() {
            while (true){
                lock.lock();
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName() + "正在买票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println("余票为:" + count+Thread.currentThread().getName());
                    } else {
                        break;
                    }
                    lock.unlock();
                }
            }
        }

同步代码块和同步方法都属于隐身锁
显示锁:Lock

转载-----线程状态转换

下面的这个图非常重要!你如果看懂了这个图,那么对于多线程的理解将会更加深刻!
在这里插入图片描述

1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

————————————————
版权声明:本文为CSDN博主「Evankaka」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/evankaka/article/details/44153709

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值