Java-- 线程

线程

程序:

        程序是一个静态的概念,一般对应于操作系统中的一个可执行文件,比如:我们在打开酷狗听音乐时,则对应酷狗的可执行程序。当我们双击酷狗,则加载程序到内存中,开始执行该程序才产生了进程。

线程

        一个进程可以产生多个线程。同一进程的多个线程也可以共享此进程的某些资源(比如:代码、数据),所以线程又被称为轻量级进程(lightweight process)。 1.是能够进行运算的最小单位。它被包含在之中,是中的实际运作单位。 2.一个进程可拥有多个线程 3.一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且它们从同一堆中分配对象并进行通信、 数据交换和同步操作。 4.线程的启动、中断、消亡,消耗的资源非常少。 5.每个iava程序都有一个主线程:main thread(对应main方法启动)

进程

        执行中的程序叫做进程(Process),是一个动态的概念。现代的操作系统都可以同时启动多个进程。比如:我们在用酷狗听音乐,也可以使用wps写文档,也可以同时用浏览器查看网页。进程具有如下特点:

        进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是结构的基础 每个进程由3部分组成:cpu、data、code。每个进程都是独立的,保有自己的cpu时间,代码和数据。 进程的查看 ​ Windows系统:Ctrl+Alt+Del,启动任务管理器即可查看所有进程。 Linux系统:ps or top.

JAVA中线程和操作系统线程的关系(源码解读)

        green threads 是一种由运行环境或虚拟机(VM)调度,而不是由本地底层操作系统调度的线程。绿色线程并不依赖底层的系统功能,模拟实现了多线程的运行,这种线程的管理调配发生在用户空间而不是内核空间,所以它们可以在没有原生线程支持的环境中工作。

        在Java1.1中,绿色线程是JVM中使用的唯一一种线程模型。 ​ 在Java1.2之后,Linux中的JVM是基于pthread实现的,即现在的Java中线程的本质,其实就是操作系统中的线程。

        我们分析Thread类的start(),就能看出最终调用的是native方法startO0),也就是调用了操作系统底层的方法。

通过继承Thread类实现多线程

        继承Thread类实现多线程的步骤: ​ 1.在Java中负责实现线程功能的类是java.lang.Thread 类。 ​ 2.可以通过创建Thread的实例来创建新的线程。 ​ 3.每个线程都是通过某个特定的Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。 ​ 4.调用Thread类的start()方法来启动一个线程。

public class Testtthread extends Thread{
    @Override
    public void run() {
        for(int i = 0;i < 10;i++)
        {
            System.out.println(this.getName() + "   " + i);
        }
    }
    public static void main(String[] args) {
        Testtthread t = new Testtthread();
        Testtthread t1 = new Testtthread();
        t.start();
        t1.start();
    }
}
public class Testtthread implements Runnable{//实现Run方法(Thraed的本质也是实现了Run方法)
    @Override
    public void run() {
        for(int i = 0;i < 10;i++)
        {
            System.out.println(Thread.currentThread().getName() + "   " + i);
        }
    }
    public static void main(String[] args) {
        Testtthread t = new Testtthread();//用实现了Run方法的Runnable接口来创建线程
        Thread t1 = new Thread(t);
        t1.start();
        Thread t2 = new Thread(t);
        t2.start();
        new Thread(new Runnable() {//匿名内部类创建线程
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() +"  "+ i);
                }
            }
        }).start();
        new Thread(()->{//lambda创建线程(类中只有一个方法体)
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() +"  "+ i);
            }
        }
                ).start();
    }
}
​

        实现线程分别可以用实现了Runnable接口的类进行传入Thread new Thead()中进行创建线程,也可以用匿名内部类和lambda表达式来直接实现Run方法来实现线程的建立。

 

获取线程基本信息的方法

isAlive() //判断线程是否还“活”着,即线程是否还夫终止

getPriority0) //获得线程的优先级数值

setPriority0) //设置线程的优先级数值

setName() 给线程一个名字

getName() 取得线程的名字

currentThread() 取得当前正在运行的线程对象,也就是取得自己本身

线程的优先级

        处于就绪状态的线程,会进入“就绪队列”等待 JVM 来挑选。 ​ 线程的优先级用数字表示 范围从1到10,一个线程的缺省优先级是5。

        注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。

线程同步和安全性

        同步问题的提出

        现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比如:教室里,只有一台电脑,多个人都想使用。天然的解决办法就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。

线程同步的概念

        多个线程访问同一个对象,并且某些线程还想修改这个对象。"这时,我们就需要用到“线程同步”。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

