• java并发编程系列一、多线程


    一、什么是线程

      一个应用就是一个进程、一个进程由多个线程组成。一个生产车间比作是一个进程、工人比作是线程。当任务比较多的时候,增加工人可以提高效率,同时成本就是支付费用(机器资源,内存)也会增加。

    package com.study.demo;
    
    
    import java.lang.management.ManagementFactory;
    import java.lang.management.ThreadInfo;
    import java.lang.management.ThreadMXBean;
    
    public class ThreadTest {
        public static void main(String[] args) {
            //java虚拟机的线程管理接口
            ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    
            //获取线程信息的方法
            ThreadInfo[] threadInfos =
                    threadMXBean.dumpAllThreads(false,false);
            for(ThreadInfo threadInfo:threadInfos){
                System.out.println(threadInfo.getThreadId()+":"+threadInfo.getThreadName());
            }
        }
    }

    输出:

    5:Attach Listener  //获取内存dump,线程dump

    4:Signal Dispatcher  //将信号分发给jvm的线程

    3:Finalizer  //调用对象的finalizer 方法 进行垃圾回收

    2:Reference Handler  //清除Reference

    1:main //程序的主入口

    为什么要用线程?

    1、 充分利用CPU多处理核心;

    2、 更快的响应时间

    二、启动线程和退出线程

    1、创建线程的方法

    1. extends Thread
    2. implements Runnable

    启动线程:threadl类的start()

    线程完成:

    1、run()方法执行完成;

    2、抛出一个未处理的异常导致线程的提前结束

    package com.study.demo;
    
    public class CreateThread {
    
        private class TestThread extends Thread{
    
            @Override
            public void run(){
                System.out.println(" Thread");
            }
        }
    
        private class TestRunnable implements Runnable{
    
            @Override
            public void run() {
                System.out.println("Runnable");
            }
        }
    
        private void test(){
            Thread t1 = new TestThread();
            Thread t2 = new Thread(new TestRunnable());
            t1.start();
            t2.start();
        }
    
        public static void main(String[] args) {
            new CreateThread().test();
        }
    
    }

    2、取消和中断

    • 不安全的取消
    •  单独使用一个取消标志位
    package com.lgstudy.interrupt;
    
    /**
     * lgs
     * 
     * 使用自定义的取消标志位中断线程(不安全)
     */
    public class FlagCancel {
    
        private static class TestRunable implements Runnable{
    
            private volatile boolean on = true;
            private long i =0;
    
            @Override
            public void run() {
                while(on){
                    i++;
                    //阻塞方法,on不起作用
                    //wait,sleep,blockingqueue(put,take)
                    try {
                        Thread.sleep(20000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("TestRunable is runing :"+i);
            }
    
            public void cancel(){
                on = false;
            }
        }
    
    }

    Stop(),suspend(),resume()是过期的api,很大的副作用,容易导致死锁(suspend():将线程挂起不会释放锁)或者数据不一致(Stop():线程在未处理完数据时就停止)

    3、如何安全的终止线程

    使用线程的中断 : 

    interrupt() 中断线程,本质是将线程的中断标志位设为true,其他线程向需要中断的线程打个招呼。是否真正进行中断由线程自己决定。

    isInterrupted() 线程检查自己的中断标志位

    静态方法Thread.interrupted() 将中断标志位复位为false

    由上面的中断机制可知Java里是没有抢占式任务,只有协作式任务。

    为何要用中断,线程处于阻塞(如调用了java的sleep,wait等等方法时)的时候,是不会理会我们自己设置的取消标志位的,但是这些阻塞方法都会检查线程的中断标志位。

    package com.lgstudy.interrupt;
    
    /**
     * lgs
     * 
     * 安全的中断线程
     */
    public class SafeInterrupt implements Runnable {
    
        private volatile boolean on = true;
        private long i =0;
    
        @Override
        public void run() {
            //阻塞方法wait,sleep,blockingqueue(put,take),on不起作用
            //要加上线程的中断才能安全的终止线程
            while(on&&!Thread.currentThread().isInterrupted()){
                i++;
            }
            System.out.println("TestRunable is runing :"+i);
        }
    
        public void cancel(){
            on = false;
            //在java线程很忙的时候可能不会理会中断,所以定义一个标志位on更好
            Thread.currentThread().interrupt();
        }
    }

     4、处理不可中断的阻塞

    IO通信 inputstream read/write等阻塞方法,不会理会中断,而关闭底层的套接字socket.close()会抛出socketException

    NIO: selector.select()会阻塞,调用selector的wakeup和close方法会抛出ClosedSelectorException

    死锁状态不响应中断的请求,这个必须重启程序,检查程序找到死锁的位置修改错误。

    package com.lgstudy.interrupt;
    
    /**
     * lgs
     * 
     * 调用阻塞方法时,如何中断线程
     */
    public class BlockInterrupt {
    
        private static volatile boolean on = true;
    
        private static class WhenBlock implements Runnable {
    
            @Override
            public void run() {
                while (on && !Thread.currentThread().isInterrupted()) {
                    try {
                        //抛出中断异常的阻塞方法(wait,sleep,blockingqueue(put,take)),抛出异常后,中断标志位改成false
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();//重新设置一下
                        //do my work
                    }
                    //清理工作结束线程
                }
            }
    
            //外部线程调用方法阻塞
            public void cancel() {
                on = false;
                Thread.currentThread().interrupt();
            }
    
        }
    }

    如何让我们的代码既可以响应普通的中断,又可以关闭底层的套接字呢?

    覆盖线程的interrupt方法,在处理套接字异常时,再用super.interrupt()自行中断线程。

    package com.lgstudy.interrupt;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.Socket;
    
    /**
     * lgs
     * 
     * 如何覆盖线程的interrupt() 方法
     */
    public class OverrideInterrupt extends Thread {
        private final Socket socket;
        private final InputStream in;
    
        public OverrideInterrupt(Socket socket, InputStream in) {
            this.socket = socket;
            this.in = in;
        }
    
        private void t(){
        }
    
        @Override
        public void interrupt() {
            try {
                //关闭底层的套接字
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
                //.....
            }finally {
                //同时中断线程
                super.interrupt();
            }
    
        }
    }

    5、线程的状态

    • 新创建   线程被创建,但是没有调用start方法
    • 可运行(RUNNABLE)  运行状态,由cpu决定是不是正在运行
    • 被阻塞(BLOCKING)  阻塞,线程被阻塞于锁
    • 等待/计时等待(WAITING) 等待某些条件成熟
    • 被终止  线程执行完毕

    6、线程的优先级

    成员变量priority控制优先级,范围1-10之间,数字越高优先级越高,缺省为5,创建线程时setPriotity()可以设置优先级,不要指望他发挥作用,因为线程优先级是由操作系统决定的,有的操作系统甚至会忽略jvm的线程优先级。

    7、Daemon线程

    守护型线程(如GC线程),程序里没有非Daemon线程时,java程序就会退出。一般用不上,也不建议我们平时开发时使用,因为Try/Finally里的代码不一定执行的。

    package com.lgstudy;
    
    import com.lgstudy.threadstate.SleepUtils;
    
    /**
     * lgs
     * 
     * 守护线程
     */
    public class Daemon {
        public static void main(String[] args) {
            Thread thread = new Thread(new DaemonRunner());
            //将线程置为守护线程
            thread.setDaemon(true);
            thread.start();
        }
    
        static class DaemonRunner implements Runnable {
            @Override
            public void run() {
                try {
                    SleepUtils.second(100);
                } finally {
                    System.out.println("DaemonThread finally run.");
                }
            }
        }
    }

    三、常用方法深入理解

    run()和start() 

    run就是一个普通的方法,跟其他类的实例方法没有任何区别,他之所以能在线程里面运行时因为调用了start()方法。

    Sleep

    不会释放锁,当前线程变成了休眠状态,所以我们在用sleep时,要把sleep放在同步代码块的外面。

    yield()

    不会释放锁,当前线程出让cpu占有权,当前线程变成了可运行状态,下一时刻仍然可能被cpu选中。

    wait()和 notify()/notiyfAll()

    调用以前,当前线程必须要持有锁,调用了wait() notify()/notiyfAll()会释放锁。

    等待通知机制:

    线程 A调用了对象O的wait方法进入等待状态,线程 B调用了对象O的notify方法进行唤醒,唤醒的是在对象O上wait的线程(比如线程A)

    notify() 唤醒一个线程,唤醒哪一个完全看cpu的心情(谨慎使用)

    notiyfAll() 所有在对象O上wait的线程全部唤醒(应该用notiyfAll())

    join方法

    线程A,执行了thread.join(),线程A等待thread线程终止了以后,A在join后面的语句才会继续执行

    package com.lgstudy;
    
    /**
     * join的使用
     */
    public class JoinTest {
    
        static class CutInLine implements Runnable{
    
            private Thread thread;
    
            public CutInLine(Thread thread) {
                this.thread = thread;
            }
    
            @Override
            public void run() {
                try {
                    //在被插队的线程里,调用一下插队线程的join方法
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" will work");
            }
        }
    
        public static void main(String[] args) {
            Thread previous = Thread.currentThread();
            for(int i=0;i<10;i++){
                Thread thread =
                        new Thread(new CutInLine(previous),String.valueOf(i));
                System.out.println(previous.getId()+" cut in the thread:"+thread.getName());
                thread.start();
                previous = thread;
            }
    
        }
    
    }

     四、线程间协作和通信

    每个线程有自己栈空间,孤立运行,对我们没有价值。如果多个线程能够相互配合完成工作,这将会带来巨大的价值。

    1、volatile和synchronized

    多个线程同时访问一个共享的变量的时候,每个线程的工作内存有这个变量的一个拷贝,变量本身还是保存在共享内存中。

    volatile修饰字段,对这个变量的访问必须要从共享内存刷新一次。最新的修改写回共享内存。可以保证字段的可见性。绝对不是线程安全的,没有操作的原子性。

    适用场景:

    1、一个线程写,多个线程读;

    2、volatile变量的变化很固定即变化以后都是一个固定的值。

    package com.lgstudy.volatiletest;
    
    /**
     * 
     * 测试Volatile型变量的操作原子性
     */
    public class VolatileThread implements Runnable {
    
        private volatile  int a= 0;
    
        @Override
        public void run() {
            synchronized (this){
                a=a+1;
                System.out.println(Thread.currentThread().getName()+"----"+a);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                a=a+1;
                System.out.println(Thread.currentThread().getName()+"----"+a);
    
            }
        }
    }
    package com.lgstudy.volatiletest;
    
    public class VolatileTest {
        public static void main(String[] args) {
            VolatileThread volatileThread = new VolatileThread();
    
            Thread t1 = new Thread(volatileThread);
            Thread t2 = new Thread(volatileThread);
            Thread t3 = new Thread(volatileThread);
            Thread t4 = new Thread(volatileThread);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    输出:

    Thread-0----1
    Thread-0----2
    Thread-3----3
    Thread-3----4
    Thread-2----5
    Thread-2----6
    Thread-1----7
    Thread-1----8

    关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。

    Synchronized的类锁和对象锁,本质上是两把锁,类锁实际锁的是每一个类的class对象。对象锁锁的是当前对象实例。

    package com.lgstudy.syn;
    
    import com.lgstudy.threadstate.SleepUtils;
    
    /**
     * 类锁和实例锁
     */
    public class InstanceAndClass {
    
        //测试类锁
        private static class TestClassSyn extends Thread{
            @Override
            public void run() {
                System.out.println("TestClass is going...");
                synClass();
            }
        }
    
        //测试对象锁
        private static class TestInstanceSyn extends Thread{
            private InstanceAndClass instanceAndClass;
    
            public TestInstanceSyn(InstanceAndClass instanceAndClass) {
                this.instanceAndClass = instanceAndClass;
            }
    
            @Override
            public void run() {
                System.out.println("TestInstance is going..."+instanceAndClass);
                instanceAndClass.synInstance();
            }
    
        }
    
        //测试对象锁
        private static class TestInstance2Syn implements Runnable{
            private InstanceAndClass instanceAndClass;
    
            public TestInstance2Syn(InstanceAndClass instanceAndClass) {
                this.instanceAndClass = instanceAndClass;
            }
            @Override
            public void run() {
                System.out.println("TestInstance2 is going..."+instanceAndClass);
                instanceAndClass.synInstance2();
            }
        }
    
        //锁对象的方法
        private synchronized void synInstance(){
            SleepUtils.second(3);
            System.out.println("synInstance is going...");
            SleepUtils.second(3);
            System.out.println("synInstance ended");
        }
    
        //锁对象的方法
        private synchronized void synInstance2(){
            SleepUtils.second(3);
            System.out.println("synInstance2 going...");
            SleepUtils.second(3);
            System.out.println("synInstance2 ended");
        }
    
        //锁类的方法
        private static synchronized void synClass(){
            SleepUtils.second(1);
            System.out.println("synClass going...");
            SleepUtils.second(1);
        }
    
        public static void main(String[] args) {
            InstanceAndClass instanceAndClass = new InstanceAndClass();
            Thread t1 = new TestClassSyn();
            Thread t2 = new Thread(new TestInstanceSyn(instanceAndClass));
            Thread t3 = new Thread(new TestInstance2Syn(instanceAndClass));
            t2.start();
            t3.start();
            SleepUtils.second(1);
            t1.start();
        }
    
    }

    2、等待和通知机制

    等待方原则:

    1、获取对象锁

    2、如果条件不满足,调用对象的wait方法,被通知后依然要检查条件是否满足

    3、条件满足以后,才能执行相关的业务逻辑

    Synchronized(对象){

    While(条件不满足){

    对象.wait()

    }

    业务逻辑处理

    }

    通知方原则:

    1、 获得对象的锁;

    2、 改变条件;

    3、 通知所有等待在对象的线程

    Synchronized(对象){

    业务逻辑处理,改变条件

    对象.notify/notifyAll

    }

    package com.lgstudy.bq;
    
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * 
     * 有界阻塞队列/有界缓存队列
     */
    public class BlockingQueueWN<T> {
    
        //当前队列
        private List queue = new LinkedList<>();
        //队列支持的最大容量
        private final int limit;
    
        //外部修改队列的容量
        public BlockingQueueWN(int limit) {
            this.limit = limit;
        }
    
        //入队
        public synchronized void enqueue(T item) throws InterruptedException {
            //如果当前队列的容量已经满了的话就要等待
            while(this.queue.size()==this.limit){
                wait();
            }
            //如果当前队列的容量为0的话,可以肯定有出队的线程正在等待,需要他可以准备出队了
            if (this.queue.size()==0){
                notifyAll();
            }
            //开始入队
            this.queue.add(item);
        }
    
        //出队
        public synchronized T dequeue() throws InterruptedException {
            //如果当前队列的容量为0的话就等待暂不出队
            while(this.queue.size()==0){
                wait();
            }
            //如果当前队列的容量已经满了的话,可以肯定有入队线程正在等待,需要唤醒他可以准备入队了
            if (this.queue.size()==this.limit){
                notifyAll();
            }
            //开始出队
            return (T)this.queue.remove(0);
        }
    }

    输出;

    Pop will pop.....
    i=5 will push
    i=5 alread pop
    Pop will pop.....
    i=4 will push
    i=4 alread pop
    Pop will pop.....
    i=3 will push
    i=3 alread pop
    Pop will pop.....
    i=2 will push
    i=2 alread pop
    Pop will pop.....
    i=1 will push
    i=1 alread pop
    Pop will pop.....

    管道输入输出流 使用较少

    管道输入输出流用于线程中间的数据传递,传输媒介是内存

    PpedOutputStream/PpedInputStream 面向的字节

    PipedReader/PipedWriter 面向的是字符

    只适合线程间一对一的通信,适用范围较狭窄。

    ThreadLocal

    本质是个map,map的键就是每个线程对象,值就是每个线程所拥有的值

    常用方法:

    initialValue()

    get()

    set()

    remove():将当前线程局部变量的值删除,这个方法是JDK 5.0新增的方法。当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

    ThreadLocal拥有的这个变量,在线程之间很独立的,相互之间没有联系。内存占用相对来说比较大。

    性能问题

    串行化、无锁化、异步化编程是趋势之一,比如node.js,Vert.x。

    黄金原则:编码时候不要考虑性能优化的事情,先正确实现业务,发现性能不行,这个时候再来考虑性能优化。

    等待超时模式

    调用场景:调用一个方法时等待一段时间(一般来说是给定一个时间段),如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果。

    假设等待时间段是T,那么可以推断出在当前时间now+T之后就会超时

    等待持续时间:REMAINING=T。

    ·超时时间:FUTURE=now+T。

    public synchronized Object get(long mills) throws InterruptedException {
    long future = System.currentTimeMillis() + mills;
    long remaining = mills;
    // 当超时大于0并且result返回值不满足要求
    while ((result == null) && remaining > 0) {
    wait(remaining);
    remaining = future - System.currentTimeMillis();
    }
    return result;
    }
  • 相关阅读:
    普林斯顿宣布开源 25 核处理器
    瑞芯微RK3399宣布系统开源,进入百余种行业市场!
    Qt浅谈之二十App自动重启及关闭子窗口
    学在LINUX下编程(各种情况比较详细)
    ASP.NET开发规范:OWIN
    IDEA14中安装go语言插件
    sqlserver不能直接create table as select
    表复制语句select into from 与 insert into select 区别鉴赏
    实现Asp.net Mvc分布式Session Redis群集
    大小端模式
  • 原文地址:https://www.cnblogs.com/wangzhuxing/p/5206981.html
Copyright © 2020-2023  润新知