IT教程 ·

Java基础——多线程

7年加工作经验的程序员,从大厂跳槽出来,遭遇了什么?

Java基本-多线程

多个线程一同做统一件事变,压缩时刻,提拔效力
进步资本应用率
加速程序相应,提拔用户体验

竖立线程

1. 继承Thread类

  • 步骤
    • 继承Thread类,重写run要领
    • 挪用的时刻,直接new一个对象,然后调start()要领启动线程
  • 特性
    • 由因而继承体式格局,所以不发起运用,由于Java是单继承的,不够天真
    • Thread类实质也是完成Runnable接口(public class Thread implements Runnable)

Java基础——多线程 IT教程 第1张

2. 完成Runnable接口

  • 步骤
    • 完成Runnable接口,重写run()要领
    • 竖立Runnable完成类的实例,并用这个实例作为Thread的target来竖立Thread对象
    • 挪用Thread类实例对象的start()要领启动线程
  • 特性
    • 只是完成,保留了继承的其他类的才能
    • 假如须要接见当前线程,必需运用Thread.currentThread()要领

Java基础——多线程 IT教程 第2张

3. 完成 Callable接口

  • 步骤
    • 完成Callable接口,重写call()要领
    • 竖立Callable完成类的实例,运用FutureTask类来包装Callable对象
    • 并用FutureTask实例作为Thread的target来竖立Thread对象
    • 挪用Thread类实例对象的start()要领启动线程
    • 挪用FutureTask类实例对象的get()要领猎取异步返回值
  • 特性
    • call要领可以抛出非常
    • 只是完成,保留了继承的其他类的才能
    • 假如须要接见当前线程,必需运用Thread.currentThread()要领
    • 经由过程FutureTask对象可以相识使命实行情况,可取消使命的实行,还可猎取实行效果

Java基础——多线程 IT教程 第3张

4. 匿名内部类完成

  • 申明
    • 实质照样前面的要领,只是运用了匿名内部类来完成,简化代码
    • Callable接口之所以把FutureTask类的实例化写出来,是由于须要经由过程task对象猎取返回值

Java基础——多线程 IT教程 第4张

参数通报

1. 经由过程组织要领通报数据

经由过程前面的进修,可以看到,不论何种竖立对象的体式格局,都须要新竖立实例,所以我们可以经由过程组织函数传入参数,并将传入的数据运用类变量保存起来

  • 特性
    • 在线程运转之前,数据就已传入了
    • 运用组织参数,当参数较多时,运用不方便
    • 差异参数前提须要差异的组织要领,使得组织要领较多

2. 经由过程变量和要领通报数据

在线程类内里定义一些列的变量,然后定义set要领,在新建实例以后,挪用set要领通报参数

  • 特性
    • 在参数较多时运用方便,按需通报参数

3. 经由过程回调函数通报数据

运用线程要领本身发生的变量值作为参数,去调取外部的要领,猎取返回数据的体式格局

  • 特性
    • 具有猎取数据的主动权

线程同步

要跨线程保护准确的可见性,只要在几个线程之间同享非 final 变量,就必需运用线程同步

1. ThreadLocal

ThreadLocal应用空间换时刻,经由过程为每一个线程供应一个自力的变量副本,避免了资本守候,处理了变量并发接见的争执问题,进步了并发量。完成了线程间的数据断绝,然则线程间没法同享统一个资本

public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SyncTest syncTest = new SyncTest();
        ConcurrentHashMap<String, String> testConMap = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            ThreadTest2 threadTest2 = new ThreadTest2();
            threadTest2.setSyncTest(syncTest);
            Thread threadTest = new Thread(threadTest2);
            threadTest.start();
        }
    }
}

//完成Runnable
class ThreadTest2 implements Runnable {
    private SyncTest syncTest;

    public void setSyncTest(SyncTest syncTest) {
        this.syncTest = syncTest;
    }

    @Override
    public void run() {
        syncTest.threadLocalTest(Thread.currentThread().getName());
    }
}

class SyncTest {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public void threadLocalTest(String name) {
        try {
            System.out.println(name + "进入了threadLocal要领!");
            threadLocal.set(name);
            Thread.currentThread().sleep(100);
            System.out.println(threadLocal.get() + "脱离了threadLocal要领!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. synchronized

不论synchronized是用来润饰要领,照样润饰代码块,其实质都是锁定某一个对象。润饰要领时,锁上的是挪用这个要领的对象,即this;润饰代码块时,锁上的是括号里的谁人对象。每一个Java对象都有一个内置锁,接见synchronized代码块或synchronized要领的时刻,线程都须要起首猎取到对象关联的内置锁,关于static要领,线程猎取的是类对象的内置锁。

  • 特性
    • 锁的对象越小越好
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SyncTest syncTest = new SyncTest();
        ConcurrentHashMap<String, String> testConMap = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            ThreadTest2 threadTest2 = new ThreadTest2();
            threadTest2.setSyncTest(syncTest);
            threadTest2.setTestConMap(testConMap);
            Thread threadTest = new Thread(threadTest2);
            threadTest.start();
        }
    }

}
//完成Runnable
class ThreadTest2 implements Runnable {
    private ConcurrentHashMap<String, String> testConMap;
    private SyncTest syncTest;

