前面面试什么的多线程部分问的比较多,需要加强下,这里记录总结 《Java多线程编程核心技术》 读书情况。
第一章 Java 多线程技能 本章主要介绍了线程概念、核心方法以及优先级。
1、进程和线程的区别
进程 是受操作系统管理的基本运行单元,是系统进行资源分配和调度的独立单位。线程 是在进程中独立运行的子任务,CPU 在这些任务之间不停地切换,各子任务共享程序的内存空间。
线程和进程的区别是什么? - 知乎:https://www.zhihu.com/question/25532384
2、线程的创建
(1)继承 Thread 类
(2)实现 Runnable 接口
其中 Thread 类实现了 Runable 接口,都需要重写里面 run 方法。
两种方式的区别:
实现 Runnable 的类更具有健壮性,避免了单继承的局限。 Runnable 更容易实现资源共享,能多个线程同时处理一个资源。
Java中 Thread和Runnable实现多线程的区别和联系 - CSDN博客:https://blog.csdn.net/oxuanboy1/article/details/51733279
非线程安全 主要是指多个线程对同一对象中的同一变量实例进行操作时会出现值被修改、值不同步的现象。
3、线程的启动和停止
线程的启动可以使用 start() 或者 run() 方法,
调用 start() 方法,虚拟机会创建新的线程运行 run 方法; 调用 run() 方法后,会在调用的线程执行,不会创建新的线程。
线程的终止方法有三种:
(1)使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 (2)使用 stop() 方法强行终止线程(这个方法不推荐使用,因为 stop() 和 suspend()、resume() 一样,也可能发生不可预料的结果)。 (3)使用 interrupt() 方法中断线程。
下面介绍了 interrupt() 方法停止线程,调用该方法知识在当前线程中打了一个停止的标记,并不是真的停止线程。
如何判断线程的状态是不是停止的:
(1)this.interrupted() 测试 当前线程 是否已经中断,会将状态标志置清为 false 。
public static boolean interrupted () {}
(2)this.isInterrupted() 测试线程是否已经中断,不清除状态标志。
public boolean isInterrupted () {}
Java并发(基础知识)—— 创建、运行以及停止一个线程 - Tim-Tom - 博客园:https://www.cnblogs.com/timlearn/p/4007982.html
其他方法 stop()、suspend()、resume() 不推荐使用。
4、线程的主要方法
Thread.currentThread() 返回代码段正在被哪个线程调用的信息。
isAlive() 判断当前线程是否处于活动状态,即线程已经启动但尚未终止。
package com.wshunli.thread;public class CountOperate extends Thread { public CountOperate () { System.out.println("CountOperate---begin" ); System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive()=" + Thread.currentThread().isAlive()); System.out.println("this.getName=" + this .getName()); System.out.println("this.isAlive()=" + this .isAlive()); System.out.println("CountOperate---end " ); System.out.println("Thread.currentThread()==this :" + (Thread.currentThread() == this )); } @Override public void run () { System.out.println("run---begin" ); System.out.println("Thread.currentThread().getName=" + Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive()" + Thread.currentThread().isAlive()); System.out.println("Thread.currentThread()==this :" + (Thread.currentThread() == this )); System.out.println("this.getName()=" + this .getName()); System.out.println("this.isAlive()=" + this .isAlive()); System.out.println("run --- end" ); } } package com.wshunli.thread;public class Main { public static void main (String[] args) { CountOperate c = new CountOperate (); Thread t1 = new Thread (c); System.out.println("main begin t1 isAlive=" + t1.isAlive()); t1.setName("A" ); t1.start(); System.out.println("main end t1 isAlive=" + t1.isAlive()); } }
Thread.sleep() 方法是在指定的毫秒数内让“当前正在执行的线程”休眠(暂停执行)。
yield() 放弃当前 CPU 资源,将它让给其他任务占用 CPU 时间。
5、线程的优先级
在 Java 中线程的优先级分为 1 ~ 10 这 10 个等级,可以使用 setPriority(int newPriority) 方法设置。
优先级较高的线程得到的 CPU 资源较多,CPU 会尽量优先执行,并不一定先执行完。
线程的优先级具有继承性。
6、守护线程
在 java 中线程有两种:用户线程、守护线程。
守护线程的作用是为其他线程提供便利服务,当进程中不存在非守护线程了,守护线程会自动销毁。
守护线程最典型的应用就是 GC (垃圾回收器)。
第二章 对象及变量的并发访问 synchronized 同步方法 (1)方法内的变量线程安全,而 实例变量 非线程安全。
(2)多个对象多个锁
关键字 synchronized 取得的锁都是对象锁,而不是把方法或者代码段当作锁。
class HasSelfPrivateNum { public void addI (String username) { try { int num = 0 ; if (username.equals("a" )) { num = 100 ; System.out.println("a set over!" ); Thread.sleep(2000 ); } else { num = 200 ; System.out.println("b set over!" ); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadA extends Thread { private HasSelfPrivateNum numRef; public ThreadA (HasSelfPrivateNum numRef) { super (); this .numRef = numRef; } @Override public void run () { super .run(); numRef.addI("a" ); } } class ThreadB extends Thread { private HasSelfPrivateNum numRef; public ThreadB (HasSelfPrivateNum numRef) { super (); this .numRef = numRef; } @Override public void run () { super .run(); numRef.addI("b" ); } } class Main { public static void main (String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum (); ThreadA athread = new ThreadA (numRef); athread.start(); ThreadB bthread = new ThreadB (numRef); bthread.start(); } }
对于多个对象实例,会有多个对象锁,所以还是异步的、互不影响。
(3)synchronized 方法与对象锁
调用 synchronized 声明的方法是排队同步执行的。
把 HasSelfPrivateNum 类的 addI() 方法添加 synchronized 关键字,输出就变为:
class HasSelfPrivateNum { synchronized public void addI (String username) { try { int num = 0 ; if (username.equals("a" )) { num = 100 ; System.out.println("a set over!" ); Thread.sleep(2000 ); } else { num = 200 ; System.out.println("b set over!" ); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面多个线程调用同一方法,下面再看调用 不同方法 的情况。
class MyObject { synchronized public void methodA () { try { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); Thread.sleep(5000 ); System.out.println("end endTime=" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public void methodB () { try { System.out.println("begin methodB threadName=" + Thread.currentThread().getName() + " begin time=" + System.currentTimeMillis()); Thread.sleep(5000 ); System.out.println("end" ); } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadA extends Thread { private MyObject object; public ThreadA (MyObject object) { super (); this .object = object; } @Override public void run () { super .run(); object.methodA(); } } class ThreadB extends Thread { private MyObject object; public ThreadB (MyObject object) { super (); this .object = object; } @Override public void run () { super .run(); object.methodB(); } } public class Main { public static void main (String[] args) { MyObject object = new MyObject (); ThreadA a = new ThreadA (object); a.setName("A" ); ThreadB b = new ThreadB (object); b.setName("B" ); a.start(); b.start(); } }
修改下 methodB() 方法,去除 synchronized 关键字。
class MyObject { synchronized public void methodA () { try { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); Thread.sleep(5000 ); System.out.println("end endTime=" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } public void methodB () { try { System.out.println("begin methodB threadName=" + Thread.currentThread().getName() + " begin time=" + System.currentTimeMillis()); Thread.sleep(5000 ); System.out.println("end" ); } catch (InterruptedException e) { e.printStackTrace(); } } }
由此可见:
A 线程先持有 object 对象锁,B 线程可以以异步方式调用 object 对象中的 非 synchronized 类型 的方法。 A 线程先持有 object 对象锁,B 线程如果在这时调用 object 对中的 synchronized 类型 的方法则需等待,也就是同步。
(4)脏读
脏读(dirtyRead)即赋值时进行了同步,但在取值时已经被其他线程修改过了。
跟上例类似,在方法上加 synchronized 关键字可以解决脏读问题。
(5)synchronized 锁重入
synchronized 具有锁重入功能,也就说当一个线程获得对象锁之后,再次请求此对象锁可以再次得到该对象的锁。
public class Service { synchronized public void service1 () { System.out.println("service1" ); service2(); } synchronized public void service2 () { System.out.println("service2" ); service3(); } synchronized public void service3 () { System.out.println("service3" ); } }
可重入锁的概念:自己可以再次获取自己的内部锁。假如说不可锁重入的话,线程一直等待释放对象锁,而对象锁自己拥有,就会造成死锁。
可重入锁 也支持在父子类继承的环境中,即子类可以通过重入锁调用父类的同步方法。
(6)当线程执行的代码出现异常时,其所持有的锁会自动释放。
(7)同步不可以继承。
class Super { synchronized public void serviceMethod () { try { System.out.println("int main 下一步 sleep begin threadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); Thread.sleep(5000 ); System.out.println("int main 下一步 sleep end threadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } class Sub extends Super { @Override public void serviceMethod () { try { System.out.println("int sub 下一步 sleep begin threadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); Thread.sleep(5000 ); System.out.println("int sub 下一步 sleep end threadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); super .serviceMethod(); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyThreadA extends Thread { private Sub sub; public MyThreadA (Sub sub) { super (); this .sub = sub; } @Override public void run () { sub.serviceMethod(); } } class MyThreadB extends Thread { private Sub sub; public MyThreadB (Sub sub) { super (); this .sub = sub; } @Override public void run () { sub.serviceMethod(); } } public class Main { public static void main (String[] args) { Sub subRef = new Sub (); MyThreadA a = new MyThreadA (subRef); a.setName("A" ); a.start(); MyThreadB b = new MyThreadB (subRef); b.setName("B" ); b.start(); } }
子类方法添加 synchronized 关键字。
class Sub extends Super { @Override synchronized public void serviceMethod () { try { System.out.println("int sub 下一步 sleep begin threadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); Thread.sleep(5000 ); System.out.println("int sub 下一步 sleep end threadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); super .serviceMethod(); } catch (InterruptedException e) { e.printStackTrace(); } } }
由此可见:重写父类的同步方法并不能同步,还是要加 synchronized 关键字才行。
synchronized 同步语句块 (1)synchronized 同步语句块的使用
当两个线程同时访问同一对象 object 中的 synchronized (this) 同步代码块时,一段时间内只能有一个线程被执行,另一个线程要等这个线程执行完以后才能执行。
(2)用同步代码块解决同步方法的弊端
同步方法的弊端是有时候执行时间会比较长。
public class Task { private String getData1; private String getData2; public synchronized void doLongTimeTask () { try { System.out.println("begin task" ); Thread.sleep(3000 ); getData1 = "长时间处理任务后从远程返回的值1 threadName=" + Thread.currentThread().getName(); getData2 = "长时间处理任务后从远程返回的值2 threadName=" + Thread.currentThread().getName(); System.out.println(getData1); System.out.println(getData2); System.out.println("end task" ); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Task { private String getData1; private String getData2; public void doLongTimeTask () { try { System.out.println("begin task" ); Thread.sleep(3000 ); String privateGetData1 = "长时间处理任务后从远程返回的值1 threadName=" + Thread.currentThread().getName(); String privateGetData2 = "长时间处理任务后从远程返回的值2 threadName=" + Thread.currentThread().getName(); synchronized (this ) { getData1 = privateGetData1; getData2 = privateGetData2; } System.out.println(getData1); System.out.println(getData2); System.out.println("end task" ); } catch (InterruptedException e) { e.printStackTrace(); } } }
其实也就是在耗时部分是异步执行的,从而缩短了代码的执行时长。
当一个线程访问 同步代码块 时,另一个线程仍然可以访问该对象的非同步代码块。即在 synchronized 块中就是同步执行,不在 synchronized 块中时异步执行。
(3)synchronized 代码块之间的同步性
在使用 synchronized (this) 代码块时,当线程访问 object 的一个 synchronized (this) 代码块时,其他线程对同一 object 中所有 synchronized (this) 同步代码块的访问将阻塞。
和 synchronized 方法一样,synchronized (this) 代码块也是锁定 当前对象 的。其他对象访问同一 object 中的 synchronized 方法也会阻塞。
(4)将任意对象作为对象监视器
多个线程调用同一个对象中的不同名称的 synchronized 同步方法或 synchronized(this) 同步代码块时,调用的效果就是按顺序执行,也就是 同步的,阻塞的 。
这说明 synchronized 同步方法或 synchronized(this) 同步代码块分别有 两种作用 :
synchronized 同步 方法
对其他 synchronized 同步方法或 synchronized(this) 同步代码块呈阻塞状态。
同一时间只有一个线程可以执行 synchronized 同步方法中的代码。
synchronized(this) 同步 代码块
对其他 synchronized 同步方法或 synchronized(this) 同步代码块呈阻塞状态。
同一时间只有一个线程可以执行 synchronized(this) 同步代码块中的代码。
类似,对于 synchronized(非this对象) 同步代码块:
当多个线程持有 “对象监视器” 为同一个对象的前提下,同一时间只有一个线程可以执行 synchronized(非this对象x) 同步代码块中的代码。
public class Service { private String usernameParam; private String passwordParam; public void setUsernamePassword (String username, String password) { try { String anyString = new String (); synchronized (anyString) { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块" ); usernameParam = username; Thread.sleep(3000 ); passwordParam = password; System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块" ); } } catch (InterruptedException e) { e.printStackTrace(); } } }
而多个线程持有的 “对象监视器” 不是同一对象时,还是异步执行的。
public class Service { private String anyString = new String (); public void a () { try { synchronized (anyString) { System.out.println("a begin" ); Thread.sleep(3000 ); System.out.println("a end" ); } } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public void b () { System.out.println("b begin" ); System.out.println("b end" ); } }
站在多个线程角度,多个线程调用同一方法先后顺序是随机的,只是对于单个线程而言,在同步方法/代码块内部是同步的。
(5)静态 synchronized 方法与 synchronized(class) 代码块
对于静态的 synchronized 方法,是对当前 Class 类进行封锁。 对于非静态 synchronized 方法,是对当前 对象 的封锁。
二者是不同的锁,但是 Class 锁可以对所有对象实例起作用。
package com.wshunli.thread.sync5;class Service { synchronized public static void printA () { try { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA" ); Thread.sleep(3000 ); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA" ); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB () { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB" ); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB" ); } } class ThreadA extends Thread { private Service service; public ThreadA (Service service) { super (); this .service = service; } @Override public void run () { service.printA(); } } class ThreadB extends Thread { private Service service; public ThreadB (Service service) { super (); this .service = service; } @Override public void run () { service.printB(); } } public class Main { public static void main (String[] args) { Service service1 = new Service (); Service service2 = new Service (); ThreadA a = new ThreadA (service1); a.setName("A" ); a.start(); ThreadB b = new ThreadB (service2); b.setName("B" ); b.start(); } }
同步 synchronized(class) 代码块的作用其实和 synchronized static 方法的作用一样。
class Service { public static void printA () { synchronized (Service.class) { try { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA" ); Thread.sleep(3000 ); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA" ); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void printB () { synchronized (Service.class) { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB" ); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB" ); } } }
(6)数据类型 String 的常量池特性
对于 synchronized(string ) 同步块,string 值为相同 String 常量时,两个线程持有相同的锁。
所以在大多数情况下,synchronized 代码块不使用 String 作为锁对象。
(7)多线程的死锁
不同的线程都在等待根本不可能释放的锁,从而导致所有任务都无法继续完成。
线程互相持有对方的锁,然后等待对方释放锁就有可能出现死锁。
(8)内置类与静态内置类
对于内置类中有两个同步方法,只要是使用不同的锁,多线程还是异步的。
对于两个内置类 class1 class2 ,class1 中使用 synchronized(class2 ) 同步代码块时,其他线程只能以同步方式调用 class2 中的 synchronized 同步方法
(9)锁对象的改变
再将任何数据作为同步锁时,只需判断多线程是否同时持有相同的锁对象即可。
class MyService { private String lock = "123" ; public void testMethod () { try { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis()); lock = "456" ; Thread.sleep(2000 ); System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadA extends Thread { private MyService service; public ThreadA (MyService service) { super (); this .service = service; } @Override public void run () { service.testMethod(); } } class ThreadB extends Thread { private MyService service; public ThreadB (MyService service) { super (); this .service = service; } @Override public void run () { service.testMethod(); } } public class Main { public static void main (String[] args) throws InterruptedException { MyService service = new MyService (); ThreadA a = new ThreadA (service); a.setName("A" ); ThreadB b = new ThreadB (service); b.setName("B" ); a.start(); Thread.sleep(50 ); b.start(); } }
运行到线程 B 时,对象锁已经改变,所以是异步。
但是对于对象而言,只是改变对象的属性时不行的,运行结果还是同步的。
volatile 关键字 关键字 volatile 的主要作用是使变量在多个线程间可见。
(1)关键字 volatile 与死循环
class PrintString { private boolean isContinuePrint = true ; public boolean isContinuePrint () { return isContinuePrint; } public void setContinuePrint (boolean isContinuePrint) { this .isContinuePrint = isContinuePrint; } public void printStringMethod () { try { while (isContinuePrint == true ) { System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName()); Thread.sleep(1000 ); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Run { public static void main (String[] args) { PrintString printStringService = new PrintString (); printStringService.printStringMethod(); System.out.println("我要停止它! stopThread=" + Thread.currentThread().getName()); printStringService.setContinuePrint(false ); } }
这个程序停不下来,主要原因是 main 线程一直在处理 while() 循环,导致程序不能继续执行。
我们可以使用多线程技术解决这个问题,在子线程中执行 while() 循环,这样主线程就能继续执行了。
class PrintString implements Runnable { private boolean isContinuePrint = true ; public boolean isContinuePrint () { return isContinuePrint; } public void setContinuePrint (boolean isContinuePrint) { this .isContinuePrint = isContinuePrint; } public void printStringMethod () { try { while (isContinuePrint == true ) { System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName()); Thread.sleep(1000 ); } } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run () { printStringMethod(); } } public class Run { public static void main (String[] args) { PrintString printStringService = new PrintString (); new Thread (printStringService).start(); System.out.println("我要停止它! stopThread=" + Thread.currentThread().getName()); printStringService.setContinuePrint(false ); } }
但是这种在 -server 服务器模式下,还是会出现死循环。 主要是因为在服务器模式下,为了提高线程运行的效率,线程一致在私有堆栈中取得 isContinuePrint 的值为 true 。
volatile 关键字增加了实例变量在多个线程之间的可见性,但不支持原子性。
也就是说同步私有堆栈中的值和公共堆栈中的值,强制从公共堆栈中进行取值,或者强制将值写入公共堆栈。
synchronized 和 volatile 的比较 :
1、关键字 volatile 是线程同步的轻量级实现,所以 volatile 性能肯定比 synchronized 要好,并且 volatile 只能修饰变量,而 synchronized 可以修饰方法和代码块。目前在开发中使用 synchronized 关键字的比率还是比较大的。 2、多线程访问 volatile 不会发生阻塞,而 synchronized 会出现阻塞。 3、volatile 能保证数据的可见性,但是不能保证原子性;而 synchronized 可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据同步。 4、volatile 关键字是解决变量在多个线程之间的可见性;而 synchronized 关键字是解决多个线程之间访问资源同步性的。
所谓的线程安全包含 原子性和可见性 两个方面,Java 的同步机制都是围绕这两个方面量保证线程安全的。
volatile和synchronized的区别 - CSDN博客:https://blog.csdn.net/suifeng3051/article/details/52611233
(2)volatile 关键字的非原子性
(3)原子类
原子操作是不可分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。
import java.util.concurrent.atomic.AtomicInteger;public class AddCountThread extends Thread { private AtomicInteger count = new AtomicInteger (0 ); @Override public void run () { for (int i = 0 ; i < 10000 ; i++) { System.out.println(count.incrementAndGet()); } } }
一个原子(atomic)类型就是一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全(thread-safe)。
注意方法之间的调用不是原子操作。
(4)synchronized 代码块具有 volatile 同步功能。
关键字 synchronized 可以使多个线程访问统一资源具有同步性,而且它还具有将线程内存中的私有变量与公共内存中的变量同步的功能。
第三章 线程间通信 进程间通信使系统之间的交互性更强大,在大大提高 CPU 利用率的同时,还可以对个线程任务在处理的过程中进行有效的把控和监督。
等待/通知机制 (1)使用 wait/notify 实现进程间通信
java中等待通知机制(wait/notify) - CSDN博客:https://blog.csdn.net/joenqc/article/details/54981532
wait() 和 notify() 需要在同步方法或者同步代码块中调用。
wait() 会使线程停止运行,而 notify() 使停止的线程继续运行。
import java.util.ArrayList;import java.util.List;class MyList { private static List list = new ArrayList (); public static void add () { list.add("anyString" ); } public static int size () { return list.size(); } } class ThreadA extends Thread { private Object lock; public ThreadA (Object lock) { super (); this .lock = lock; } @Override public void run () { try { synchronized (lock) { if (MyList.size() != 5 ) { System.out.println("wait begin " + System.currentTimeMillis()); lock.wait(); System.out.println("wait end " + System.currentTimeMillis()); } } } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadB extends Thread { private Object lock; public ThreadB (Object lock) { super (); this .lock = lock; } @Override public void run () { try { synchronized (lock) { for (int i = 0 ; i < 10 ; i++) { MyList.add(); if (MyList.size() == 5 ) { lock.notify(); System.out.println("已发出通知!" ); } System.out.println("添加了" + (i + 1 ) + "个元素!" ); Thread.sleep(1000 ); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Main { public static void main (String[] args) { try { Object lock = new Object (); ThreadA a = new ThreadA (lock); a.start(); Thread.sleep(50 ); ThreadB b = new ThreadB (lock); b.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
方法 wait() 被执行后,锁会自动释放;执行完 notify() 方法后,锁不会自动释放,只有同步方法或者代码块执行完后才会释放。
(2)Thread 中关于线程状态的 API
Java 线程一共有 6 个状态,分别是新建(New),RUNNABLE [ 就绪(Ready to run)、运行中(Running)],睡眠(Sleeping),阻塞(Blocked),等待(Waiting),死亡(Dead/Terminate)。
java线程状态切换 - 简书:https://www.jianshu.com/p/531310753a64
每个锁对象都有两个队列:就绪队列、阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。
当一个线程被唤醒 (notify) 后,才会进入到就绪队列,等待 CPU 的调度,反之,当一个线程被等待 (wait) 后,就会进入阻塞队列,等待下一次被唤醒。
(3)wait() 方法的使用
wait() 方法会使线程进入等待状态。
wait(long) 方法是线程等待一段时间内是否被唤醒,超时则自动唤醒。
当线程在 wait() 状态时,调用线程的 interrupt() 方法会出现 InterruptedException 异常。
在以下情况下,持有锁的线程会释放锁: 1、执行完同步代码块。 2、在执行同步代码块的过程中,遇到异常而导致线程终止。 3、在执行同步代码块的过程中,执行了锁所属对象的 wait() 方法,这个线程会释放锁,进行对象的等待池。
(4)通知一个线程和唤醒所有线程
notify() 仅 随机 唤醒一个线程,多次调用也会随机将等待 wait 状态的线程进行唤醒。
notifyAll() 方法可以唤醒所有线程。
但是通知 notify() 方法在 wait() 方法之前调用,后面 wait 线程收不到通知就会一直处于等待状态。
注意线程在等待结束后其外部条件数据值可能发生改变。
(5)生产者/消费者模式
1、一生产者一消费者:
class MyStack { private List list = new ArrayList (); synchronized public void push () { try { if (list.size() == 1 ) { this .wait(); } list.add("anyString=" + Math.random()); this .notify(); System.out.println("push=" + list.size()); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public String pop () { String returnValue = "" ; try { if (list.size() == 0 ) { System.out.println("pop操作中的:" + Thread.currentThread().getName() + " 线程呈wait状态" ); this .wait(); } returnValue = "" + list.get(0 ); list.remove(0 ); this .notify(); System.out.println("pop=" + list.size()); } catch (InterruptedException e) { e.printStackTrace(); } return returnValue; } } class P { private MyStack myStack; public P (MyStack myStack) { super (); this .myStack = myStack; } public void pushService () { myStack.push(); } } class C { private MyStack myStack; public C (MyStack myStack) { super (); this .myStack = myStack; } public void popService () { System.out.println("pop=" + myStack.pop()); } } class P_Thread extends Thread { private P p; public P_Thread (P p) { super (); this .p = p; } @Override public void run () { while (true ) { p.pushService(); } } } class C_Thread extends Thread { private C r; public C_Thread (C r) { super (); this .r = r; } @Override public void run () { while (true ) { r.popService(); } } } public class Main { public static void main (String[] args) { MyStack myStack = new MyStack (); P p = new P (myStack); C r = new C (myStack); P_Thread pThread = new P_Thread (p); C_Thread rThread = new C_Thread (r); pThread.start(); rThread.start(); } }
2、一生产者多消费者:
class MyStack { private List list = new ArrayList (); synchronized public void push () { try { while (list.size() == 1 ) { this .wait(); } list.add("anyString=" + Math.random()); this .notify(); System.out.println("push=" + list.size()); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public String pop () { String returnValue = "" ; try { while (list.size() == 0 ) { System.out.println("pop操作中的:" + Thread.currentThread().getName() + " 线程呈wait状态" ); this .wait(); } returnValue = "" + list.get(0 ); list.remove(0 ); this .notify(); System.out.println("pop=" + list.size()); } catch (InterruptedException e) { e.printStackTrace(); } return returnValue; } }
3、多生产者一消费者。
4、多生产者多消费者。
可能产生假死的情况,使用 notifyAll() 方法通知其他线程即可。
(6)通过管道进行进程间通信
在 Java 语言中提供了各种各样的输入/输出流,其中管道流 pipeStream 可用于不同线程间之间传送数据。
一个线程发送数据到输出管道,另一个线程从输入管道中读取数据。
class WriteData { public void writeMethod (PipedOutputStream out) { try { System.out.println("write :" ); for (int i = 0 ; i < 300 ; i++) { String outData = "" + (i + 1 ); out.write(outData.getBytes()); System.out.print(outData); } System.out.println(); out.close(); } catch (IOException e) { e.printStackTrace(); } } } class ReadData { public void readMethod (PipedInputStream input) { try { System.out.println("read :" ); byte [] byteArray = new byte [20 ]; int readLength = input.read(byteArray); while (readLength != -1 ) { String newData = new String (byteArray, 0 , readLength); System.out.print(newData); readLength = input.read(byteArray); } System.out.println(); input.close(); } catch (IOException e) { e.printStackTrace(); } } } class ThreadWrite extends Thread { private WriteData write; private PipedOutputStream out; public ThreadWrite (WriteData write, PipedOutputStream out) { super (); this .write = write; this .out = out; } @Override public void run () { write.writeMethod(out); } } class ThreadRead extends Thread { private ReadData read; private PipedInputStream input; public ThreadRead (ReadData read, PipedInputStream input) { super (); this .read = read; this .input = input; } @Override public void run () { read.readMethod(input); } } public class Main { public static void main (String[] args) { try { WriteData writeData = new WriteData (); ReadData readData = new ReadData (); PipedInputStream inputStream = new PipedInputStream (); PipedOutputStream outputStream = new PipedOutputStream (); outputStream.connect(inputStream); ThreadRead threadRead = new ThreadRead (readData, inputStream); threadRead.start(); Thread.sleep(2000 ); ThreadWrite threadWrite = new ThreadWrite (writeData, outputStream); threadWrite.start(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
通过管道进行进程间通信可以传递,字节流和字符流。
方法 join 的使用 join() 方法具有使线程排队运行的作用,使所属线程 x 正常执行 run() 方法,而使当前线程 z 进行无限期阻塞,等待线程 x 销毁后继续执行线程 z 后面的代码。
class MyThread extends Thread { @Override public void run () { try { int secondValue = (int ) (Math.random() * 10000 ); System.out.println(secondValue); Thread.sleep(secondValue); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Test { public static void main (String[] args) { try { MyThread threadTest = new MyThread (); threadTest.start(); threadTest.join(); System.out.println("我想当threadTest对象执行完毕后我再执行,我做到了" ); } catch (InterruptedException e) { e.printStackTrace(); } } }
join 方法有点类似与同步的效果。
但是 join() 方法内部是由 wait() 方法进行等待,而 sychronized 关键字是使用 对象监视器 原理作为同步。
在 join 过程中,如果当前线程对象被终端,则当前线程出现异常,而 join 的线程正常执行。
方法 join(long) 中参数是设定等待时间。
long 是最长的等待时间,如果子线程提前结束,主线程也会结束等待。
public final synchronized void join (long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0 ; if (millis < 0 ) { throw new IllegalArgumentException ("timeout value is negative" ); } if (millis == 0 ) { while (isAlive()) { wait(0 ); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0 ) { break ; } wait(delay); now = System.currentTimeMillis() - base; } } }
方法 join() 内部由 wait() 方法实现会立即释放对象锁,而 Thread.sleep() 方法不会释放对象锁。
类 ThreadLocal 的使用 类 ThreadLocal 是保证线程之间变量的隔离性。
class Tools { public static ThreadLocal tl = new ThreadLocal (); } class ThreadA extends Thread { @Override public void run () { try { for (int i = 0 ; i < 100 ; i++) { if (Tools.tl.get() == null ) { Tools.tl.set("ThreadA" + (i + 1 )); } else { System.out.println("ThreadA get Value=" + Tools.tl.get()); } Thread.sleep(200 ); } } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadB extends Thread { @Override public void run () { try { for (int i = 0 ; i < 100 ; i++) { if (Tools.tl.get() == null ) { Tools.tl.set("ThreadB" + (i + 1 )); } else { System.out.println("ThreadB get Value=" + Tools.tl.get()); } Thread.sleep(200 ); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Run { public static void main (String[] args) { try { ThreadA a = new ThreadA (); ThreadB b = new ThreadB (); a.start(); b.start(); for (int i = 0 ; i < 100 ; i++) { if (Tools.tl.get() == null ) { Tools.tl.set("Main" + (i + 1 )); } else { System.out.println("Main get Value=" + Tools.tl.get()); } Thread.sleep(200 ); } } catch (InterruptedException e) { e.printStackTrace(); } } }
类 InheritableThreadLocal 的使用 类 InheritableThreadLocal 可以再子线程中取得父线程继承下来的值。
class InheritableThreadLocalExt extends InheritableThreadLocal { @Override protected Object initialValue () { return new Date ().getTime(); } @Override protected Object childValue (Object parentValue) { return parentValue + " 我在子线程加的~!" ; } } class Tools { public static InheritableThreadLocalExt tl = new InheritableThreadLocalExt (); } class ThreadA extends Thread { @Override public void run () { try { for (int i = 0 ; i < 10 ; i++) { System.out.println("在ThreadA线程中取值=" + Tools.tl.get()); Thread.sleep(100 ); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Run { public static void main (String[] args) { try { for (int i = 0 ; i < 10 ; i++) { System.out.println(" 在Main线程中取值=" + Tools.tl.get()); Thread.sleep(100 ); } Thread.sleep(5000 ); ThreadA a = new ThreadA (); a.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
在继承的同时可以对值进行进一步的处理。
但在子线程取得值的同时,主线程将 InheritableThreadLocal 中的值进行更改,子线程取得的值还是旧值。
第四章 Lock 的使用 ReentrantLock 类 (1)ReentrantLock 实现同步
ReentrantLock reentrantLock = new ReentrantLock ();reentrantLock.lock(); reentrantLock.unlock();
(2)使用 Condition 实现等待/通知
class MyService { private Lock lock = new ReentrantLock (); public Condition condition = lock.newCondition(); public void await () { try { lock.lock(); System.out.println(" await时间为" + System.currentTimeMillis()); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signal () { try { lock.lock(); System.out.println("signal时间为" + System.currentTimeMillis()); condition.signal(); } finally { lock.unlock(); } } } class ThreadA extends Thread { private MyService service; public ThreadA (MyService service) { super (); this .service = service; } @Override public void run () { service.await(); } } public class Run { public static void main (String[] args) throws InterruptedException { MyService service = new MyService (); ThreadA a = new ThreadA (service); a.start(); Thread.sleep(3000 ); service.signal(); } }
这样就实现了 Condition 的通知/等待功能。
对于多个 Condition 实现通知部分线程,可多次调用 lock.newCondition() 方法。
(3)公平锁和非公平锁
锁 Lock 分为公平锁和非公平锁:
公平锁表示线程获取锁的顺序是按照线程 加锁的顺序 来分配的。而非公平锁是抢占机制,是随机获取获取锁的。
ReentrantLock 可使用构造函数实例化公平锁和非公平锁。
(4)ReentrantLock 中的常用方法
getHoldCount() 当前线程保持锁定的个数,也就是调用 lock() 方法的次数。
getQueueLength() 返回等待获得锁定的线程的估计数。
getWaitQueueLength(Condition condition) 返回等待与此锁定相关的给定条件 condition 的线程估计数。
hasQueuedThread(Thread thread) 查询指定线程 thread 是否在等待获得此锁定;hasQueuedThreads() 查询是否有线程在等待次锁。
hasWaiters(Condition condition) 查询是否有线程正在等待与此线程有关的 condition 条件。
isFair() 是不是公平锁。
isHeldByCurrentThread() 当前线程是否保持此锁。
isLocked() 查询此锁定是否由任意线程锁定。
lockInterruptibly() 如果当前线程未中断,则获得此锁;否则出现异常。
tryLock() 调用时未被另外一个线程保持的情况下,才获得此锁定;tryLock(long timeout, TimeUnit unit) 在给定时间内。
ReentrantReadWriteLock 类 读写锁 ReentrantReadWriteLock 表示也有两个锁,一个是与读有关的锁,也成为共享锁;另一个是与写有关的锁,也叫排他锁。
多个读锁之间不互斥,读锁和写锁互斥,写锁和写锁互斥。
(1)读读共享
import java.util.concurrent.locks.ReentrantReadWriteLock;class Service { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock (); public void read () { try { try { lock.readLock().lock(); System.out.println("获得读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(10000 ); } finally { lock.readLock().unlock(); } } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadA extends Thread { private Service service; public ThreadA (Service service) { super (); this .service = service; } @Override public void run () { service.read(); } } class ThreadB extends Thread { private Service service; public ThreadB (Service service) { super (); this .service = service; } @Override public void run () { service.read(); } } public class Main { public static void main (String[] args) { Service service = new Service (); ThreadA a = new ThreadA (service); a.setName("A" ); ThreadB b = new ThreadB (service); b.setName("B" ); a.start(); b.start(); } }
(2)写写互斥
class Service { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock (); public void write () { try { try { lock.writeLock().lock(); System.out.println("获得写锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(10000 ); } finally { lock.writeLock().unlock(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
(3)读写互斥、写读互斥
class Service { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock (); public void read () { try { try { lock.readLock().lock(); System.out.println("获得读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(10000 ); } finally { lock.readLock().unlock(); } } catch (InterruptedException e) { e.printStackTrace(); } } public void write () { try { try { lock.writeLock().lock(); System.out.println("获得写锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(10000 ); } finally { lock.writeLock().unlock(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
第五章 定时器 Timer (1)Timer 的概念
类 Timer 主要负责计划任务的功能,也就是在指定时间开始执行某一个任务。Timer 的封装类是 TimerTask 。
(2)Timer 的常用方法
1、schedule(TimerTask task, Date time) 在指定日期执行一次某一任务。
public class Run { private static Timer timer = new Timer (); static public class MyTask extends TimerTask { @Override public void run () { System.out.println("运行了!时间为:" + new Date ()); } } public static void main (String[] args) { try { MyTask task = new MyTask (); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); String dateString = "2014-10-12 11:55:00" ; Date dateRef = sdf.parse(dateString); System.out.println("字符串时间:" + dateRef.toLocaleString() + " 当前时间:" + new Date ().toLocaleString()); timer.schedule(task, dateRef); } catch (ParseException e) { e.printStackTrace(); } } }
如果 time 晚于当前时间,则按照计划执行;若早于当前时间,则立即执行。
TimerTask 是以任务队列的方式按照顺序知心话,如果前面的任务执行时间较长,后面的任务也会延迟。
2、schedule(TimerTask task, Date firstTime, long period) 在指定日期后,按照指定时间间隔周期性无限循环地执行某一任务。
3、schedule(TimerTask task, long delay) 以当前时间为参考,延迟指定地毫秒数之后执行一次 TimerTask 任务。
4、schedule(TimerTask task, long delay, long period) 以当前时间为参考,延迟指定地毫秒数之后,再以指定时间间隔周期性无限循环地执行某一任务。
5、scheduleAtFixedRate() 方法
主要有两个方法:
scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
scheduleAtFixedRate(TimerTask task, long delay, long period)
方法 schedule 和 scheduleAtFixedRate 区别和联系:
方法 schedule 和 scheduleAtFixedRate 都会顺序序执行,所以不要考虑非线程安全的情况。
方法 schedule 和 scheduleAtFixedRate 主要的区别只在于不延时的情况。
使用 schedule 方法:如果执行任务的时间没有被延时,那么下一次任务的执行时间参考的是上一次任务的“开始”时的时间来计算。
使用 scheduleAtFixedRate 方法:如果执行任务的时间没有被延时,那么下一次任务的执行时间参考的是上一次任务的“结束”时的时间来计算。
延时的情况则没有区别,也就是使用 schedule 或 scheduleAtFixedRate 方法都是如果执行任务的时间被延时,那么下一次任务的执行时间参考的是上一次任务“结束”时的时间来计算。
schedule 方法没有具有任务追赶执行性,而 scheduleAtFixedRate 是有的。
不延时 是指 TimerTask 任务执行的时间比 period 周期要短,不会对下次任务执行造成延迟。追赶执行性 是指 任务执行时间 比当前时间早的情况下,过去时间段内的任务从现在开始执行。
第六章 单例模式与多线程 (1)单例模式概述
单例模式可分为两种:
1、立即执行/“饿汉模式”:使用类的时候已经将对象创建完毕。
public class MyObject { private static MyObject myObject = new MyObject (); private MyObject () { } public static MyObject getInstance () { return myObject; } }
2、延迟加载/“懒汉模式”:在使用类的时候实例才被创建。
DCL 双检查锁机制。
public class MyObject { private volatile static MyObject myObject; private MyObject () { } public static MyObject getInstance () { try { if (myObject != null ) { } else { Thread.sleep(3000 ); synchronized (MyObject.class) { if (myObject == null ) { myObject = new MyObject (); } } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }
(2)单例模式的实现方式
单例模式还可以由静态内置类、static 代码块、enum 枚举数据类实现。
第七章 拾遗增补 线程的状态 线程在不同运行时期有不同的状态,状态信息存储在 State 枚举类中。
A thread can be in one of the following states:
NEW :A thread that has not yet started.
RUNNABLE :A thread executing in the Java virtual machine.
BLOCKED :A thread that is blocked waiting for a monitor lock.
WAITING :A thread that is waiting indefinitely for another thread to perform a particular action.
TIMED_WAITING :A thread that is waiting for another thread to perform an action for up to a specified waiting time.
TERMINATED :A thread that has exited.
A thread can be in only one state at a given point in time. These states are virtual machine states which do not reflect any operating system thread states.
线程组 线程组中可以包含线程对象和线程组,对其进行批量管理和有效组织。
(1)线程组的关联
1 级关联,即没有子孙对象。
ThreadA aRunnable = new ThreadA ();ThreadB bRunnable = new ThreadB ();ThreadGroup group = new ThreadGroup ("线程组" );Thread aThread = new Thread (group, aRunnable);Thread bThread = new Thread (group, bRunnable);aThread.start(); bThread.start();
多级关联,存在子孙对象。
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();ThreadGroup group = new ThreadGroup (mainGroup, "A" );Runnable runnable = new Runnable () { @Override public void run () { try { System.out.println("runMethod!" ); Thread.sleep(10000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread newThread = new Thread (group, runnable);newThread.setName("Z" ); newThread.start();
(2)线程组自动归属特性
自动归属就是在实例化一个 ThreadGroup 线程组 x 时,如果不指定所属的线程组,则 x 线程组会自动归到当前线程对象所属的线程组中。
(3)线程组的操作
获取父线程组,ThreadGroup 的 getParent() 方法获取父线程组。
Thread.currentThread().getThreadGroup().getParent()
线程组内的线程批量停止,ThreadGroup 的 interrupt() 方法。
类 SimpleDateFormat 主要负责日期的转化和格式化,但在多线程环境下,非常容易造成数据转换和处理的不准确,SimpleDateFormat 不是线程安全的。
多线程出现异常的处理 (1)线程中出现异常的处理
MyThread t1 = new MyThread ();t1.setName("线程t1" ); t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler () { @Override public void uncaughtException (Thread t, Throwable e) { System.out.println("线程:" + t.getName() + " 出现了异常:" ); e.printStackTrace(); } }); t1.start(); MyThread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler () { @Override public void uncaughtException (Thread t, Throwable e) { System.out.println("线程:" + t.getName() + " 出现了异常:" ); e.printStackTrace(); } });
(2)线程组中出现异常的处理
public class MyThreadGroup extends ThreadGroup { public MyThreadGroup (String name) { super (name); } @Override public void uncaughtException (Thread t, Throwable e) { super .uncaughtException(t, e); this .interrupt(); } }
注意线程组中的线程对象 run() 方法内部不要有 catch 语句,否则 uncaughtException 不执行。
(3)线程组异常处理的传递
package com.wshunli.thread.sync71;class MyThread extends Thread { private String num = "a" ; public MyThread () { super (); } public MyThread (ThreadGroup group, String name) { super (group, name); } @Override public void run () { int numInt = Integer.parseInt(num); System.out.println("在线程中打印:" + (numInt + 1 )); } } class MyThreadGroup extends ThreadGroup { public MyThreadGroup (String name) { super (name); } @Override public void uncaughtException (Thread t, Throwable e) { super .uncaughtException(t, e); System.out.println("线程组的异常处理" ); e.printStackTrace(); } } class ObjectUncaughtExceptionHandler implements Thread .UncaughtExceptionHandler { @Override public void uncaughtException (Thread t, Throwable e) { System.out.println("对象的异常处理" ); e.printStackTrace(); } } class StateUncaughtExceptionHandler implements Thread .UncaughtExceptionHandler { @Override public void uncaughtException (Thread t, Throwable e) { System.out.println("静态的异常处理" ); e.printStackTrace(); } } public class Main { public static void main (String[] args) { MyThread myThread = new MyThread (); myThread.setUncaughtExceptionHandler(new ObjectUncaughtExceptionHandler ()); myThread.start(); } } public class Main { public static void main (String[] args) { MyThread myThread = new MyThread (); MyThread.setDefaultUncaughtExceptionHandler(new StateUncaughtExceptionHandler ()); myThread.start(); } }
对于线程组而言:
public class Main { public static void main (String[] args) { MyThreadGroup group = new MyThreadGroup ("我的线程组" ); MyThread myThread = new MyThread (group, "我的线程" ); myThread.setUncaughtExceptionHandler(new ObjectUncaughtExceptionHandler ()); myThread.start(); } } public class Main { public static void main (String[] args) { MyThreadGroup group = new MyThreadGroup ("我的线程组" ); MyThread myThread = new MyThread (group, "我的线程" ); MyThread.setDefaultUncaughtExceptionHandler(new StateUncaughtExceptionHandler ()); myThread.start(); } }
本书是阅读完了,也只是一些多线程的基础,距离实战还是有差距,后面再深入学习 《Java并发编程的艺术》、《Java并发编程实战》等书籍逐步提高。