Java多线程学习,自己整理的学习笔记
一、基础概念
程序:
完成特定的任务,用某种语言编写的一组指令的集合。(静态代码)
进程:
线程:
(1)进程的进一步细化,进程执行的其中一条路径
(2)线程拥有独立的栈和独立的程序计数器
(3)多个线程共享堆和方法区,方便线程之间的通讯,但也带来安全隐患
并行和并发:
并行:多个CPU同时执行多个任务
并发:一个CPU“同时”执行多个任务
多线程优点:
提高应用程序的响应,对图形化界面更有意义,可以增强用于体验
提高计算机系统CPU的利用率
改善程序结构,将复杂的进程分为多个线程,利于理解和修改
何时需要多线程:
(1)程序需要同时执行多个任务
(2)程序实现一些需要等待的任务。如用户输入、文件读写操作、网络操作、搜索等
(3)需要一些后台运行的程序
二、创建多线程 多线程的创建,需要使用到java.lang.Thread类中的内容
2.1 创建线程的方法
方法一:
(1)创建一个继承与Thread的子类
(2)重写Thread类的run() -> 将线程执行的操作[方法体]声明在run()中
(3)常见Thread类子类的对象
(4)通过此对象调用start()来新开一个线程来执行,不是使用main()中的线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 代码测试: public class ThreadTest { public static void main (String[] args) { MyThread myThread = new MyThread(); myThread.start(); System.out.println("helloworld" ); } } class MyThread extends Thread { @Override public void run () { for (int i = 0 ; i < 100 ; i++) { if (i % 2 == 0 ){ System.out.println(i); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 整合方法: public class ThreadTest { public static void main (String[] args) { new Thread(){ @Override public void run () { for (int i = 0 ; i < 100 ; i++) { if (i % 2 == 0 ) { System.out.println(i); } } } }.start(); } }
方法二:
1.创建一个实现了Runnable接口的类
2.实现了类去实现Runable中的抽象方法:Run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class MThread implements Runnable { @Override public void run () { for (int i = 0 ; i < 100 ; i++) { if (i%2 == 0 ){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadCreate2 { public static void main (String[] args) { MThread mThread = new MThread(); Thread thread = new Thread(mThread); thread.start(); } }
比较创建线程的两种方式:
1.开发中,优先选择:实现Runnable接口的方式
(1)实现的方式没有类的单继承性的局限性
(2)实现的方式更适合处理多个线程共享数据的情况
2.联系:Thread类其实也是继承于Runnable接口
public class Thread implements Runnable
3.相同点:两个方式都需要重新run(),将执行的逻辑声明在run()中
三、Thread中的方法 3.1 基本方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 start():启动当前线程,调用当前线程的run() run():通常需要重写此方法,将创建的线程要执行的操作声明在此方法中 currentThread():静态方法,返回当前代码的线程 getName():获去当前线程名字 setName():设置当前线程的名字 yield():释放当前CPU的执行权 join():在线程A中调用线程B的join(),此时线程A进入阻塞状态,知道线程B执行完后,线程A才继续执行(可以理解为插队,但线程不一样) stop():已过时,执行此方法时,强制结束当前线程 sleep():使当前线程“睡眠”一段时间[单位:毫秒],在该时间内线程是阻塞状态。静态方法,会报异常,需要try -catch isAlive():判断线程是否还存活
3.2 线程优先级
在Thread方法中存在三个常量,来表示线程的优先级
1 2 3 MAX_PRIORITY: 10 MIN_PRIORITY: 1 NORM_PRIORITY: 5 --> 默认优先级
1 2 getPriority(); 获取优先级 setPriority(); 设置优先级
注意 :优先级高只是被CPU执行的概率变高,但不一定是优先执行
四、线程的生命周期
五、同步 同步是用于解决使用共享数据引发的线程安全问题
5.1 经典线性安全问题
100张票,3个窗口同时进行售卖 (1)使用继承Thread类方式实现:
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 public class Thread1Test { public static void main (String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("窗口一" ); w2.setName("窗口二" ); w3.setName("窗口三" ); w1.start(); w2.start(); w3.start(); } } class Window extends Thread { private static int ticket = 100 ; @Override public void run () { while (true ){ if (ticket > 0 ){ System.out.println(getName() + "卖票,票号为:" + ticket); ticket --; } else { break ; } } } }
(2)使用Runnable接口的方式实现
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 public class Thread2Test { public static void main (String[] args) { Window1 window1 = new Window1(); Thread t1 = new Thread(window1); Thread t2 = new Thread(window1); Thread t3 = new Thread(window1); t1.setName("窗口一" ); t2.setName("窗口二" ); t3.setName("窗口三" ); t1.start(); t2.start(); t3.start(); } } class Window1 implements Runnable { private int ticket = 100 ; @Override public void run () { while (true ){ if (ticket > 0 ){ System.out.println(Thread.currentThread().getName() + "卖票:票号" + ticket); ticket--; } } } }
以上的方法都会出现重票,错票的情况,是由于使用了共享数据ticket的问题
5.2 同步方法一:同步代码块 1 2 3 synchronized (同步监视器){ }
同步监视器: 俗称“锁”,任何一个类的对象都可以充当锁,但必须多个线程用的是同一把锁
补充: 可以考虑使用”this”或者”类名.class”来充当锁,但慎重考虑是否是唯一的锁
(1)对继承Thread方法的进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Window extends Thread { private static int ticket = 100 ; @Override public void run () { while (true ){ synchronized (Window.class){ if (ticket > 0 ){ System.out.println(getName() + "卖票,票号为:" + ticket); ticket --; } else { break ; } } } } }
(2)对实现Runnable接口的进行修改
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 class SaleTicket implements Runnable { private int ticket = 100 ; Object obj = new Object(); @Override public void run () { while (true ) { synchronized (obj) { if (ticket > 0 ) { System.out.println(Thread.currentThread().getName() + "卖票:票号" + ticket); ticket--; } else { break ; } } } } }
5.3 同步方法二:同步方法 1 2 3 private synchronized void 方法名(){ }
说明: 将操作共享数据的代码封装到一个方法中,并对该方法声明为synchronized
补充:
1.同步方法依然使用了同步监视器,只是不需要我们显示声明
2.非静态同步方法的同步监视器是:this
3.静态同步方法的同步监视器是:当前类本身
(1)对继承Thread方法的进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class SaleTicket2 implements Runnable { private static int ticket = 100 ; @Override public void run () { while (true ) { show(); if (ticket == 0 ){ break ; } } } private static synchronized void show () { if (ticket > 0 ) { System.out.println(Thread.currentThread().getName() + "卖票:票号" + ticket); ticket--; } } }
(2)对实现Runnable接口的进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class SaleTicket2 implements Runnable { private int ticket = 100 ; @Override public void run () { while (true ) { show(); if (ticket == 0 ){ break ; } } } private synchronized void show () { if (ticket > 0 ) { System.out.println(Thread.currentThread().getName() + "卖票:票号" + ticket); ticket--; } } }
六、死锁 6.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 47 public class DeadLockTest { public static void main (String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); new Thread(){ @Override public void run () { synchronized (s1){ s1.append("123" ); try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2){ s2.append("456" ); System.out.println(s1); System.out.println(s2); } } } }.start(); new Thread(new Runnable() { @Override public void run () { synchronized (s2) { s2.append("123" ); synchronized (s1) { s1.append("456" ); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
说明:
(1)出现死锁后,不会出现异常,不会出现提示,只是所有线程进入阻塞状态,无法执行
(2)使用同步时,要注意避免死锁。就如使用循环避免死循环。
七、线程通信 7.1 理解
7.2 实现方法
wait(),使线程进入阻塞状态,并会释放当前拥有的锁
notify(),唤醒被wait()的一个线程,有多个被wait()的线程,唤醒优先级高的线程
notifyAll(),唤醒所有被wait()的线程
7.3 举例
线程通信例子:使用两个线程打印1-100。线程1,线程2,交替打印
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 public class communicationTest { public static void main (String[] args) { Number number = new Number(); Thread t1 = new Thread(number); Thread t2 = new Thread(number); t1.setName("线程1" ); t2.setName("线程2" ); t1.start(); t2.start(); } } class Number implements Runnable { private int num = 1 ; @Override public void run () { while (true ){ synchronized (this ) { this .notify(); if (num <= 100 ){ System.out.println(Thread.currentThread().getName() + "--打印了:" + num); num ++; try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { break ; } } } } }
八、JDK5.0后新增的内容 8.1新增同步方法
Lock锁:
1.导入java.util.concurrent.locks.ReentrantLock包
2.创建一个实例化的Reentrantlock对象
可以选择传入参数“true”,使线程之间交互执行,形成线程通信
3.try-finally包裹执行共享数据的代码
4.在try{}内调用lock方法上锁,在finally{}调用unlock方法解锁
注意:lock对象也需要每个线程使用的都是同一对象
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 47 48 49 50 51 public class LockTest { public static void main (String[] args) { Windows w = new Windows(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口一" ); t2.setName("窗口二" ); t3.setName("窗口三" ); t1.start(); t2.start(); t3.start(); } } class Windows implements Runnable { private int ticket = 100 ; private ReentrantLock lock = new ReentrantLock(); @Override public void run () { while (true ){ try { lock.lock(); if (ticket > 0 ){ try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖票:票号为" + ticket); ticket --; }else { break ; } }finally { lock.unlock(); } } } }
8.2 新增线程创建方法
(1)实现Callable接口
1.创建一个Callable的实现类
2.实现call方法,将次线程需要执行的操作声明在call()中
3.创建Callable的实现类对象
4.将实现类对象作为参数,传递到FutureTask中,并创建FutureTask对象
5.FutureTask对象作为参数,传递到Thread中,并创建出对象,并start方法调用
6.若是需要返回值,可以通过FutureTask对象.get()来获取返回值,会抛出异常
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 class NumThread implements Callable { @Override public Object call () throws Exception { int sum = 0 ; for (int i = 0 ; i <= 100 ; i++) { if (i % 2 == 0 ){ System.out.println(i); sum += i; } } return sum; } } public class CreateMethod3 { public static void main (String[] args) { NumThread numthread = new NumThread(); FutureTask futureTask = new FutureTask(numthread); new Thread(futureTask).start(); try { Object sum = futureTask.get(); System.out.println(sum); } catch (Exception e) { e.printStackTrace(); } } }
如何理解实现Callable接口 比 Runnable接口强大
1.call()可以有返回值
2.call()可以抛出异常
3.Callable是支持泛型
(2)线程池
1.提供指定数量的线程池
2.设置线程属性(可选)
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后悔终止
3执行指定线程的操作,需要提供实现Runnable接口 或 Callable接口的实现类的对象
Runnable 用 execute()
Callable 用 submit()
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 public class CreateMethod4 { public static void main (String[] args) { ExecutorService service = Executors.newFixedThreadPool(10 ); ((ThreadPoolExecutor) service).setMaximumPoolSize(10 ); service.execute(new RunTest()); service.submit(new CallTest()); service.shutdown(); } } class RunTest implements Runnable { @Override public void run () { for (int i = 1 ; i <= 100 ; i++) { if (i%2 == 0 ){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } class CallTest implements Callable { @Override public Object call () throws Exception { for (int i = 1 ; i <= 100 ; i++) { if (i%2 != 0 ){ System.out.println(Thread.currentThread().getName() + ":" + i); } } return null ; } }
线程池的好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程的管理
九、面试题