    public void setTestConMap(ConcurrentHashMap<String, String> testConMap) {
        this.testConMap = testConMap;
    }

    public void setSyncTest(SyncTest syncTest) {
        this.syncTest = syncTest;
    }

    @Override
    public void run() {
        //三个要领须要零丁测试,由于testConMap会相互影响

        //测试同步要领,锁住的对象是syncTest
        //syncTest.testSyncMethod(testConMap,Thread.currentThread().getName());
        //测试同步代码块,锁住的对象是testConMap
        //syncTest.testSyncObject(testConMap, Thread.currentThread().getName());
        //测试没有锁时实行请求是何等的杂沓!!!
        //syncTest.testNoneSyncObject(testConMap, Thread.currentThread().getName());
    }
}
//同步测试要领类
class SyncTest {
    public synchronized void testSyncMethod(ConcurrentHashMap<String, String> testConMap, String name) {
        try {
            System.out.println(name + "进入了同步要领!");
            testConMap.put("name", name);
            Thread.currentThread().sleep(10);
            System.out.println(testConMap.get("name") + "脱离了同步要领!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void testSyncObject(ConcurrentHashMap<String, String> testConMap, String name) {
        synchronized (testConMap) {
            try {
                System.out.println(name + "进入了同步代码块!");
                testConMap.put("name", name);
                Thread.currentThread().sleep(10);
                System.out.println(testConMap.get("name") + "脱离了同步代码块!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void testNoneSyncObject(ConcurrentHashMap<String, String> testConMap, String name) {
        try {
            System.out.println(name + "进入了无人统领地区!");
            testConMap.put("name", name);
            Thread.currentThread().sleep(10);
            System.out.println(testConMap.get("name") + "脱离了无人统领地区!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3. volatile

  • 特性
    • 保证可见性,有序性,不保证原子性
    • 它会强迫将对缓存的修正操纵马上写入主存
    • volatile不适合复合操纵(对变量的写操纵不依赖于当前值),不然须要保证只要单一线程可以修正变量的值
    • 运用volatile关键字,可以制止指令重排序(单例两重搜检锁)
public class StudyThread {
    static int v = 1;//volatile可以保证变量的可见性

    public static void main(String[] args) {

        //修改线程
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    v++;//确保只要一个线程修正变量值
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        //检测线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                int old = 0;
                while (old < 11) {
                    if (old != v) {
                        old = v;
                        System.out.println("检测线程:v的值更改为" + old);
                    }
                }
            }
        }).start();
    }
}

4. ReentrantLock

  • 申明
    • 现在ReentrantLock和synchronized机能上没有什么差异
    • ReentrantLock须要手动加锁和解锁,且解锁的操纵只管要放在finally代码块中,保证线程准确开释锁
    • ReentrantLock可以完成平正锁,在锁上守候时刻最长的线程将取得锁的运用权,机能没有非平正锁机能好
    • ReentrantLock供应了一个可以相应中断的猎取锁的要领lockInterruptibly(),可以用来处理死锁问题
    • ReentrantLock还供应了猎取锁限时守候的要领tryLock(),运用该要领合营失利重试机制来更好的处理死锁问题
    • ReentrantLock连系Condition接口可以完成守候关照机制
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        //三段代码一一测试
        
        //测试tryLock
        Thread threadTest00 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while(!lock.tryLock()){
                        System.out.println(Thread.currentThread().getName() + "没有拿到锁,继承守候!");
                        Thread.sleep(50);
                    }
                    System.out.println(Thread.currentThread().getName() + "进入了lock代码块!");
                    Thread.currentThread().sleep(300);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + "预备开释锁,并脱离lock代码块!");
                    lock.unlock();
                }
            }
        });
        threadTest00.start();
        Thread threadTest01 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while(!lock.tryLock()){
                        System.out.println(Thread.currentThread().getName() + "没有拿到锁,继承守候!");
                        Thread.sleep(50);
                    }
                    System.out.println(Thread.currentThread().getName() + "进入了lock代码块!");
                    Thread.currentThread().sleep(300);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + "预备开释锁,并脱离lock代码块!");
                    lock.unlock();
                }
            }
        });
        threadTest01.start();

        //测试中断锁
        Thread threadTest02 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "进入了lock代码块!");
                    Thread.currentThread().sleep(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + "预备开释锁,并脱离lock代码块!");
                    lock.unlock();
                }
            }
        });
        threadTest02.start();
        Thread threadTest03 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "进入了lock代码块!");
                    Thread.currentThread().sleep(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + "预备开释锁,并脱离lock代码块!");
                    lock.unlock();
                }
            }
        });
        threadTest03.start();
        Thread.currentThread().sleep(20);
        threadTest02.interrupt();

        //测试condition
        Thread threadTest04 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "进入了lock代码块,守候关照!");
                    condition.await();
                    System.out.println(Thread.currentThread().getName() + "收到关照,继承实行!");
                    Thread.currentThread().sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + "预备开释锁,并脱离lock代码块!");
                    lock.unlock();
                }
            }
        });
        threadTest04.start();
        Thread threadTest05 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "进入了lock代码块!");
                    Thread.currentThread().sleep(1000);
                    condition.signal();
                    System.out.println(Thread.currentThread().getName() + "发出关照!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + "预备开释锁,并脱离lock代码块!");
                    lock.unlock();
                }
            }
        });
        threadTest05.start();
    }

}

