多线程 线程(Thread)是一个程序内部的一条执行流程。
程序中如果只有一条执行流程,那这个程序就是单线程的程序。
多线程 是指从软硬件上实现的多条执行流程 的技术(多条线程由CPU负责调度执行)。
Java是通过java.lang.Thread类的对象来代表线程的。
多线程的创建方式 继承Thread类 1.定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法。
2.创建MyThread类的对象。
3.调用线程对象的start()方法启动线程(启动后还是执行run方法的)。
优点:编码简单。
缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
1 2 3 4 5 6 7 8 public class MyThread extends Thread { @Override public void run () { for (int i = 0 ; i < 5 ; i++) { System.out.println("子线程MyThread输出:" + i); } } }
1 2 3 4 5 Thread t = new MyThread (); t.start();for (int i = 0 ; i < 5 ; i++) { System.out.println("主线程main输出:" + i); }
注意:
1.启动线程必须是调用start方法,不是调用run方法。直接调用run方法会当成普通方法执行 ,此时相当于还是单线程执行,这样主线程一直是先跑完的,相当于是一个单线程的效果。只有调用start方法才是启动一个新的线程执行。
2.不要把主线程任务放在启动子线程之前。这样会导致先运行完主线程任务,然后再启动子线程。
实现Runnable接口 1.定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法。
2.创建MyRunnable任务对象。(简化:创建Runnable的匿名内部类对象。)
3.把MyRunnable任务对象交给Thread处理。
1 public Thread (Runnable target)
4.调用线程对象的start()方法启动线程。
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个Runnable对象。
1 2 3 4 5 6 7 8 public class MyRunnable implements Runnable { @Override public void run () { for (int i = 0 ; i < 5 ; i++) { System.out.println("子线程MyRunnable输出:" + i); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 Runnable target = new MyRunnable ();new Thread (target).start();for (int i = 0 ; i < 5 ; i++) { System.out.println("主线程main输出:" + i); }Runnable target2 = new Runnable (){ @Override public void run () { for (int i = 0 ; i < 5 ; i++) { System.out.println("子线程1输出:" + i); } } };new Thread (target2).start();new Thread (new Runnable () { @Override public void run () { for (int i = 0 ; i < 5 ; i++) { System.out.println("子线程2输出:" + i); } } }).start();new Thread (() -> { for (int i = 0 ; i < 5 ; i++) { System.out.println("子线程3输出:" + i); } }).start();
实现Callable接口 1.创建任务对象
定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。
把Callable类型的对象封装成FutureTask(线程任务对象)。
2.把线程任务对象交给Thread对象。
3.调用Thread对象的start方法启动线程。
4.线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。
1 2 public FutureTask<>(Callable call) public V get () throws Exception
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MyCallable implements Callable <String> { private int n; public MyCallable (int n) { this .n = n; } @Override public String call () throws Exception { int sum = 0 ; for (int i = 1 ; i <= n; i++) { sum += i; } return "线程" + Thread.currentThread().getName() + "求出1-" + n + "的和是:" + sum; } }
1 2 3 4 5 6 7 8 9 10 11 12 Callable<String> call = new MyCallable (100 ); FutureTask<String> f1 = new FutureTask <>(call);new Thread (f1).start();String rs = f1.get(); System.out.println(rs);
Thread类的常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 public class MyThread extends Thread { public MyThread () {} public MyThread (String name) { super (name); } @Override public void run () { Thread t = Thread.currentThread(); for (int i = 0 ; i < 5 ; i++) { System.out.println(t.getName() + "输出:" + i); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String[] args) throws Exception { Thread t1 = new MyThread ("1号线程" ); t1.start(); t1.join(); System.out.println(t1.getName()); Thread t2 = new MyThread (); t2.setName("2号线程" ); t2.start(); Thread.sleep(2000 ); Thread m = Thread.currentThread(); System.out.println(m.getName()); for (int i = 0 ; i < 5 ; i++) { System.out.println("主线程main输出:" + i); } }
线程安全 线程安全问题:多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
线程安全问题出现的原因:
存在多个线程在同时执行。
同时访问一个共享资源。
存在修改该共享资源。
场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元。
用程序模拟线程安全问题 需求:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,模拟2人同时去取钱10万。
需要提供一个账户类,接着创建一个账户对象代表2个人的共享账户。
需要定义一个线程类(用于创建两个线程,分别代表小明和小红)。
创建2个线程,传入同一个账户对象给2个线程处理。
启动2个线程,同时去同一个账户对象中取钱10万。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Account { private String cardId; private double money; public Account (String cardId, double money) { this .cardId = cardId; this .money = money; } public void drawMoney (double money) { String name = Thread.currentThread().getName(); if (this .money >= money) { System.out.println(name + "来取钱" + money + "成功" ); this .money -= money; System.out.println(name + "来取钱后,余额剩余:" + this .money); }else { System.out.println(name + "来取钱,余额不足" ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 public class DrawThread extends Thread { private Account acc; public DrawThread (Account acc, String name) { super (name); this .acc = acc; } @Override public void run () { acc.drawMoney(100000 ); } }
1 2 3 4 5 6 7 8 9 Account acc = new Account ("ICBC" , 100000 );new DrawThread (acc, "小明" ).start();new DrawThread (acc, "小红" ).start();
线程同步 线程同步让多个线程实现先后依次访问共享资源 ,这样就解决了线程安全问题。
线程同步的常见方案:加锁 ,每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
同步代码块 作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。
注意:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
使用共享资源作为锁对象,对于实例方法使用this 作为锁对象。
对于静态方法 建议使用字节码(类名.class) 对象作为锁对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Account { private String cardId; private double money; public Account (String cardId, double money) { this .cardId = cardId; this .money = money; } public static void test () { synchronized (Account.class){ } } public void drawMoney (double money) { String name = Thread.currentThread().getName(); synchronized (this ) { if (this .money >= money) { System.out.println(name + "来取钱" + money + "成功" ); this .money -= money; System.out.println(name + "来取钱后,余额剩余:" + this .money); }else { System.out.println(name + "来取钱,余额不足" ); } } } }
同步方法 作用:把访问共享资源的核心方法给上锁,以此保证线程安全。
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
同步方法底层原理:
同步方法其实底层也是有隐式锁对象 的,只是锁的范围是整个方法代码 。
如果方法是实例方法 :同步方法默认用this 作为锁对象。
如果方法是静态方法 :同步方法默认用类名.class 作为锁对象。
同步方法与同步代码块的区别:
范围上:同步代码块锁的范围更小,同步方法锁的范围更大。
可读性:同步方法更好。
1 2 3 public synchronized static void test () {}public synchronized void drawMoney (double money) {}
Lock锁 Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
1 2 3 4 5 6 public ReentrantLock () void lock () void unlock ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Account { private String cardId; private double money; private final Lock lk = new ReentrantLock (); public Account (String cardId, double money) { this .cardId = cardId; this .money = money; } public void drawMoney (double money) { String name = Thread.currentThread().getName(); try { lk.lock(); if (this .money >= money) { System.out.println(name + "来取钱" + money + "成功" ); this .money -= money; System.out.println(name + "来取钱后,余额剩余:" + this .money); }else { System.out.println(name + "来取钱,余额不足" ); } } catch (Exception e) { e.printStackTrace(); } finally { lk.unlock(); } } }
线程通信 当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
线程通信的常见模型(生产者与消费者模型 ):
生产者线程负责生产数据。
消费者线程负责消费生产者生产的数据。
生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产。
案例
3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上。
2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class Desk { private List<String> list = new ArrayList <String>(); public synchronized void put () { String name = Thread.currentThread().getName(); try { if (list.size() == 0 ){ list.add(name + "做的肉包子" ); System.out.println(name + "做了一个肉包子" ); Thread.sleep(2000 ); this .notifyAll(); this .wait(); }else { this .notifyAll(); this .wait(); } } catch (Exception e) { e.printStackTrace(); } } public synchronized void get () { String name = Thread.currentThread().getName(); try { if (list.size() == 1 ){ System.out.println(name + "吃了:" + list.get(0 ) + "做的肉包子" ); list.clear(); Thread.sleep(2000 ); this .notifyAll(); this .wait(); }else { this .notifyAll(); this .wait(); } } catch (Exception e) { e.printStackTrace(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class Test2 { public static void main (String[] args) { Desk desk = new Desk (); new Thread (()->{ while (true ){ desk.put(); } }, "厨师1" ).start(); new Thread (()->{ while (true ){ desk.put(); } }, "厨师2" ).start(); new Thread (()->{ while (true ){ desk.put(); } }, "厨师3" ).start(); new Thread (()->{ while (true ){ desk.get(); } }, "吃货1" ).start(); new Thread (()->{ while (true ){ desk.get(); } }, "吃货2" ).start(); } }
线程池 线程池是一个可以复用线程 的技术。
不使用线程池的问题:用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了又要创建新线程处理, 而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
创建线程池 线程池的接口:ExecutorService
1.使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
注意:
临时线程创建的时间:新任务提交时发现核心线程都在忙 ,任务队列也满了 ,并且还可以创建临时线程,此时才会创建临时线程。
拒绝新任务的时间:核心线程和临时线程都在忙 ,任务队列也满了 ,新的任务过来的时候才会开始拒绝任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 ExecutorService pool = new ThreadPoolExecutor (3 , 5 , 8 , TimeUnit.SECONDS, new ArrayBlockingQueue <>(4 ), Executors.defaultThreadFactory(), new ThreadPoolExecutor .AbortPolicy()); new ThreadFactory (){ @Override public Thread newThread (Runnable r) { return new Thread (r); } };
2.使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
线程池处理Runnable任务
1 2 3 4 5 6 7 8 9 10 11 12 public class MyRunnable1 implements Runnable { @Override public void run () { System.out.println(Thread.currentThread().getName() + " -> 输出" ); try { Thread.sleep(Integer.MAX_VALUE); } catch (Exception e) { e.printStackTrace(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class Test3 { public static void main (String[] args) { ExecutorService pool = new ThreadPoolExecutor (3 , 5 , 8 , TimeUnit.SECONDS, new ArrayBlockingQueue <>(4 ), Executors.defaultThreadFactory(), new ThreadPoolExecutor .AbortPolicy()); Runnable target = new MyRunnable1 (); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); } }
线程池处理Callable任务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Test4 { public static void main (String[] args) throws Exception{ ExecutorService pool = new ThreadPoolExecutor (3 , 5 , 8 , TimeUnit.SECONDS, new ArrayBlockingQueue <>(4 ), Executors.defaultThreadFactory(), new ThreadPoolExecutor .AbortPolicy()); Future<String> f1 = pool.submit(new MyCallable (100 )); Future<String> f2 = pool.submit(new MyCallable (200 )); Future<String> f3 = pool.submit(new MyCallable (300 )); Future<String> f4 = pool.submit(new MyCallable (400 )); System.out.println(f1.get()); System.out.println(f2.get()); System.out.println(f3.get()); System.out.println(f4.get()); } }
Executors工具类实现线程池 Executors是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Test4 { public static void main (String[] args) throws Exception{ ExecutorService pool = Executors.newFixedThreadPool(3 ); Future<String> f1 = pool.submit(new MyCallable (100 )); Future<String> f2 = pool.submit(new MyCallable (200 )); Future<String> f3 = pool.submit(new MyCallable (300 )); Future<String> f4 = pool.submit(new MyCallable (400 )); System.out.println(f1.get()); System.out.println(f2.get()); System.out.println(f3.get()); System.out.println(f4.get()); } }
Executors使用可能存在的陷阱:大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
阿里巴巴Java开发手册【强制】 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors返回的线程池对象的弊端如下:
1.FixedThreadPool和 SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2.CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
核心线程池数量的配置 :
计算密集型的任务:核心线程数量 = CPU核数 + 1
IO密集型的任务:核心线程数量 = CPU核数 * 2
进程 进程 正在运行的程序(软件) 就是一个独立的进程。线程是属于进程的,一个进程中可以同时运行很多个线程。进程中的多个线程 其实是并发 和并行 执行的。
并发 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务 ,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
并行 在同一个时刻上,同时有多个线程在被CPU调度 执行。
线程的生命周期 Java总共定义了线程的6种状态,6种状态都定义在Thread类的内部枚举类 中。
悲观锁、乐观锁 1.悲观锁:一上来就加锁,每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差。
1 2 3 4 5 6 7 8 9 10 11 public class MyRunnable2 implements Runnable { private int count; @Override public void run () { for (int i = 0 ; i < 100 ; i++) { synchronized (this ) { System.out.println("count ===> " + (++count)); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 public class Test5 { public static void main (String[] args) { Runnable target = new MyRunnable2 (); for (int i = 0 ; i < 100 ; i++) { new Thread (target).start(); } } }
2.乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。
1 2 3 4 5 6 7 8 9 10 public class MyRunnable3 implements Runnable { private AtomicInteger count = new AtomicInteger (); @Override public void run () { for (int i = 0 ; i < 100 ; i++) { System.out.println(Thread.currentThread().getName() + "count ===> " + count.incrementAndGet()); } } }
1 2 3 4 5 6 7 8 9 10 11 12 public class Test5 { public static void main (String[] args) { Runnable target = new MyRunnable3 (); for (int i = 0 ; i < 100 ; i++) { new Thread (target).start(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public final int incrementAndGet () { return U.getAndAddInt(this , VALUE, 1 ) + 1 ; }@IntrinsicCandidate public final int getAndAddInt (Object o, long offset, int delta) { int v; do {vv v = getIntVolatile(o, offset); } while (!weakCompareAndSetInt(o, offset, v, v + delta)); return v; }@IntrinsicCandidate public final boolean weakCompareAndSetInt (Object o, long offset, int expected, int x) { return compareAndSetInt(o, offset, expected, x); }
案例 有100份礼品,小红,小明两人同时发送,当剩下的礼品小于10份的时候则不再送出,利用多线程模拟该过程并将线程的名称打印出来。并最后在控制台分别打印小红,小明各自送出多少分礼物。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class SendThread extends Thread { private List<String> gift; private int count; public SendThread (List<String> gift, String name) { super (name); this .gift = gift; } @Override public void run () { String name = Thread.currentThread().getName(); Random r = new Random (); while (true ) { synchronized (gift) { if (gift.size() < 10 ) { break ; } String rs = gift.remove(r.nextInt(gift.size())); System.out.println(name + "发出了:" + rs); count++; } } } public int getCount () { return count; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Demo { public static void main (String[] args) throws Exception{ List<String> gift = new ArrayList <String>(); String[] names = {"口红" , "包包" , "鲜花" , "剃须刀" , "皮带" , "手表" }; Random r = new Random (); for (int i = 0 ; i < 100 ; i++) { gift.add(names[r.nextInt(names.length)] + (i+1 )); } SendThread xm = new SendThread (gift, "小明" ); xm.start(); SendThread xh = new SendThread (gift, "小红" ); xh.start(); xm.join(); xh.join(); System.out.println(xm.getCount()); System.out.println(xh.getCount()); } }