public class TestThreadCiycle implements Runnable{
    Boolean live = true;
    @Override
    public void run() {
        int i = 0;
        while(live){
            System.out.println(Thread.currentThread().getName() + (i++));
        }
    }
    public void stop(){
        this.live = false;
    }
​
    public static void main(String[] args) {
        TestThreadCiycle tt = new TestThreadCiycle();
        Thread t = new Thread(tt);
        t.start();
        for(int i = 0;i < 100;i++)
        {
            System.out.println("主线程" + i);
        }
        tt.stop();
    }
}
import static java.lang.Thread.sleep;
public class tick implements Runnable{
    private int number = 20;
​
    @Override
    public void run() {
        while(number >= 0)
        {
            synchronized (this) {
                if (number == 0) {
                    System.out.println(Thread.currentThread().getName() + "票已售空!");
                    break;
                } else {
                    System.out.println(Thread.currentThread().getName() + "卖出第" + number + "张票");
                    this.number--;
                    try {
                        sleep(1000);//暂停秒数(毫秒)
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
​
            }
            Thread.yield();//实现恢复就绪状态
    }
}}
public class Test {
    public static void main(String[] args) {
        tick tick1 = new tick();
        Thread dog = new Thread(tick1,"dog");
        Thread cat =  new Thread(tick1,"cat");
            dog.start();
            cat.start();
        System.out.println("我爱你");
    }//可以实现交替式买票
}
​

实现线程同步

        由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。

        这个机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized 块。

synchronized 方法

        通过在方法声明中加入 synchronized关键字来声明,语法如下:

    public synchronized void accessVal(int newVal)

        synchronized 方法控制对“对象的成员变量”的访问:每个对象对应一把锁,每个synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。

生产/消费者模式:

        多线程环境下,我们经常需要多个线程的并发和协作。这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”。

        什么是生产者?

        生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。

        什么是消费者?

        消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。多线程环境下,我们经常需要多个线程的并发和协作。这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”。

        什么是缓冲区?

        消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。

线程协作和通信

        线程并发协作(也叫线程通信),通常用于生产者/消费者模式。需要使用wait()/notify)/notifyAll()方法。 线程通信常用方法 方法名 作用 其他说明 final void wait() 表示线程一直等待 线程执行wait0方法时候,会释放当前的锁,然后让出CPU进入等待状态 void wait(long timeout) 线程等待指定毫秒参数的时间 final void notify0 唤醒一个处于等待状态的线程

        以上方法只能在同步方法中或者同步方法块中使用,否则会抛出异常;

public class TestLys {
    public static void main(String[] args) {
        huanChong x = new huanChong();
        produce ji = new produce(x);
        cost ko = new cost(x);
        ji.start();
        ko.start();
    }
}
class Bread{
    private int id;
    public Bread(int id) {
        this.id = id;
    }
    public int getid(){
        return id;
    }
}
class huanChong{
    Bread t[] = new Bread[10];
    private int idex;
    public synchronized void push(Bread bread)
    {
        while(idex == t.length){
            try {
                this.wait();//缓冲区满,暂停线程
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.notify();
        t[idex++] = bread;
    }
    public synchronized Bread pop(){
        while(idex == 0)
        {
            try {
                this.wait();//未生产,暂停
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.notify();
        idex--;
        return t[idex];
    }
}
class produce extends Thread{
    huanChong s = null;
    public produce(huanChong s) {
        this.s = s;
    }
​
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Bread k = new Bread(i);
            System.out.println("生产" + k.getid());
            s.push(k);
        }
    }
}
class cost extends Thread{
    huanChong s = null;
​
    public cost(huanChong s) {
        this.s = s;
    }
​
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Bread l = s.pop();
            System.out.println("消费" + l.getid());
        }
    }
}

线程池:

        通俗点讲,当有工作来,就会向线程池拿一个线程,当工作完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。这样就避免了频繁的创建线程、销毁线程。极大的提高的响应速度。假如创建线程用的 时间为T1,执行任务用的时间为T2,销毁线程用的时间为3,那么使用线程池就免去了T1 和T3的时间;

        比如:有一个省级银行的数据网络中心,高峰期每秒的客户端请求并发数超过200,如果为每个客户端请求创建一个新的线程的话,那耗费的CPU 时间和内存都是十分惊人的,如果采用一个拥有200个线程的线程池,那将会节约大量的系统资源,使得更多的CPU时间和内存用来处理实际的商业应用,而不是频繁的线程创建和销毁。 说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

池化技术

        池化技术指的是提前准备一些资源,在需要时可以重复使用这些预先准备的 资源。它有两个优点: 1.提前创建; 2.重复利用。 常见的池化技术的使用有:线程池、数据库连接池、HttpClient连接池等。

线程池的优势:

  • 解耦作用;线程的创建于执行完全分开,方便维护。 2.重复使用,降低系统资源消耗:通过重用已存在的线程,降低线程创建和销 毁造成的消耗。 3.统一管理线程:控制线程并发数量,降低服务器压力,统一管理所有线程; 4.提升系统响应速度。使用线程池以后,工作效率通常能提高 。

        创建线程池对象,可以通过如下构造器创建:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,,ThreadFactory threadFactory,RejectedExecutionHandler handler)