5. 线程平安的类

Java中许多类说的线程平安指的是,它的每一个要领零丁挪用(即原子操纵)都是线程平安的,然则代码整体的互斥性并不受掌握

  • 线程平安的类有以下几类
    • Concurrentxxx
    • ThreadPoolExecutor
    • BlockingQueue和BlockingDeque
    • 原子类Atomicxxx—包装类的线程平安类
    • CopyOnWriteArrayList和CopyOnWriteArraySet
    • 经由过程synchronized 关键字给要领加上内置锁来完成线程平安:Timer,TimerTask,Vector,Stack,HashTable,StringBuffer
    • Collections中的synchronizedCollection(Collection c)要领可将一个鸠合变成线程平安:

      Map m=Collections.synchronizedMap(new HashMap());

线程池

线程池只能放入完成Runable或callable类线程,不能直接放入继承Thread的类

1. Executors线程池的完成

  • 要点
    • 大概致使资本耗尽,OOM问题涌现
    • 线程池不允许运用Executors去竖立,而是经由过程ThreadPoolExecutor的体式格局(阿里巴巴java开发)
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //竖立一个线程池,该线程池重用牢固数目的从同享无界行列中运转的线程
        //ExecutorService threadPool = Executors.newFixedThreadPool(20);
        //竖立一个保护充足的线程以支撑给定的并行级别的线程池,线程的现实数目可以动态增进和压缩,事情盗取池不保证实行提交的使命的次序
        //ExecutorService threadPool = Executors.newWorkStealingPool(8);
        //竖立一个运用从无界行列运转的单个事情线程的实行程序。
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //竖立一个依据须要竖立新线程的线程池,但在可用时将从新运用之前组织的线程。假如没有可用的线程,将竖立一个新的线程并将其添加到该池中。未运用六十秒的线程将被停止并从缓存中删除
        ExecutorService threadPool = Executors.newCachedThreadPool();
        //放入Runnable类线程
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                }
            });
        }
        //Thread.currentThread().sleep(1000);
        //放入Callable类线程
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<String> future = threadPool.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                    return Thread.currentThread().getName();
                }
            });
            futures.add(future);
        }
        threadPool.shutdown();
        for (Future future : futures) {
            System.out.println(future.get());
        }
    }
}

2. ThreadPoolExecutor竖立线程池

Java基础——多线程 IT教程 第5张

  • 要点
    • 线程池余暇大小和最大线程数依据现实情况肯定
    • keepAliveTime平常设置为0
    • unit平常设置为TimeUnit.SECONDS(其他的也行,反恰是0)
    • 使命行列须要指定大小,不要运用无界行列,轻易形成OOM-> new ArrayBlockingQueue<>(512)
    • ThreadFactory threadFactory运用体系默许的
    • 谢绝战略:
      • AbortPolicy:抛出RejectedExecutionException(该非常黑白受检非常,要记得捕捉)
      • DiscardPolicy:什么也不做,直接疏忽
      • DiscardOldestPolicy:抛弃实行行列中最老的使命,尝试为当前提交的使命腾出位置
      • CallerRunsPolicy:直接由提交使命者实行这个使命
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int poolSize = Runtime.getRuntime().availableProcessors() * 2;
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(512);
        RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy();
        ExecutorService executorService = new ThreadPoolExecutor(poolSize, poolSize,
                0, TimeUnit.SECONDS,
                queue,
                policy);
        //放入Runnable类线程
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                }
            });
        }

        //放入Callable类线程
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<String> future = executorService.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                    return Thread.currentThread().getName();
                }
            });
            futures.add(future);
        }
        for (Future future:futures) {
            System.out.println(future.get());
        }

        //放入Callable类线程
        //运用CompletionService简化猎取效果的操纵,实行完一个使命,猎取一个效果,效果次序和实行次序雷同
        CompletionService<String> ecs = new ExecutorCompletionService<String>(executorService);
        for (int i = 0; i < 10; i++) {
            Future<String> future = ecs.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                    return Thread.currentThread().getName();
                }
            });
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(ecs.take().get());
        }
    }
}

一文带你了解 C# DLR 的世界

参与评论