java并发编程系列一、多线程
一、什么是线程
一个应用就是一个进程、一个进程由多个线程组成。一个生产车间比作是一个进程、工人比作是线程。当任务比较多的时候,增加工人可以提高效率,同时成本就是支付费用(机器资源,内存)也会增加。
package com.study.demo; import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean; public class ThreadTest {
public static void main(String[] args) {
//java虚拟机的线程管理接口
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); //获取线程信息的方法
ThreadInfo[] threadInfos =
threadMXBean.dumpAllThreads(false,false);
for(ThreadInfo threadInfo:threadInfos){
System.out.println(threadInfo.getThreadId()+":"+threadInfo.getThreadName());
}
}
}
输出:
5:Attach Listener //获取内存dump,线程dump
4:Signal Dispatcher //将信号分发给jvm的线程
3:Finalizer //调用对象的finalizer 方法 进行垃圾回收
2:Reference Handler //清除Reference
1:main //程序的主入口
为什么要用线程?
1、 充分利用CPU多处理核心;
2、 更快的响应时间
二、启动线程和退出线程
1、创建线程的方法
- extends Thread
- implements Runnable
启动线程:threadl类的start()
线程完成:
1、run()方法执行完成;
2、抛出一个未处理的异常导致线程的提前结束
package com.study.demo; public class CreateThread { private class TestThread extends Thread{ @Override
public void run(){
System.out.println(" Thread");
}
} private class TestRunnable implements Runnable{ @Override
public void run() {
System.out.println("Runnable");
}
} private void test(){
Thread t1 = new TestThread();
Thread t2 = new Thread(new TestRunnable());
t1.start();
t2.start();
} public static void main(String[] args) {
new CreateThread().test();
} }
2、取消和中断
- 不安全的取消
- 单独使用一个取消标志位
package com.lgstudy.interrupt; /**
* lgs
*
* 使用自定义的取消标志位中断线程(不安全)
*/
public class FlagCancel { private static class TestRunable implements Runnable{ private volatile boolean on = true;
private long i =0; @Override
public void run() {
while(on){
i++;
//阻塞方法,on不起作用
//wait,sleep,blockingqueue(put,take)
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("TestRunable is runing :"+i);
} public void cancel(){
on = false;
}
} }
Stop(),suspend(),resume()是过期的api,很大的副作用,容易导致死锁(suspend():将线程挂起不会释放锁)或者数据不一致(Stop():线程在未处理完数据时就停止)
3、如何安全的终止线程
使用线程的中断 :
interrupt() 中断线程,本质是将线程的中断标志位设为true,其他线程向需要中断的线程打个招呼。是否真正进行中断由线程自己决定。
isInterrupted() 线程检查自己的中断标志位
静态方法Thread.interrupted() 将中断标志位复位为false
由上面的中断机制可知Java里是没有抢占式任务,只有协作式任务。
为何要用中断,线程处于阻塞(如调用了java的sleep,wait等等方法时)的时候,是不会理会我们自己设置的取消标志位的,但是这些阻塞方法都会检查线程的中断标志位。
package com.lgstudy.interrupt; /**
* lgs
*
* 安全的中断线程
*/
public class SafeInterrupt implements Runnable { private volatile boolean on = true;
private long i =0; @Override
public void run() {
//阻塞方法wait,sleep,blockingqueue(put,take),on不起作用
//要加上线程的中断才能安全的终止线程
while(on&&!Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("TestRunable is runing :"+i);
} public void cancel(){
on = false;
//在java线程很忙的时候可能不会理会中断,所以定义一个标志位on更好
Thread.currentThread().interrupt();
}
}
4、处理不可中断的阻塞
IO通信 inputstream read/write等阻塞方法,不会理会中断,而关闭底层的套接字socket.close()会抛出socketException
NIO: selector.select()会阻塞,调用selector的wakeup和close方法会抛出ClosedSelectorException
死锁状态不响应中断的请求,这个必须重启程序,检查程序找到死锁的位置修改错误。
package com.lgstudy.interrupt; /**
* lgs
*
* 调用阻塞方法时,如何中断线程
*/
public class BlockInterrupt { private static volatile boolean on = true; private static class WhenBlock implements Runnable { @Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()) {
try {
//抛出中断异常的阻塞方法(wait,sleep,blockingqueue(put,take)),抛出异常后,中断标志位改成false
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();//重新设置一下
//do my work
}
//清理工作结束线程
}
} //外部线程调用方法阻塞
public void cancel() {
on = false;
Thread.currentThread().interrupt();
} }
}
如何让我们的代码既可以响应普通的中断,又可以关闭底层的套接字呢?
覆盖线程的interrupt方法,在处理套接字异常时,再用super.interrupt()自行中断线程。
package com.lgstudy.interrupt; import java.io.IOException;
import java.io.InputStream;
import java.net.Socket; /**
* lgs
*
* 如何覆盖线程的interrupt() 方法
*/
public class OverrideInterrupt extends Thread {
private final Socket socket;
private final InputStream in; public OverrideInterrupt(Socket socket, InputStream in) {
this.socket = socket;
this.in = in;
} private void t(){
} @Override
public void interrupt() {
try {
//关闭底层的套接字
socket.close();
} catch (IOException e) {
e.printStackTrace();
//.....
}finally {
//同时中断线程
super.interrupt();
} }
}
5、线程的状态
- 新创建 线程被创建,但是没有调用start方法
- 可运行(RUNNABLE) 运行状态,由cpu决定是不是正在运行
- 被阻塞(BLOCKING) 阻塞,线程被阻塞于锁
- 等待/计时等待(WAITING) 等待某些条件成熟
- 被终止 线程执行完毕
6、线程的优先级
成员变量priority控制优先级,范围1-10之间,数字越高优先级越高,缺省为5,创建线程时setPriotity()可以设置优先级,不要指望他发挥作用,因为线程优先级是由操作系统决定的,有的操作系统甚至会忽略jvm的线程优先级。
7、Daemon线程
守护型线程(如GC线程),程序里没有非Daemon线程时,java程序就会退出。一般用不上,也不建议我们平时开发时使用,因为Try/Finally里的代码不一定执行的。
package com.lgstudy; import com.lgstudy.threadstate.SleepUtils; /**
* lgs
*
* 守护线程
*/
public class Daemon {
public static void main(String[] args) {
Thread thread = new Thread(new DaemonRunner());
//将线程置为守护线程
thread.setDaemon(true);
thread.start();
} static class DaemonRunner implements Runnable {
@Override
public void run() {
try {
SleepUtils.second(100);
} finally {
System.out.println("DaemonThread finally run.");
}
}
}
}
三、常用方法深入理解
run()和start()
run就是一个普通的方法,跟其他类的实例方法没有任何区别,他之所以能在线程里面运行时因为调用了start()方法。
Sleep
不会释放锁,当前线程变成了休眠状态,所以我们在用sleep时,要把sleep放在同步代码块的外面。
yield()
不会释放锁,当前线程出让cpu占有权,当前线程变成了可运行状态,下一时刻仍然可能被cpu选中。
wait()和 notify()/notiyfAll()
调用以前,当前线程必须要持有锁,调用了wait() notify()/notiyfAll()会释放锁。
等待通知机制:
线程 A调用了对象O的wait方法进入等待状态,线程 B调用了对象O的notify方法进行唤醒,唤醒的是在对象O上wait的线程(比如线程A)
notify() 唤醒一个线程,唤醒哪一个完全看cpu的心情(谨慎使用)
notiyfAll() 所有在对象O上wait的线程全部唤醒(应该用notiyfAll())
join方法
线程A,执行了thread.join(),线程A等待thread线程终止了以后,A在join后面的语句才会继续执行
package com.lgstudy; /**
* join的使用
*/
public class JoinTest { static class CutInLine implements Runnable{ private Thread thread; public CutInLine(Thread thread) {
this.thread = thread;
} @Override
public void run() {
try {
//在被插队的线程里,调用一下插队线程的join方法
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" will work");
}
} public static void main(String[] args) {
Thread previous = Thread.currentThread();
for(int i=0;i<10;i++){
Thread thread =
new Thread(new CutInLine(previous),String.valueOf(i));
System.out.println(previous.getId()+" cut in the thread:"+thread.getName());
thread.start();
previous = thread;
} } }
四、线程间协作和通信
每个线程有自己栈空间,孤立运行,对我们没有价值。如果多个线程能够相互配合完成工作,这将会带来巨大的价值。
1、volatile和synchronized
多个线程同时访问一个共享的变量的时候,每个线程的工作内存有这个变量的一个拷贝,变量本身还是保存在共享内存中。
volatile修饰字段,对这个变量的访问必须要从共享内存刷新一次。最新的修改写回共享内存。可以保证字段的可见性。绝对不是线程安全的,没有操作的原子性。
适用场景:
1、一个线程写,多个线程读;
2、volatile变量的变化很固定即变化以后都是一个固定的值。
package com.lgstudy.volatiletest; /**
*
* 测试Volatile型变量的操作原子性
*/
public class VolatileThread implements Runnable { private volatile int a= 0; @Override
public void run() {
synchronized (this){
a=a+1;
System.out.println(Thread.currentThread().getName()+"----"+a);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
a=a+1;
System.out.println(Thread.currentThread().getName()+"----"+a); }
}
}
package com.lgstudy.volatiletest; public class VolatileTest {
public static void main(String[] args) {
VolatileThread volatileThread = new VolatileThread(); Thread t1 = new Thread(volatileThread);
Thread t2 = new Thread(volatileThread);
Thread t3 = new Thread(volatileThread);
Thread t4 = new Thread(volatileThread);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出:
Thread-0----1
Thread-0----2
Thread-3----3
Thread-3----4
Thread-2----5
Thread-2----6
Thread-1----7
Thread-1----8
关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。
Synchronized的类锁和对象锁,本质上是两把锁,类锁实际锁的是每一个类的class对象。对象锁锁的是当前对象实例。
package com.lgstudy.syn; import com.lgstudy.threadstate.SleepUtils; /**
* 类锁和实例锁
*/
public class InstanceAndClass { //测试类锁
private static class TestClassSyn extends Thread{
@Override
public void run() {
System.out.println("TestClass is going...");
synClass();
}
} //测试对象锁
private static class TestInstanceSyn extends Thread{
private InstanceAndClass instanceAndClass; public TestInstanceSyn(InstanceAndClass instanceAndClass) {
this.instanceAndClass = instanceAndClass;
} @Override
public void run() {
System.out.println("TestInstance is going..."+instanceAndClass);
instanceAndClass.synInstance();
} } //测试对象锁
private static class TestInstance2Syn implements Runnable{
private InstanceAndClass instanceAndClass; public TestInstance2Syn(InstanceAndClass instanceAndClass) {
this.instanceAndClass = instanceAndClass;
}
@Override
public void run() {
System.out.println("TestInstance2 is going..."+instanceAndClass);
instanceAndClass.synInstance2();
}
} //锁对象的方法
private synchronized void synInstance(){
SleepUtils.second(3);
System.out.println("synInstance is going...");
SleepUtils.second(3);
System.out.println("synInstance ended");
} //锁对象的方法
private synchronized void synInstance2(){
SleepUtils.second(3);
System.out.println("synInstance2 going...");
SleepUtils.second(3);
System.out.println("synInstance2 ended");
} //锁类的方法
private static synchronized void synClass(){
SleepUtils.second(1);
System.out.println("synClass going...");
SleepUtils.second(1);
} public static void main(String[] args) {
InstanceAndClass instanceAndClass = new InstanceAndClass();
Thread t1 = new TestClassSyn();
Thread t2 = new Thread(new TestInstanceSyn(instanceAndClass));
Thread t3 = new Thread(new TestInstance2Syn(instanceAndClass));
t2.start();
t3.start();
SleepUtils.second(1);
t1.start();
} }
2、等待和通知机制
等待方原则:
1、获取对象锁
2、如果条件不满足,调用对象的wait方法,被通知后依然要检查条件是否满足
3、条件满足以后,才能执行相关的业务逻辑
Synchronized(对象){
While(条件不满足){
对象.wait()
}
业务逻辑处理
}
通知方原则:
1、 获得对象的锁;
2、 改变条件;
3、 通知所有等待在对象的线程
Synchronized(对象){
业务逻辑处理,改变条件
对象.notify/notifyAll
}
package com.lgstudy.bq; import java.util.LinkedList;
import java.util.List; /**
*
* 有界阻塞队列/有界缓存队列
*/
public class BlockingQueueWN<T> { //当前队列
private List queue = new LinkedList<>();
//队列支持的最大容量
private final int limit; //外部修改队列的容量
public BlockingQueueWN(int limit) {
this.limit = limit;
} //入队
public synchronized void enqueue(T item) throws InterruptedException {
//如果当前队列的容量已经满了的话就要等待
while(this.queue.size()==this.limit){
wait();
}
//如果当前队列的容量为0的话,可以肯定有出队的线程正在等待,需要他可以准备出队了
if (this.queue.size()==0){
notifyAll();
}
//开始入队
this.queue.add(item);
} //出队
public synchronized T dequeue() throws InterruptedException {
//如果当前队列的容量为0的话就等待暂不出队
while(this.queue.size()==0){
wait();
}
//如果当前队列的容量已经满了的话,可以肯定有入队线程正在等待,需要唤醒他可以准备入队了
if (this.queue.size()==this.limit){
notifyAll();
}
//开始出队
return (T)this.queue.remove(0);
}
}
输出;
Pop will pop.....
i=5 will push
i=5 alread pop
Pop will pop.....
i=4 will push
i=4 alread pop
Pop will pop.....
i=3 will push
i=3 alread pop
Pop will pop.....
i=2 will push
i=2 alread pop
Pop will pop.....
i=1 will push
i=1 alread pop
Pop will pop.....
管道输入输出流 使用较少
管道输入输出流用于线程中间的数据传递,传输媒介是内存
PpedOutputStream/PpedInputStream 面向的字节
PipedReader/PipedWriter 面向的是字符
只适合线程间一对一的通信,适用范围较狭窄。
ThreadLocal
本质是个map,map的键就是每个线程对象,值就是每个线程所拥有的值
常用方法:
initialValue()
get()
set()
remove():将当前线程局部变量的值删除,这个方法是JDK 5.0新增的方法。当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
ThreadLocal拥有的这个变量,在线程之间很独立的,相互之间没有联系。内存占用相对来说比较大。
性能问题
串行化、无锁化、异步化编程是趋势之一,比如node.js,Vert.x。
黄金原则:编码时候不要考虑性能优化的事情,先正确实现业务,发现性能不行,这个时候再来考虑性能优化。
等待超时模式
调用场景:调用一个方法时等待一段时间(一般来说是给定一个时间段),如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果。
假设等待时间段是T,那么可以推断出在当前时间now+T之后就会超时
等待持续时间:REMAINING=T。
·超时时间:FUTURE=now+T。
public synchronized Object get(long mills) throws InterruptedException {
long future = System.currentTimeMillis() + mills;
long remaining = mills;
// 当超时大于0并且result返回值不满足要求
while ((result == null) && remaining > 0) {
wait(remaining);
remaining = future - System.currentTimeMillis();
}
return result;
}
java并发编程系列一、多线程的更多相关文章
- 干货:Java并发编程系列之volatile(二)
接上一篇<Java并发编程系列之synchronized(一)>,这是第二篇,说的是关于并发编程的volatile元素. Java语言规范第三版中对volatile的定义如下:Java编程 ...
- Java并发编程系列-(5) Java并发容器
5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...
- Java并发编程系列-(2) 线程的并发工具类
2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...
- Java并发编程系列-(1) 并发编程基础
1.并发编程基础 1.1 基本概念 CPU核心与线程数关系 Java中通过多线程的手段来实现并发,对于单处理器机器上来讲,宏观上的多线程并行执行是通过CPU的调度来实现的,微观上CPU在某个时刻只会运 ...
- Java并发编程系列-(7) Java线程安全
7. 线程安全 7.1 线程安全的定义 如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的. 类的线程安全表现为: 操作的原子性 内存的可见性 不 ...
- Java并发编程系列-(8) JMM和底层实现原理
8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...
- Java并发编程系列-(9) JDK 8/9/10中的并发
9.1 CompletableFuture CompletableFuture是JDK 8中引入的工具类,实现了Future接口,对以往的FutureTask的功能进行了增强. 手动设置完成状态 Co ...
- java并发编程系列原理篇--JDK中的通信工具类Semaphore
前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...
- Java并发编程系列-(4) 显式锁与AQS
4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...
- Java并发编程系列-(3) 原子操作与CAS
3. 原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程. 为了实 ...
随机推荐
- coursera吴恩达 机器学习编程作业原文件 及我的作业
保存在github上供广大网友下载:点击 8个zip,原文件,没有任何改动. 另外,不定期上传我自己关于这门课的学习过程笔记和心得,有兴趣的盆友可以点击这里查看.
- SpringBoot集成RocketMQ
实战,用案例来说话 前面已经说了JMS和RocketMQ一些概念和安装,下面使用SpringBoot来亲身操作一下. 生产者的操作 SpringBoot项目创建完成,引入依赖是第一步: <dep ...
- A1084. Broken Keyboard
On a broken keyboard, some of the keys are worn out. So when you type some sentences, the characters ...
- 【POJ1741】Tree
题目大意:给定一棵 N 个节点的无根树,边有边权,统计树上边权和不大于 K 的路径数. 对于每条树上路径,对于每一个点来说,该路径只有经过该点和不经过该点两种情况,对于不经过该点的情况,可以转化成是否 ...
- apigateway-kong(六)认证
到上游服务(API或微服务)的流量通常由各种Kong认证插件的应用程序和配置来控制.由于Kong的服务实体(Service Entity)代表自己的上游服务的1对1映射,最简单的方案是在选择的服务上配 ...
- 记录一次react相关总结
背景说明:元旦接到一个管理后台的项目,是一个关于自定义专题的项目.通过后台的配置自定义专题,前端根据专题模块进行渲染数据.由于管理后台是react做的前后分离,对于一个后端的我来说写写js也算是浅尝一 ...
- NO.11 复制时勿忘其每个成分
1.Coping 函数应该确保复制对象内的"每一个成员变量",和调用合适的 "base class"构造函数(base class 某些成员往往是private ...
- 国内互联网公司UED博客
1,淘宝UED http://ued.taobao.com/blog淘宝网用户体验团队博客,有关用户体验设计和研究的经验分享.UED的本意是用户体验设计,是英文User Experience De ...
- X-UA-compatible浅谈
最近了解到svg,原来它出现之前好几年,微软已经推出了vml,但是那时候却被人吐槽无数,看来过早的创新也是失败的原因之一呢~ 为什么谈到这个话题呢?因为IE史上有一个特别奇怪的浏览器IE8,它及不兼容 ...
- Hadoop生态圈-CentOs7.5单机部署ClickHouse
Hadoop生态圈-CentOs7.5单机部署ClickHouse 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 到了新的公司,认识了新的同事,生产环境也得你去适应新的集群环境,我 ...