Java多线程、并发

    并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过多线程机制,这些独立的任务中的每一个都将由执行线程来驱动。单个进程可以拥有多个并发执行任务。
    实现并发最直接的方式是在操作系统级别使用进程。
    Java的线程机制是抢占式的,调度机制会周期性的中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的赶时间去驱动它的任务。还有一种方式是协作式,这种需要程序主动去释放线程控制权。

一、多线程使用

1、继承Runnable接口

 1 public class MyRunnable implements Runnable {
 2     private int index;
 3     @Override
 4     public void run() {
 5         while (index ++ < 10){
 6             System.out.print(index + ",");
 7             Thread.yield();//可选使用,用来告诉线程调试器,当前任务已执行完,可以切换线程了
 8         }
 9     }
10     public static void main(String[] args) {
11         //启用新线程
12         new Thread(new MyRunnable()).start();
13         new Thread(new MyRunnable()).start();
14         System.out.println("main ended");
15     }
16 }

结果:

main ended
1,2,1,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,

相关参数设置:

    Thread thread1 = new Thread(new MyRunnable());
    //设置thread1为后台线程,程序会在所有非后台线程运行结束后结束,而不关心后台线程是否已经结束了。
    thread1.setDaemon(true);
    thread1.setName("threadname");//设置线程名称
    thread1.start();

    Thread thread2 = new Thread(new MyRunnable());
    thread2.start();
    try {
        thread2.join(6000); //主线程会在此等待thread2执行完,最多等待6秒,如果不设置超时时间则一直等待
        thread2.interrupt();//取消等待,这个与join在同一个线程执行是没有效果的。
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

使用Executors来管理线程:

    //SE5后可以使用Executors来管理线程
    ExecutorService pool = Executors.newCachedThreadPool();
    //相似的还有FixedThreadPool(固定线程), SingleThreadExecutor单线程(如果有多个任务会排队)
//  Executors.newFixedThreadPool(10);
//  Executors.newSingleThreadExecutor();
    pool.execute(new MyRunnable());
    pool.shutdown();//shutdown后就不能添加新的线程任务了

使用Executors来执行线程还可以获取到线程内的异常getUncaughtExceptionHandler;   

2、继承Callable接口

可获取线程任务返回值

 1 public class MyCallable implements Callable<String> {
 2     private int index;
 3     @Override
 4     public String call() throws Exception {
 5         while (index ++ < 10){
 6             System.out.print(index + ",");
 7         }
 8         return "success";
 9     }
10 
11     public static void main(String[] args) throws ExecutionException, InterruptedException {
12         ExecutorService pool = Executors.newCachedThreadPool();
13         Future<String> result = pool.submit(new MyCallable());
14         result.isDone();  //返回当前任务是否已经完成,未完成时调用get()会一直阻塞,直到完成返回结果为止
15         System.out.println(result.get()); //success
16     }
17 }

二、共享资源加锁

1、synchronized关键字

可使用synchronized关键字来修饰方法,防止资源冲突。
如:synchronized void f(){}
    synchronized void g(){}
当在对象上调用其任意synchronized方法时,此对象都会被加锁,此时该对象上的其他synchronized方法只有等前一个方法调用完毕释放锁之后才能被调用。
以上如某个任务对对象调用了f(),对于同一个对象而言,就只能等f()调用结束并释放锁之后其他任务才能调用f()和g()。所以对于某个特定对象来说synchronized共享同一个锁。
但同一个任务可多次获得对象锁,如果f()调用了g(),也是可以的,这时锁的计数就是2,只有当锁的计数变为0时,锁才会被释放。
注意在使用并发时,将被共享资源的域设置为private是非常重要的,否则synchronized关键字就不能防止其他任务直接访问域。
示例:

 1 //多线程计数
 2 public class SynchObject {
 3     private Integer index = 0 ;
 4     synchronized public void counting(){
 5         while (index ++ < 10){
 6             System.out.printf(index + ",");
 7             try {
 8                 Thread.sleep(100L);
 9             } catch (InterruptedException e) {
10                 e.printStackTrace();
11             }
12         }
13     }
14 }
15 public class SyncTesthRunnable implements Runnable {
16     SynchObject synchObject;
17     SyncTesthRunnable(SynchObject synchObject){
18         this.synchObject = synchObject;
19     }
20     @Override
21     public void run() {
22     
23     
24         this.synchObject.counting();
25     }
26     public static void main(String[] args){
27         SynchObject synchObject = new SynchObject();
28         new Thread(new SyncTesthRunnable(synchObject)).start();
29         new Thread(new SyncTesthRunnable(synchObject)).start();
30     }
31 }

如果counting方法不加synchronized关键字,计数的结果就不会是顺序的;
不加synchronized时的运行结果:2,2,3,3,4,5,7,7,8,9,10,
加synchronized时的运行结果:1,2,3,4,5,6,7,8,9,10,

2、显示的使用锁Lock对象来加锁

使用如下:

 1 public class SynchObject {
 2     private Integer index = 0 ;
 3     private Lock lock = new ReentrantLock();
 4 
 5     public void counting(){
 6         lock.lock();
 7         while (index ++ < 10){
 8             System.out.printf(index + ",");
 9             try {
10                 Thread.sleep(100L);
11             } catch (InterruptedException e) {
12                 e.printStackTrace();
13             }
14         }
15         lock.unlock();//注意锁一定要主动释放,不然其他任务就永远获取不到锁。最好能放在finally里,以防异常发生无法释放锁。
16     }
17 }

如果在synchronized里事务失败了则会抛出异常,我们没有机会去做一些清理工作。使用Lock锁则可以做更多的动作。通常使用synchronized就足够了,只有在解决特殊问题时才显示使用Lock对象。
*在类中只有一个可变域,且对域只进行原子操作时,可使用volatile关键字来限定域,确保同步。但最好不要用,很可能因为理解不到位出现同步问题,一般都推荐直接使用synchronized。
*什么是原子操作?对域中(除long,double外的基本类型)的值做赋值和返回值操作通常都是原子性的。如果域有递增递减操作的肯定不是原子操作。

3、临界区

只对方法内的部分代码加锁

    synchronized(synchObject){
        //这个代码块里的代码只能同时被一个线程访问,也称为同步控制块
    }

三、中断线程

ExecutorService exec = Executors.newCachedThreadPool();
Future<String> future = exec.submit(new MyCallable());
future.cancel(true);//方式一
exec.shutdownNow();//方式二
Thread.interrupted();// 检查当前线程是否中断

四、线程间协作

1、wait(),notifyAll()/notify()

 1 public class SynchObject {
 2     private boolean waked = false;
 3 
 4     public synchronized void wakeup() throws InterruptedException {
 5         Thread.sleep(1000L);//sleep()和yield()方法不会释放锁, wait()会释放锁
 6         waked = true;
 7         System.out.println("wake up");
 8         notifyAll(); //notifyAll()唤醒所有的等待,notify()只唤醒一个。如果确定只有一个可以使用notify来进行优化,其他情况建议使用notifyAll
 9     }
10     public synchronized void getup() throws InterruptedException {
11         while (!waked){//wait一般注意要加判断条件,因为不能确定哪个线程先执行,不判断可能造成选notify了再wait,那么就可能一直等不到通知,造成线程一直挂起
12             System.out.println("waiting");
13             wait(); //wait和notify方法都需要使用synchronized锁定对象线程,否则会报IllegalMonitorStateException异常
14         }
15         System.out.println("get up");
16     }
17 
18     public class WakeupTask implements Runnable{
19         private SynchObject object;
20         WakeupTask(SynchObject object){this.object = object;}
21         public void run() {
22             try {
23                 this.object.wakeup();
24             } catch (InterruptedException e) {
25                 e.printStackTrace();
26             }
27         }
28     }
29     public class GetupTask implements Runnable{
30         private SynchObject object;
31         GetupTask(SynchObject object){this.object = object;}
32         public void run() {
33             try {
34                 this.object.getup();
35             } catch (InterruptedException e) {
36                 e.printStackTrace();
37             }
38         }
39     }
40 
41     public void test() {
42         SynchObject object = new SynchObject();
43         WakeupTask wakeupTask = new WakeupTask(object);
44         GetupTask getupTask = new GetupTask(object);
45         new Thread(getupTask).start();
46         new Thread(wakeupTask).start();
47     }
48 }
-------
test()运行结果:
waiting
wake up
get up

2、生产-消费者与队列

更高效的协作

 1 class Person{
 2     private String name;
 3     Person(String name){this.name = name; }
 4     void wakeup(){System.out.println(name + " wake up");}
 5     void getup() throws InterruptedException {
 6         Thread.sleep((long) (1000L * Math.random()));
 7         System.out.println(name + "get up");
 8     }
 9     void working(){System.out.println(name + " working");}
10 }
11 class PersonQueue extends LinkedBlockingQueue<Person>{}
12 
13 class WakedPerson implements Runnable{
14     private PersonQueue personQueue;
15     public WakedPerson(PersonQueue personQueue){this.personQueue = personQueue;}
16 
17     public void run() {
18         for (Integer i = 0; i< 5; i++){
19             Person p = new Person(i.toString());
20             p.wakeup();
21             personQueue.add(p);//也可用put()
22         }
23     }
24 }
25 class GetupPerson implements Runnable{
26     private PersonQueue wakedQueue;
27     private PersonQueue gotupQueue;
28     GetupPerson (PersonQueue personQueue, PersonQueue gotupQueue){
29         this.wakedQueue = personQueue;
30         this.gotupQueue = gotupQueue;
31     }
32     public void run(){
33         try {
34             while (!Thread.interrupted()){
35                 Person p = wakedQueue.take();
36                 p.getup();
37                 gotupQueue.put(p);
38             }
39         } catch (InterruptedException e) {
40             System.out.println("GetupPerson thread ended");
41         }
42     }
43 }
44 class WorkingPerson implements Runnable{
45     private PersonQueue gotupQueue;
46     WorkingPerson(PersonQueue gotupQueue){this.gotupQueue = gotupQueue; }
47     public void run(){
48         try {
49             while (!Thread.interrupted()){
50                 Person p = gotupQueue.take();
51                 p.working();
52             }
53         } catch (InterruptedException e){
54             System.out.println("WorkingPerson thread ended");
55         }
56     }
57 }
58 
59 void test() throws InterruptedException {
60     PersonQueue personQueue = new PersonQueue();
61     PersonQueue gotupQueue = new PersonQueue();
62     ExecutorService exec = Executors.newCachedThreadPool();
63     exec.execute(new WakedPerson(personQueue));
64     exec.execute(new GetupPerson(personQueue, gotupQueue));
65     exec.execute(new WorkingPerson(gotupQueue));
66     //后两个线程的写法不会主动结束,这里有需要的话可以主动结束
67     TimeUnit.SECONDS.sleep(5L);
68     exec.shutdownNow();
69 }
----------
test()运行结果:
0 wake up
1 wake up
2 wake up
3 wake up
0get up
0 working
1get up
1 working
2get up
2 working
3get up
3 working
GetupPerson thread ended
WorkingPerson thread ended
test()运行结果:

3、管道通信

另一种协作,不同平台之间可能存在差异,相对而言BlockingQueue更健壮

 1 class Sender implements Runnable{
 2     private Random random = new Random(46);
 3     private PipedWriter writer = new PipedWriter();
 4     public PipedWriter getWriter(){return writer;}
 5     public void run() {
 6         for (char i = 'A'; i < 'G'; i++){
 7             try {
 8                 writer.write(i);
 9                 TimeUnit.MILLISECONDS.sleep(random.nextInt(500));
10             } catch (IOException | InterruptedException e) {
11                 e.printStackTrace();
12             }
13         }
14     }
15 }
16 
17 class Receiver implements Runnable{
18     private PipedReader reader;
19     Receiver(Sender sender) throws IOException {
20         this.reader = new PipedReader(sender.getWriter());
21     }
22     public void run(){
23         while (true){
24             try {
25                 System.out.println("read: " + (char)reader.read());
26             } catch (IOException e) {
27                 e.printStackTrace();
28             }
29         }
30     }
31 }
32 void test() throws IOException {
33     Sender sender = new Sender();
34     Receiver receiver = new Receiver(sender);
35     ExecutorService excu = Executors.newCachedThreadPool();
36     excu.execute(receiver);
37     excu.execute(sender);
38 }
----------
test()运行结果:
read: A
read: B
read: C
read: D
read: E
read: F
test()运行结果

五、SE5引入的线程处理构件(类)

1、同步一个或多个任务,强制它们等待另一组任务的执行完成(CountDownLatch)

 1 class PreTask implements Runnable{
 2     private final Integer index;
 3     private CyclicBarrier cyclicBarrier;
 4     PreTask(Integer index, CyclicBarrier cyclicBarrier) {
 5         this.index = index;
 6         this.cyclicBarrier = cyclicBarrier;
 7     }
 8     public void run() {
 9         try {
10             TimeUnit.MILLISECONDS.sleep(100 * index);
11             cyclicBarrier.await();
12             System.out.print(index + ",");
13         } catch (InterruptedException | BrokenBarrierException e) {}
14     }
15 }
16 
17 class AfterTask{
18     private ExecutorService excu = Executors.newCachedThreadPool();
19     private CyclicBarrier barrier;
20     private int index = 0;
21 
22     public void runn(){
23         barrier = new CyclicBarrier(3, new Runnable() {//3表示cycle运行了3次才会运行此内容
24             @Override
25             public void run() {
26                 System.out.println("AfterTask;");
27             }
28         });
29     }
30 
31     public void test(){
32         runn();
33         for (int index = 0; index < 12; index ++){
34             excu.execute(new PreTask(index, barrier));
35         }
36     }
37 }
----------
test()运行结果:
AfterTask;
2,0,1,AfterTask;
3,4,5,AfterTask;
6,7,8,AfterTask;
11,9,10,
test()运行结果:

3、基础优先级队列PriorityBlockingQueue

可以根据优先级执行顺便执行,必须要实现Comparable接口;

 1 class PriorityTask implements Runnable, Comparable{
 2     private final int index;
 3     public final int priority = new Random().nextInt(20);
 4     PriorityTask(int index) {this.index = index;}
 5 
 6     @Override
 7     public void run() {
 8         System.out.println(index + " run with priority:" + priority);
 9     }
10     @Override
11     public int compareTo(Object o) {
12         return this.priority - ((PriorityTask) o).priority;
13     }
14 }
15 class Consumer implements Runnable{
16     private PriorityBlockingQueue<Runnable> queue;
17     Consumer(PriorityBlockingQueue<Runnable> queue) { this.queue = queue;}
18 
19     @Override
20     public void run() {
21         while (!Thread.interrupted()){
22             try {
23                 queue.take().run();
24             } catch (InterruptedException e) {}
25         }
26     }
27 }
28 
29 void test(){
30     ExecutorService excu = Executors.newCachedThreadPool();
31     PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<Runnable>();
32     for (int index = 1; index < 7; index ++){
33         queue.put(new PriorityTask(index));
34     }
35     excu.execute(new Consumer(queue));
36 }
----------
test()运行结果:
1 run with priority:1
6 run with priority:3
4 run with priority:8
2 run with priority:10
3 run with priority:15
5 run with priority:16
test()运行结果:

4、等待执行DelayQueue

等待一段时间后执行,可作超时处理任务之类的工作,其实除去等待时间也是一个优先级队列;

 1 class DelayTask implements Runnable, Delayed{
 2     private int index;
 3     int wait = new Random().nextInt(10);
 4     private Long startTime = System.currentTimeMillis();
 5     DelayTask(int index){ this.index = index;}
 6     public int getWait(){return this.wait;}
 7 
 8     //如果两个都在队列里判断谁优先执行(与getDelay的时间无关),所以一般可以用延迟的时间进行排序
 9     @Override
10     public int compareTo(Delayed o) {
11         return wait - ((DelayTask)o).getWait();
12     }
13     //等待时间,一定要减去当前时间,因为此方法会被持续调用,直到返回值为0或负数时才执行run的方法,返回一个常量的正数,则run一直不会运行。
14     @Override
15     public long getDelay(TimeUnit unit) {
16         return unit.convert(startTime + wait * 1000  - System.currentTimeMillis(),
17                 TimeUnit.MILLISECONDS);
18     }
19     @Override
20     public void run() {
21         System.out.println(index + "waited:" + wait + "s,");
22     }
23 }
24 class Consumer implements Runnable{
25     private DelayQueue<DelayTask> queue;
26     Consumer(DelayQueue<DelayTask> queue) {this.queue = queue;}
27     @Override
28     public void run() {
29         while (!Thread.interrupted()){
30             try {
31                 queue.take().run();
32             } catch (InterruptedException e) {
33                 e.printStackTrace();
34             }
35         }
36     }
37 }
38 
39 void test(){
40     ExecutorService excu = Executors.newCachedThreadPool();
41     DelayQueue<DelayTask> queue = new DelayQueue<DelayTask>();
42     for (int index = 1; index < 12; index ++){
43         queue.put(new DelayTask(index));
44     }
45     excu.execute(new Consumer(queue));
46 }
----------
test()运行结果:
4waited:0s,
10waited:1s,
7waited:1s,
11waited:3s,
1waited:3s,
2waited:4s,
8waited:5s,
3waited:7s,
6waited:7s,
5waited:8s,
9waited:8s,
test()运行结果:

5、定时任务ScheduledThreadPoolExecutor

 1 class ScheduleTask implements Runnable{
 2     private final String name;
 3     ScheduleTask(String name) {this.name = name; }
 4 
 5     @Override
 6     public void run() {
 7         System.out.println(new Date().toString() + " running " + name);
 8     }
 9 }
10 void test() throws InterruptedException {
11     ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(10);//线程池容量
12     //执行一次
13     scheduler.schedule(new ScheduleTask("singletask"), 2, TimeUnit.SECONDS);
14     //周期性执行多次
15     scheduler.scheduleAtFixedRate(new ScheduleTask("repeatetask"), 1, 4, TimeUnit.SECONDS);
16     TimeUnit.SECONDS.sleep(20);
17     scheduler.shutdownNow();
18 }
----------
test()运行结果:
Thu Feb 04 01:08:19 CST 2021 running repeatetask
Thu Feb 04 01:08:20 CST 2021 running singletask
Thu Feb 04 01:08:23 CST 2021 running repeatetask
Thu Feb 04 01:08:27 CST 2021 running repeatetask
Thu Feb 04 01:08:31 CST 2021 running repeatetask
Thu Feb 04 01:08:35 CST 2021 running repeatetask
test()运行结果:

六、其他

1、变量在各线程内时独立的,使用java.lang.ThreadLocal<E>这个类来辅助实现线程本地存储
2、使用Lock通常会比使用synchronized高效许多,但一般可以 synchronized关键字入手,只有在性能调优时替换为Lock对象;Atomic对象只有在非常简单的情况下才有用,通常不使用。
3、线程池的创建方式:
Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲

参考:线程池的7种创建方式

原文地址:https://www.cnblogs.com/aland-1415/p/14449745.html