多线程实例底层分析等

1.程序,进程,线程理解

程序可调用多个进程,比如一个视频播放器程序,里面就存在两个进程:一个是播放视频的进程,一个是缓存视频的进程。
一个进程又同时调用多个线程
线程可以看作是cpu运行的基本的基本单位,进程可以看作是运行资源的基本单位。程序的一次执行就可以看作是一个进程。进程中又包含了许多的线程,进程之间的内存不可以共享,线程之间共享进程的内存。线程也被称为轻量级进程。

线程的生命周期

当我们new Thread的时候,就会首先分配内存,然后检查资源,之后创建一个线程,并且将该线程的状态改为runnable状态,当该线程获得了cpu的执行权的时候,该线程从可运行状态变成了运行状态,这个时候就有三种可能了,如果cpu的执行时间片用完了,则会重新变成可运行状态,如果被阻塞了,就会变成阻塞状态,如果该线程被销毁了或者退出主线程了,就会进入死亡状态。

单线程和多线程

单线程有两种创建方式,主要是解决CPU的使用权抢占和资源的共享发生了冲突的问题,大部分用锁机制来解决。锁的话又分为乐观锁和悲观锁,乐观锁主要是用cas来保障的,悲观锁就是我们常见的sycronized和lock。多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行(并发)。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。

并发:通过CPU调度算法,让用户看上去同时执行,实际上,是通过CPU在高速切换,并不是真正的额同时
并行:多个CPU实例或者多台机器同时执行一段处理逻辑,这就是真正的同时;

下面讲下乐观悲观锁:
1、悲观锁,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。【数据锁定:数据将暂时不会得到修改】
2、乐观锁,认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息。让用户决定如何去做。

悲观锁相对比较谨慎,设想现实情况应该很容易就发生冲突,所以我还是独占数据资源吧。

乐观锁就想得开而且非常聪明,应该是不会有什么冲突的,我对表使用一个时间戳或者版本号,每次读、更新操作都对这个字段进行比对,如果在我之前已经有人对数据进行更新了,那就让它更新,大不了我再读一次或者再更新一次。

乐观锁的管理跟SVN管理代码版本的原理很像,如果在我提交代码之前用本地代码的版本号与服务器做对比,如果本地版本号小于服务器上最新版本号,则提交失败,产生冲突代码,让用户决定选择哪个版本继续使用。

Synchronized总共有三种用法:
(1)修饰普通方法
(2)修饰静态方法
(3)修饰代码块

Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,进行加减1层层处理,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

使用多线程的方式

一:继承Thread类创建线程
1.类去继承(extends)Thread 类
2.该类重写Thread类的run方法,并且将线程要执行的代码,存放到run方法中
3.线程对象调用start方法,开启线程,线程会自动执行run方法

代码如下:


/**
 * 2 * @Author: Mr.Leason
 * 3 * @Date: 2020/6/4 22:34
 * 4
 */

class ThreadTest extends Thread {

    String name;

    ThreadTest(String name) {

        this.name = name;

    }

//这一步很关键,重写run方法,这里面是线程所要执行的

    public void run() {

        for (int i = 0; i < 3; i++) { //利用currentThread()方法来获取当前正在执行的线程的名称

            System.out.println(Thread.currentThread().getName() + ":" + this.name);

        }

    }

}

public class ThreadDemo {

    public static void main(String[] args) {

        ThreadTest d1 = new ThreadTest("111");//创建线程对象

        ThreadTest d2 = new ThreadTest("222");

        d1.start();//开启线程,执行run方法;但是注意,此时只是具备执行资格,CPU的执行是随机的

        d2.start();

    }

}

结果两次会不同:CPU的执行是随机的
在这里插入图片描述
在这里插入图片描述
二:实现Runnable接口创建线程
1.存放线程执行代码的类去实现(implements) Runnable接口
2.重写所实现接口的run方法,并将线程执行代码存放在run方法中
3.创建Thread对象,也就是创建线程
4.Thread线程对象调用start方法,启动线程


/**
 * 2 * @Author: Mr.Leason
 * 3 * @Date: 2020/6/4 22:45
 * 4
 */

class Demo implements Runnable {

    String name;

    Demo(String name) {

        this.name = name;

    }

    public void run() {

        for (int i = 0; i < 3; i++) {

            System.out.println(Thread.currentThread().getName() + ":" + this.name);

        }

    }

}

public class ThreadTest1 {

    public static void main(String[] args) {

        Demo d1 = new Demo("333");//创建对象

        Demo d2 = new Demo("444");

        Thread t1 = new Thread(d1);//创建线程

        Thread t2 = new Thread(d2);

        t1.start();//开启线程

        t2.start();

    }

}

结果:
在这里插入图片描述
在这里插入图片描述
总结:这两种方式建议是同第二种,第一种采用继承的方式,弊端非常明显,该类继承了Thread线程类就无法再去继承其他类了,这样提高了程序的耦合性,第二开发中常用

三:通过Callable和FutureTask创建线程 重写call方法(有返回值)
1.Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式: 实现 Callable 接口。
2.Callable 接口类似于 Runnable,但是 Runnable 不会返回结果,并且无法抛出经过检查的异常,而 Callable 依赖 FutureTask 类获取返回结果。

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 2 * @Author: Mr.Leason
 * 3 * @Date: 2020/6/4 23:00
 * 4
 */

public class CallableAndFuture {
    public static void main(String[] args) {
        Callable<Integer> callable = new Callable<Integer>() {
            public Integer call() throws Exception {
                return new Random().nextInt(100);
            }
        };
        FutureTask<Integer> future = new FutureTask<Integer>(callable);
        new Thread(future).start();
        try {
            Thread.sleep(5000);// 可能做一些事情
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

结果:
在这里插入图片描述
四:线程池

那么既然已经可以实现多线程,那么为什么需要使用线程池呢?
虽然以上两种方式能够实现多线程,但是如果不加控制,反而会对系统造成负担。线程本身也要占用内存空间,因为jvm会为每个线程分配一定内存(JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K,也可以通过jvm参数-Xss来设置),所以当线程数达到一定数量时就报了该error。大量的线程会占用内存资源并且可能会导致Out of Memory。即便没有这样的情况,大量的线程回收也会给GC带来很大的压力。
现在问题明朗了:java线程池是为了防止内存溢出,而不是为了加快效率。

线程池解决了:

1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务

2. 可以根据系统的承受能力,调整线程池中工作线程的数据,防止因为消耗过多的内存导致服务器崩溃。

线程常用的方法:

currentThread():返回对当前正在执行的线程对象的引用
getId():返回此线程的标识符
getName():返回此线程的名称
setName(String name):将此线程的名称更改为等于参数 name 。
getPriority():返回此线程的优先级
isAlive():测试这个线程是否还处于活动状态。

什么是活动状态呢?
活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。

sleep(long millis):使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行)
isDaemon():测试这个线程是否是守护线程
setDaemon(boolean on):将此线程标记为 daemon线程或用户线程。
join():在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是 主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
yield():yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。
setPriority(int newPriority):更改此线程的优先级
interrupt():中断这个线程。
interrupted()isInterrupted()
interrupted(): 测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能
isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但部清楚状态标志
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值