​
ThreadPoolExecutor executor=new ThreadPoolExecutor(5.10.200.TimeUnit.MILLISECONDS
new ArrayBlockingQueue<Runnable>(5));

        //核心线程池大小:5 线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收 //maximumPoolsize10线程池中可以容纳的最大线程的数量(核心线程数+非核心线程数) //keepAliveTime:200和TimeUnitMilliseconds非核心线程可以保留的最长的空闲时间是200毫秒(TimeUnit就是计算这个时间的一个单位) //workQueue:任务队列任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出) //threadFactory:创建线程对象的工厂//handler:线程池已满,拒绝执行策略

四种内置线程池的使用

1.CachedThreadPool

        该线程池中没有核心线程,非核心线程的数量为Integermaxvalue,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用干耗时少,任务量大的情况。

2.ScheduledThreadPool

        周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。也可以执行周期性的任务。

3.SingleThreadPool

        只有一条线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO先进先出LIFO后进先出,优先级)执行适用于有顺序的任务的应用场景。

4.FixedThreadPool

        定长的线程池,核心线程即全部线程,没有非核心线程。

handler的拒绝策略:

  1. AbortPolicy不执行新任务,直接抛出异常常,提示线程池已满(默认)

  2. DisCardPolicy:不执行新任务,也不抛出异异常

  3. DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行

  4. CallerRunsPolicy:直接调用execute来执行当前任务

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
​
public class Swsx {
    public static void main(String[] args) {
/*        ThreadPoolExecutor executor = new ThreadPoolExecutor(5,15,200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(5));*/
//        ExecutorService executor = Executors.newCachedThreadPool();//全部为临时线程工作
//        ExecutorService executor = Executors.newFixedThreadPool(5);//核心线程指定,并一直只有核心线程运行
//        ExecutorService executor = Executors.newSingleThreadExecutor();任务只有一个线程来执行,保证工作的先入先出,保证顺序
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(15);
        executor.schedule(()->{
            System.out.println("30秒后见" + "dhsadjsakdhak");
        },30, TimeUnit.SECONDS);
        for (int i = 0; i < 20; i++) {
            task x =new task(i);
            executor.execute(x);
           /* System.out.println("线程池中的线程数目  " + executor.getPoolSize() + "正在执行的线程数目  " + executor.getActiveCount() + "队列中等待的线程数  " +
                    executor.getQueue().size() + "执行完毕的任务数  " + executor.getCompletedTaskCount());*/
        }
        executor.shutdown();
    }
}
class task implements Runnable{
    private int name;
    public task(int name)
    {
        this.name = name;
    }
    public int getname()
    {
        return this.name;
    }
    @Override
    public void run() {
        System.out.println("任务" + this.getname() + "正在执行");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("任务" + this.getname() + "执行完毕");
    }
}

Treadlocal:

ThreadLocal是什么:

    • 1.ThreadLocal为每个线程提供独立的变量副本,所以每一个线程都可以独立 地改变自己的副本,而不会影响其它线程所对应的副本。

    • 2.ThreadLocal 更多是为了实现数据隔离、避免多次传参问题,围绕这点,有很多场景:

    • 1、(数据隔离)进行事务操作,用于存储线程事务信息(Spring实现事务隔离级别的源码)。

    • 2、(数据隔离)数据库连接管理

    • 3、(数据隔离)Cookie、Session的管理。

    • 4、(数据隔离)Android开发中,Looper类

    • 5、(避免多次传参)在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束

内存泄露

        不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。内存泄漏产生的原因:长生命周期对象持有短生命周期的对象导致短生命周期对象无法被释放。

强引用与弱引用

强引用

        最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。

弱引用

        进行垃圾回收时,无论内存是否充足,会回收只被弱引用关联的对象。java 中,用java.lang.refWeakReference类来表示。

ThreadLocal如何引起内存泄漏

        弱引用可能会造成内存泄漏。如果ThreadLoc al只被弱引用会被GC回收。回收后,没有再调用get()/set()/remove()方法,从而造成 value对象无法回收。但,ThreadLocal内存泄漏的真正根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key和value,就会导致内存泄漏,而不一定 是弱引用。

ThreadLocal正确的使用方法

        每次使用完ThreadLocal变量,都调用它的remove()方法清除数据。

public class TestThreadLocal extends Thread{
    public static ThreadLocal<Integer> num1 = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            System.out.println("初始化调用");
            return new Integer(100);
        }
    };
    public static int getnum1(){
        num1.set(num1.get() + 1);
        return num1.get();
    }
    public static ThreadLocal<Integer> num2 = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return new Integer(10);
        }
    };
    public static int getnum2(){
        num2.set(num2.get() + 10);
        return num2.get();
    }
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + " num:" + getnum1() + " num2:" + getnum2());
        }
        num1.remove();
        num2.remove();
    }
​
    public static void main(String[] args) {
        new TestThreadLocal().start();
        new TestThreadLocal().start();
        new TestThreadLocal().start();
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值