[TOC]
进程, 线程, 协程
转载地址:https://www.cnblogs.com/williamjie/p/11195069.html
进程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
线程
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
什么是上下文
所谓上下文,就是当时运行的环境本身。其实说白了,程序上下文可以理解为context实例中的全局变量,你给它什么样的值,它就呈现对应的值或者状态。
并发编程中的三个概念
1.原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
对基本数据类型的变量的读取和赋值是保证原子性的, 要么成功,要么失败, 这些操作是不可被中断的。
a = 10; // 原子性
b = a; // 不满足
c++; // 不满足
c = c + 1; // 不满足
2.可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
3.有序性
有序性:即程序执行的顺序按照代码的先后顺序执行。
守护线程,非守护线程,主线程
原文链接:https://blog.csdn.net/lc1010078424/article/details/79613348
Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个守护线程,而用户线程都已经退出运行时,VM就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,VM就不会退出。
守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。
虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。、
主线程
main,但不是守护线程。
守护线程
是指在程序运行的时候在后台提供一种通用服务的线程。如gc。
非守护线程
也叫用户线程,由用户创建。
关系
注意:主线程(main)和守护线程一起销毁;
主线程和非守护线程互不影响。
优点及使用场景
在主线程关闭后无需手动关闭守护线程,因为会自动关闭,避免了麻烦,Java垃圾回收线程就是一个典型的守护线程,简单粗暴的可以理解为所有为线程服务而不涉及资源的线程都能设置为守护线程。
setDaemon方法转守护线程
public class App {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while(i<5) {
System.out.println("work is begin.");
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
System.out.println("work is end.");
}
});
// thread.setDaemon(true);
thread.start();
System.out.println("main is end. ");
}
}
运行结果:
main is end.
work is begin.
work is begin.
work is begin.
work is begin.
work is begin.
work is end.
设置为true结果
main is end.
work is begin.
Thread.setDaemon的用法总结
- setDaemon需要在start方法调用之前使用
- 如果jvm中都是后台进程,当前jvm将exit。(随之而来的,所有的一切烟消云散,包括后台线程啦)
- 主线程结束后,用户线程将会继续运行,如果没有用户线程,都是后台进程的话,那么jvm结束 。
- setDaemon方法把java的线程设置为守护线程,此方法的调用必须在线程启动之前执行。只有在当前jvm中所有的线程都为守护线程时,jvm才会退出。
Runnable和Thread的区别
初步来说接口是为了把线程的控制分离出业务代码,同时接口的好处如下:
1、避免继承的局限,一个类可以继承多个接口。
2、适合于资源的共享。
演示多窗口买票问题
以卖票为例,总共只有10张动车票了,全国3个窗口在卖。
public class App implements Runnable {
// 从这里可以看到,在接口里面这个值是共享处理的,所以在接口中一般直接来处理业务逻辑
private static int TICKET_COUNT = 10;
private static final Object LOCK = new Object();
@Override
public void run() {
for (int i = 0; i < TICKET_COUNT; i++) {
synchronized(LOCK){
if (TICKET_COUNT > 0) {
System.out.println(Thread.currentThread().getName() + "卖票" + --TICKET_COUNT);
}
}
}
}
public static void main(String[] args) throws Exception {
App app = new App();
Thread thread1 = new Thread(app, "一号窗口");
Thread thread2 = new Thread(app, "二号窗口");
Thread thread3 = new Thread(app, "三号窗口");
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果如下:
一号窗口卖第 9 张票
一号窗口卖第 8 张票
一号窗口卖第 7 张票
一号窗口卖第 6 张票
一号窗口卖第 5 张票
三号窗口卖第 4 张票
三号窗口卖第 3 张票
三号窗口卖第 2 张票
二号窗口卖第 1 张票
优先级
原文地址:https://www.cnblogs.com/duanxz/p/5226109.html
Java线程可以有优先级的设定,高优先级的线程比低优先级的线程有更高的几率得到执行(不完全正确,请参考下面的“线程优先级的问题“)。
- 记住当线程的优先级没有指定时,所有线程都携带普通优先级。
- 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
- 记住优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
- 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
- 由调度程序决定哪一个线程被执行。
- t.setPriority()用来设定线程的优先级。
- 记住在线程开始方法被调用之前,线程的优先级应该被设定。
- 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级。
Thread.setPriority()可能根本不做任何事情,**这跟你的操作系统和虚拟机版本有关,所以并一定就有用,更多是可能会先执行优先级高的线程。 **
join用法
当前线程等待子线程结束后才执行,但是子线程和子线程这样的线程还是并行处理。主要针对的是当前线程。如下面针对的就是main线程。
public static void main(String[] args) throws Exception {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1_000);
System.out.println(Thread.currentThread().getName() + "->>我是子线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1_000);
System.out.println(Thread.currentThread().getName() + "->>我是子线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t2.start();
t.join();
t2.join();
System.out.println("我是主线程");
}
运行结果如下:
Thread-0->>我是子线程
Thread-1->>我是子线程
Thread-0->>我是子线程
Thread-1->>我是子线程
Thread-0->>我是子线程
Thread-1->>我是子线程
Thread-1->>我是子线程
Thread-0->>我是子线程
Thread-0->>我是子线程
Thread-1->>我是子线程
我是主线程
优雅的方式终止线程
原文地址:https://www.cnblogs.com/onlywujun/p/3565082.html
如何停止线程
- 任务中一般都会有循环结构,只要用一个标记控制住循环,就可以结束任务。
- 如果线程处于了
冻结状态
,无法读取标记,此时可以使用interrupt()
方法将线程从冻结状态强制恢复到运行状态中
来,让线程具备CPU的执行资格。
interrupt方法
本线程中断自身是被允许的,且"中断标记"设置为true
interrupt方法的目的是给线程发出中断信号,但是不保证线程真的会中断。
- 中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。
- Thread.interrupt()方法不会中断一个正在运行的线程。
- 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该Thread类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。
- synchronized在获锁的过程中是不能被中断的,
isInterrupted()方法
判断调用线程是否处于中断状态 .
说明:isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。
interrupted()方法
判断的是当前线程是否处于中断状态。是类的静态方法,同时会清除线程的中断状态。
volatile
volatile 的特性
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
- 禁止进行指令重排序。(实现有序性)
- volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
线程之间数据共享原理
原文链接:https://blog.csdn.net/xdzhouxin/article/details/81236356
-
当CPU写入数据的时候, 如果发现改变量被共享了,也就是说,在其他的CPU中也存在改变量的副本,会发出一个信号, 通知其他CPU该变量的缓存是无效。
-
当其他CPU访问改变量的时候,重新到主内存中进行获取。
可见性与Java的内存模型有关,模型采用缓存与主存的方式对变量进行操作,也就是说,每个线程都有自己的缓存空间,对变量的操作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量进行修改,那么这个线程拿到的值是主存中未被修改的值,这就是可见性的问题。
volatile很好的保证了变量的可见性,变量经过volatile修饰后,对此变量进行写操作时,汇编指令中会有一个LOCK前缀指令,这个不需要过多了解,但是加了这个指令后,会引发两件事情:
将当前处理器缓存行的数据写回到系统内存 这个写回内存的操作会使得在其他处理器缓存了该内存地址无效 什么意思呢?意思就是说当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,这就保证了可见性。
volatile为什么无法保证原子性
原文链接:https://blog.csdn.net/xdzhouxin/article/details/81236356
问题来了,既然它可以保证修改的值立即能更新到主存,其他线程也会捕捉到被修改后的值,那么为什么不能保证原子性呢? 首先需要了解的是,Java中只有对基本类型变量的赋值和读取是原子操作,如i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。
所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了。
举个栗子
一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作也就是i++,i++的过程可以分为三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。 线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也得到了i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。 问题来了,线程A已经读取到了i的值为100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存,所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。
时序性代码体现
volatile关键字禁止指令重排序有两层意思:
-
当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
-
在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
代码如下:
package app;
public class SingletionObject4 {
private static SingletionObject4 instance; // 1号
private static volatile SingletionObject4 instance; // 2号
private Test obj1;
private Test obj2;
private SingletionObject4() {
System.out.println("singletion......");
obj1 = new Test("A");
obj2 = new Test("B");
}
public static SingletionObject4 getInstance() {
if (instance == null) {
synchronized(SingletionObject4.class){
if (instance == null) {
instance = new SingletionObject4();
}
}
}
return SingletionObject4.instance;
}
public static void main(String[] args) {
SingletionObject4.getInstance();
}
}
如上采取第二种模式编写, 他会先执行里面的obj1和obj2初始化对象后, 然后在初始化SignletionObject4这个对象, 如采取1号的方式, 他是有可能,根据jvm虚拟机的优化处理的时候, 写实例化SigletionObject4这个对象, 在去实例化obj1和obj2,这个时候, 如果你去调用obj1或者obj2的方法的时候, 有可能就是NullPointExection了。
volatile的使用场景
原文链接:https://blog.csdn.net/vking_wang/article/details/9982709
模式 #1:状态标志
也许实现 volatile 变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。多个线程之间需要一个公共的变量来控制。
模式 #2:独立观察
安全使用 volatile 的另一种简单模式是:定期 “发布” 观察结果供程序内部使用。【例如】假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。
模式 #3:开销较低的“读-写锁”策略
如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。
使用锁进行所有变化的操作,使用 volatile 进行只读操作。其中,锁一次只允许一个线程访问值,volatile 允许多个线程执行读操作。
synchronized
在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
相当于把并行的问题改成了单行处理,让共享数据串行处理。
synchronized的三种应用方式
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
- 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
- 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
- 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
代码验证
- 一个线程获取了该对象的锁之后,其他线程来访问其他synchronized实例方法现象
public class SynchronizedTest {
public synchronized void method1() {
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public synchronized void method2() {
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(final String[] args) throws InterruptedException {
SynchronizedTest test = new SynchronizedTest();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
});
t.start();
t2.start();
}
}
运行结果如下:
Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end
分析:可以看出其他线程来访问synchronized修饰的其他方法时需要等待线程1先把锁释放,因为这里的锁对应的事这个对象。
同步方法和同步代码块的使用和区别
同步方法
public synchronized void execute()
{
}
同步代码块
Object object = new Object();
synchronized(object){
}
区别
如果使用同步代码块,只将可能会出现多线程问题的代码放在同步代码块里面,那么当一个线程访问同步代码块里面的代码时,其他线程也可以访问方法里其他的代码,这样代码执行效率会大大增加。所以,通常情况下,能使用同步代码块就尽量使用同步代码块。
this和class的理解
this实例代码
package app;
public class synchronizedTest {
public void method1() {
synchronized (this) {
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
}
public void method2() {
synchronized (this) {
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
}
public static void main(final String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronizedTest test = new synchronizedTest();
test.method1();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedTest test = new synchronizedTest();
test.method2();
}
});
t.start();
t2.start();
}
}
运行结果如下:
Method 2 start
Method 2 execute
Method 1 start
Method 1 execute
Method 2 end
Method 1 end
class实例代码
package app;
public class synchronizedTest {
public void method1() {
synchronized (synchronizedTest.class) {
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
}
public void method2() {
synchronized (synchronizedTest.class) {
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
}
public static void main(final String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronizedTest test = new synchronizedTest();
test.method1();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronizedTest test = new synchronizedTest();
test.method2();
}
});
t.start();
t2.start();
}
}
运行结果:
Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end
总结
1、对于静态方法,由于此时对象还未生成,所以只能采用类锁;
2、只要采用类锁,就会拦截所有线程,只能让一个线程访问。
3、对于对象锁(this),如果是同一个实例,就会按顺序访问,但是如果是不同实例,就可以同时访问。
4、如果对象锁跟访问的对象没有关系,那么就会都同时访问。
synchronized有什么缺陷
效率低
锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时。使用synchronized,当多个线程尝试获取锁时,未获取到锁的线程会不断的尝试获取锁,而不会发生中断,这样会造成性能消耗。
不够灵活
加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活
无法知道是否成功获得锁
相对而言,Lock可以拿到状态,如果成功获取锁,….,如果获取失败,…..
锁
锁的分类
显示锁
当调用synchronized修饰的代码时,并不需要显示的加锁和解锁的过程,所以称之为隐式锁。
隐式锁
Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有的加锁和解锁操作方法都是显示的,因而称为显示锁。
可重入锁
一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁。
不可重入锁
即当前线程获取这把锁后,要想再拿到这把锁,必须释放当前持有的锁,这时我们称这个锁是不可重入的。
公平锁
如果在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的
非公平锁
上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。非公平锁效率要高
自定义显示锁
原来主要通过wait(mills) 和 notifyAll(), 加上一个变量来进行控制处理的。
转载地址:https://www.cnblogs.com/Draymonder/p/10387358.html
Lock接口类
public interface Lock {
static class TimeOutException extends Exception {
TimeOutException(String message) {
super(message);
}
}
void lock() throws InterruptedException;
void lock(long mills) throws InterruptedException, TimeOutException;
void unlock();
Collection<Thread> getBlockedThread();
int getBlockedSize();
}
BooleanLock业务类
package app.lockdemo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
public class BooleanLock implements Lock {
public BooleanLock(boolean initValue) {
this.initValue = initValue;
}
// false means is free
// true means having been used
// 通过这个变量来控制时间的长度。
private volatile boolean initValue;
private Thread currentThread;
private Collection<Thread> blockedThreads = new ArrayList<>();
@Override
public synchronized void lock() throws InterruptedException {
while (initValue) {
blockedThreads.add(Thread.currentThread());
this.wait();
}
blockedThreads.remove(Thread.currentThread());
this.initValue = true;
this.currentThread = Thread.currentThread();
}
@Override
public synchronized void lock(long mills) throws InterruptedException, TimeOutException {
System.out.println("current thread :" + Thread.currentThread().getName() + " 共享变量 " + this.initValue);
if (mills <= 0)
lock();
long hasRemaining = mills;
long endTime = System.currentTimeMillis() + mills;
while (initValue) {
if (hasRemaining <= 0)
throw new TimeOutException("time out, and the Thread[" + Thread.currentThread().getName()+ "] do not get the lock--->" + this.initValue);
blockedThreads.add(Thread.currentThread());
this.wait(mills);
System.out.println("current thread ->"+Thread.currentThread().getName()+" initValue ->" + this.initValue);
hasRemaining = endTime - System.currentTimeMillis();
}
blockedThreads.remove(Thread.currentThread());
this.initValue = true;
this.currentThread = Thread.currentThread();
}
@Override
public synchronized void unlock() {
if (currentThread != null && Thread.currentThread() == currentThread) {
this.initValue = false;
Optional.of(Thread.currentThread().getName() + " release the lock...").ifPresent(System.out::println);
this.notifyAll();
}
}
@Override
public Collection<Thread> getBlockedThread() {
return Collections.unmodifiableCollection(blockedThreads);
}
@Override
public int getBlockedSize() {
return blockedThreads.size();
}
}
LockTest测试类
public class LockTest {
public static void main(String[] args) throws InterruptedException {
final BooleanLock booleanLock = new BooleanLock(false);
Stream.of("T1", "T2", "T3", "T4").forEach(name -> new Thread(() -> {
try {
booleanLock.lock(10_000L);
Optional.of(Thread.currentThread().getName() + " have the lock Monitor").ifPresent(System.out::println);
work();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Lock.TimeOutException e) {
System.out.println(e.getMessage());
// Optional.of(Thread.currentThread().getName() + " time out")
// .ifPresent(System.out::println);
} finally {
booleanLock.unlock();
}
}, name).start());
}
private static void work() throws InterruptedException {
Optional.of(Thread.currentThread().getName() + " is Working...").ifPresent(System.out::println);
Thread.sleep(1_000);
}
}
运行结果
current thread :T1 共享变量 false
current thread :T4 共享变量 true
current thread :T3 共享变量 true
current thread :T2 共享变量 true
T1 have the lock Monitor
T1 is Working...
T1 release the lock...
current thread ->T2 initValue ->false
T2 have the lock Monitor
current thread ->T3 initValue ->true
T2 is Working...
current thread ->T4 initValue ->true
T2 release the lock...
current thread ->T4 initValue ->false
T4 have the lock Monitor
T4 is Working...
current thread ->T3 initValue ->true
T4 release the lock...
current thread ->T3 initValue ->false
T3 have the lock Monitor
T3 is Working...
T3 release the lock...
sleep和wait的区别
-
sleep方法是Thread类的静态方法,wait()是Object超类的成员方法。
-
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
-
sleep方法需要抛异常,wait方法不需要。(Thread类中sleep方法就已经进行了抛异常处理)
-
sleep方法可以在任何地方使用,wait方法只能在同步方法和同步代码块中使用。
注意: wait不是加入锁,而是放入锁的队列中。
- 所有的对象都会有一个wait set, 用来存放调用了改对象的wait方法之后进入block状态线程。
- 线程被notify之后, 不一定立即得到执行。
- 线程从wait set 中被唤醒顺序不一定是FIFO。
锁池和等待池
转载地址:https://www.cnblogs.com/tiancai/p/9371357.html
在java中,每个对象都有两个池,锁(monitor)池和等待池,wait() ,notifyAll(),notify() 三个方法都是Object类中的方法.
锁池
假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池
假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.
wait等待索定池代码演示
import java.util.stream.Stream;
public class WaitTest {
public synchronized void lock() throws InterruptedException {
System.out.println("current thread is " + Thread.currentThread().getName());
this.wait();
System.out.println("current thread is " + Thread.currentThread().getName() + " unlock");
}
public static void main(String[] args) {
WaitTest wait = new WaitTest();
Stream.of("T1", "T2", "T3", "T4").forEach(name -> new Thread(new Runnable() {
@Override
public void run() {
try {
wait.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, name).start());
}
}
运行结果:
current thread is T1
current thread is T3
current thread is T2
current thread is T4
如上可以看到, 线程启动后, 全部进入了synchronized方法中,全部在wait方法的前面都执行了, 后面阻塞了, 当时前面的几条线程都直接运行完成了。
生产者和消费者
实现效果, 多个生产者和多个消费者进行消费固定张票,然后终止。
生产者
public class Producer implements Runnable {
private TicketBox ticketBox;
public Producer(TicketBox ticketBox) {
this.ticketBox = ticketBox;
}
@Override
public void run() {
boolean flag = true;
while (flag) {
try {
// 这个时间的控制,可以让其先生产一批,然后在消费,
// 也可以一边生产一边消费,这个时间主要和消费者那边的时间比较就可以了
Thread.sleep(1_00);
flag = ticketBox.produceTicket();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者
public class Consumer implements Runnable {
private TicketBox ticketBox;
public Consumer(TicketBox ticketBox) {
this.ticketBox = ticketBox;
}
@Override
public void run() {
boolean flag = true;
while (flag) {
try {
Thread.sleep(1_000);
flag = ticketBox.consumeTicket();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程通信类
/**
* 生产和消费的业务代码
*/
public class TicketBox {
private volatile int currentTicket;
private volatile List<Integer> produceTicketList = new ArrayList<>();
private volatile List<Integer> consumerTicketList = new ArrayList<>();
private volatile boolean flag = true;
private static final int MAX_TICKET_COUNT = 15;
/**
* 生产票
*/
public synchronized boolean produceTicket() {
while (currentTicket != 0 && currentTicket % 5 == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (produceTicketList.size() < MAX_TICKET_COUNT) {
++currentTicket;
produceTicketList.add(currentTicket);
System.out.println(Thread.currentThread().getName() + " 生产第" + produceTicketList.size() + "张票");
} else {
try {
wait();
// 这个flag是为了控制多个线程到这里来,打印多次下面语句,通过volatile boolean flag来控制,打印一句出来正常就OK了。
if (flag) {
System.out.println("票已经卖完了.....");
flag = false;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
return true;
}
/**
* 消费票
*/
public synchronized boolean consumeTicket() {
if (currentTicket == 0) {
notifyAll();
System.out.println("\n");
}
if (currentTicket > 0) {
currentTicket--;
consumerTicketList.add(currentTicket);
System.out.println(Thread.currentThread().getName() + " 消费第" + consumerTicketList.size() + "张票 ");
} else {
// 这里需要加入一个判断不然,会导致currentTicket多线程情况的时候为0,也到了这里来了。
if(consumerTicketList.size() == MAX_TICKET_COUNT){
notifyAll();
return false;
}
}
return true;
}
}
测试类
public static void main(String[] args) {
TicketBox ticketBox = new TicketBox();
Producer product = new Producer(ticketBox);
Consumer consumer = new Consumer(ticketBox);
Thread producerThread = new Thread(product);
Thread producerThread2 = new Thread(product);
Thread producerThread3 = new Thread(product);
producerThread.start();
producerThread3.start();
producerThread2.start();
Thread consumerThread = new Thread(consumer);
Thread consumerThread2 = new Thread(consumer);
Thread consumerThread3 = new Thread(consumer);
consumerThread3.start();
consumerThread2.start();
consumerThread.start();
}
运行结果
Thread-0 生产第1张票
Thread-1 生产第2张票
Thread-2 生产第3张票
Thread-0 生产第4张票
Thread-2 生产第5张票
Thread-5 消费第1张票
Thread-3 消费第2张票
Thread-4 消费第3张票
Thread-5 消费第4张票
Thread-4 消费第5张票
Thread-0 生产第6张票
Thread-2 生产第7张票
Thread-1 生产第8张票
Thread-0 生产第9张票
Thread-1 生产第10张票
Thread-4 消费第6张票
Thread-3 消费第7张票
Thread-5 消费第8张票
Thread-3 消费第9张票
Thread-4 消费第10张票
Thread-1 生产第11张票
Thread-0 生产第12张票
Thread-2 生产第13张票
Thread-1 生产第14张票
Thread-2 生产第15张票
Thread-3 消费第11张票
Thread-5 消费第12张票
Thread-4 消费第13张票
Thread-4 消费第14张票
Thread-3 消费第15张票
票已经卖完了.....
总结
主要是通过wait和notifyAll方法的使用, 以及用volatile关键字的控制。
线程中如何抓住异常
加入了一个回调类进行处理。
public class ThreadException implements Runnable {
@Override
public void run() {
System.out.println(" currentThread is "+Thread.currentThread().getName());
int count = 10 / 0;
}
private static final class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Thread id=" + t.getId());
System.out.println("Thread name=" + t.getName());
e.printStackTrace(System.out);
}
}
public static void main(String[] args) {
ThreadException t = new ThreadException();
Thread thread = new Thread(t);
ThreadExceptionHandler threadExceptionHandler = new ThreadExceptionHandler();
// 相当于加入了一个线程回调处理类。
thread.setUncaughtExceptionHandler(threadExceptionHandler);
thread.start();
}
}
ThreadGroup
线程组(ThreadGroup
)简单来说就是一个线程集合。线程组的出现是为了更方便地管理线程。
public class ThreadGroupDemo implements Runnable {
@Override
public synchronized void run() {
System.out.println("ThreadName = " + Thread.currentThread().getName() + "准备开始死循环了 \n");
while (!Thread.currentThread().isInterrupted()) {
// System.out.println("循环处理业务中.....");
}
try {
wait();
} catch (InterruptedException e) {
System.out.println("\n wait 方法被打断了 \n");
}
try {
Thread.sleep(2_000);
} catch (InterruptedException e) {
System.out.println("sleep 方法被打断了 \n");
}
System.out.println("ThreadName = " + Thread.currentThread().getName() + "结束了");
}
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("maizi-group");
ThreadGroupDemo runnable = new ThreadGroupDemo();
Thread thread1 = new Thread(threadGroup, runnable, "麦子1-thread");
Thread thread2 = new Thread(threadGroup, runnable, "麦子2-thread");
thread1.start();
thread2.start();
try {
Thread.sleep(1_000);
int totalThread = threadGroup.activeCount();
System.out.println("线程组的存活个数:" + totalThread + "\n");
Thread[] lstThreads = new Thread[totalThread];
threadGroup.enumerate(lstThreads);
for (Thread thread : lstThreads) {
System.out.println("活动线程数量一共:" + totalThread + " 线程id:" + thread.getId() + " 线程名称:" + thread.getName()+ " 线程状态:" + thread.getState());
}
threadGroup.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下:
ThreadName = 麦子1-thread准备开始死循环了
线程组的存活个数:2
活动线程数量一共:2 线程id:9 线程名称:麦子1-thread 线程状态:RUNNABLE
活动线程数量一共:2 线程id:10 线程名称:麦子2-thread 线程状态:BLOCKED
wait 方法被打断了
ThreadName = 麦子1-thread结束了
ThreadName = 麦子2-thread准备开始死循环了
wait 方法被打断了
ThreadName = 麦子2-thread结束了
注意:这个 interrupt方法只能打断wait的方法, sleep的是无法打断的。 而且这个打断后, 线程不会马上就无法工作了,需要等一段时间线程才会死亡。
单例模式
转载地址:https://www.cnblogs.com/saoyou/p/11087462.html
常用模式
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo() {
}
public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo.class) {
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
}
枚举方式
public enum EnumSingleton {
INSTANCE;
private static EnumSingleton getInstance() {
return INSTANCE;
}
}
注意
如果考虑了放射,则上面的单例就无法做到单例类只能有一个实例这种说法了。事实上,通过Java反射机制是能够实例化构造方法为private的类的。这也就是我们现在需要引入的枚举单例模式。
public static void main(String[] args) {
EnumSingleton instance = EnumSingleton.getInstance();
System.out.println(EnumSingleton.INSTANCE == instance);
Constructor<EnumSingleton> constructor=EnumSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
EnumSingleton newinstance=constructor.newInstance();
System.out.println(instance+"\n"+newinstance);
System.out.println("通过反射攻击单例模式,实例化两个实例是否相同:"+(instance==newinstance));
}
上面的代码, 结果会报Exception in thread “main” java.lang.NoSuchMethodException。出现这个异常的原因是因为EnumSingleton.class.getDeclaredConstructors()获取所有构造器,会发现并没有我们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器,而且在反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以枚举是不怕发射攻击的。
FutureTask获取线程返回值
转载地址: https://blog.csdn.net/tongdanping/article/details/79630637
好文:https://blog.csdn.net/qq_39654841/article/details/90631795
概述
当然多线程中继承ThreadPoolExecutor和实现Runnable也可以实现异步工作机制,可是他们没有返回值。这时可以使用FutureTask包装Runnable或者Callable对象,再使用FutureTask来执行任务。
Future接口和其唯一的实现类FutureTask类一般用于表示异步计算的结果。Future接口下提供方法来检查计算是否完成,等待其完成,并检索计算结果。 结果只能在计算完成后使用方法get进行检索,如有必要,阻塞,直到准备就绪。 取消由cancel方法执行,isCancelled方法用于检测计算是否被取消,isDone方法用于检测计算是否完成。 提供其他方法来确定任务是否正常完成或被取消。
201
FutureTask使用
根据FutureTask被执行的进度,FutureTask对象可以处于一下3种状态:
未启动:创建了一个FutureTask对象但没有执行futureTask.run();
已启动:futureTask.run()方法被执行的过程中;
已完成:futureTask.run()正常执行结束,或者futureTask被取消(futureTask.cancel()),或者执行futureTask.run()时抛出异常而异常结束;
FutureTask源码
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
FutureTask启动
FutureTask实现了Future接口和Runnable接口,因此FutureTask对象的执行有两种方式:
-
交给线程池的Execute或submit方法执行;
-
由调用线程直接执行:在调用线程中执行futureTask.run()方法;
FutureTask执行完后结果的获取
futureTask.get( )
- 在已启动的状态调用futureTask.get( )或导致调用线程阻塞,知道FutureTask执行完毕,然后得到返回的FutureTask对象,调用futureTask.get( )获得任务返回值;
- 在已完成状态调用futureTask.get( ),将导致调用线程立即返回(正常完成,得到FutureTask对象)或者抛出异常(被取消或者因异常而结束);
FutureTask被取消
futureTask.cancel( )
- 在未启动状态调用futureTask.cancel( )会导致该任务永远不会再执行;
- 在已启动状态:
- 调用futureTask.cancel(true )会以中断的方式尝试停止任务,如果该任务不响应中断则无法停止;
- 调用futureTask.cancel(false)将不会对正在执行的线程产生影响,也就是已启动的线程会让他执行完毕;
- 在已完成状态调用futureTask.cancel( )则会返回false;
FutureTask状态
原文链接:https://blog.csdn.net/qq_39654841/article/details/90631795
- 当FutureTask处于未启动或者已启动的状态时,调用FutureTask对象的get方法会将导致调用线程阻塞。当FutureTask处于已完成的状态时,调用FutureTask的get方法会立即放回调用结果或者抛出异常。
- 当FutureTask处于未启动状态时,调用FutureTask对象的cancel方法将导致线程永远不会被执行;当FutureTask处于已启动状态时,调用FutureTask对象cancel(true)方法将以中断执行此任务的线程的方式来试图停止此任务;当FutureTask处于已启动状态时,调用FutureTask对象cancel(false)方法将不会对正在进行的任务产生任何影响;当FutureTask处于已完成状态时,调用FutureTask对象cancel方法将返回false;
FutureTask的get和cancel的执行示意图
代码示例
public class FutureTaskTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<String> task = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("thread name is : "+Thread.currentThread().getName());
return "完成任务";
}
});
Thread t1 = new Thread(task);
t1.start();
String result = task.get();
System.out.println("result--->"+result);
}
}
运行结果
thread name is : Thread-0
result--->完成任务
FutureTask使用场景
- 当一个线程需要等待另一个线程把某个任务执行完以后它才能继续执行时;
- 有若干线程执行若干任务,每个任务最多只能被执行一次;
- 当多个线程师徒执行同一个任务,但只能允许一个线程执行此任务,其它线程需要等这个任务被执行完毕以后才能继续执行时;
线程之间通信
转载地址:https://www.cnblogs.com/lgyxrk/p/10404839.html
同步
这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。
public class MyObject {
synchronized public void methodA() {
//do something....
}
synchronized public void methodB() {
//do some other thing
}
}
public class ThreadA extends Thread {
private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodA();
}
}
public class ThreadB extends Thread {
private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodB();
}
}
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
//线程A与线程B 持有的是同一个对象:object
ThreadA a = new ThreadA(object);
ThreadB b = new ThreadB(object);
a.start();
b.start();
}
}
由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了 通信。
这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。
wait/notify机制
import java.util.ArrayList;
import java.util.List;
public class MyList {
private static List<String> list = new ArrayList<String>();
public static void add() {
list.add("anyString");
}
public static int size() {
return list.size();
}
}
public 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();
}
}
}
public 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 Run {
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();
}
}
}
线程A要等待某个条件满足时(list.size()==5),才执行操作。线程B则向list中添加元素,改变list 的size。
A,B之间如何通信的呢?也就是说,线程A如何知道 list.size() 已经为5了呢?
这里用到了Object类的 wait() 和 notify() 方法。
当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。
当条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。
这种方式的一个好处就是CPU的利用率提高了。
但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素并调用了notify()发送了通知,而此时线程A还执行;当线程A执行并调用wait()时,那它永远就不可能被唤醒了。因为,线程B已经发了通知了,以后不再发通知了。这说明:通知过早,会打乱程序的执行逻辑。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付