并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过多线程机制,这些独立的任务中的每一个都将由执行线程来驱动。单个进程可以拥有多个并发执行任务。
实现并发最直接的方式是在操作系统级别使用进程。
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种创建方式