java 并发编程多线程_java并发编程系列一、多线程

一、什么是线程

一个应用就是一个进程、一个进程由多个线程组成。一个生产车间比作是一个进程、工人比作是线程。当任务比较多的时候,增加工人可以提高效率,同时成本就是支付费用(机器资源,内存)也会增加。

packagecom.study.demo;importjava.lang.management.ManagementFactory;importjava.lang.management.ThreadInfo;importjava.lang.management.ThreadMXBean;public classThreadTest {public static voidmain(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、创建线程的方法

extendsThread

implementsRunnable

启动线程:threadl类的start()

线程完成:

1、run()方法执行完成;

2、抛出一个未处理的异常导致线程的提前结束

packagecom.study.demo;public classCreateThread {private class TestThread extendsThread{

@Overridepublic voidrun(){

System.out.println(" Thread");

}

}private class TestRunnable implementsRunnable{

@Overridepublic voidrun() {

System.out.println("Runnable");

}

}private voidtest(){

Thread t1= newTestThread();

Thread t2= new Thread(newTestRunnable());

t1.start();

t2.start();

}public static voidmain(String[] args) {newCreateThread().test();

}

}

2、取消和中断

不安全的取消

单独使用一个取消标志位

packagecom.lgstudy.interrupt;/*** lgs

*

* 使用自定义的取消标志位中断线程(不安全)*/

public classFlagCancel {private static class TestRunable implementsRunnable{private volatile boolean on = true;private long i =0;

@Overridepublic voidrun() {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 voidcancel(){

on= false;

}

}

}

Stop(),suspend(),resume()是过期的api,很大的副作用,容易导致死锁(suspend():将线程挂起不会释放锁)或者数据不一致(Stop():线程在未处理完数据时就停止)

3、如何安全的终止线程

使用线程的中断 :

interrupt()中断线程,本质是将线程的中断标志位设为true,其他线程向需要中断的线程打个招呼。是否真正进行中断由线程自己决定。

isInterrupted()线程检查自己的中断标志位

静态方法Thread.interrupted() 将中断标志位复位为false

由上面的中断机制可知Java里是没有抢占式任务,只有协作式任务。

为何要用中断,线程处于阻塞(如调用了java的sleep,wait等等方法时)的时候,是不会理会我们自己设置的取消标志位的,但是这些阻塞方法都会检查线程的中断标志位。

packagecom.lgstudy.interrupt;/*** lgs

*

* 安全的中断线程*/

public class SafeInterrupt implementsRunnable {private volatile boolean on = true;private long i =0;

@Overridepublic voidrun() {//阻塞方法wait,sleep,blockingqueue(put,take),on不起作用//要加上线程的中断才能安全的终止线程

while(on&&!Thread.currentThread().isInterrupted()){

i++;

}

System.out.println("TestRunable is runing :"+i);

}public voidcancel(){

on= false;//在java线程很忙的时候可能不会理会中断,所以定义一个标志位on更好

Thread.currentThread().interrupt();

}

}

4、处理不可中断的阻塞

IO通信 inputstream read/write等阻塞方法,不会理会中断,而关闭底层的套接字socket.close()会抛出socketException

NIO: selector.select()会阻塞,调用selector的wakeup和close方法会抛出ClosedSelectorException

死锁状态不响应中断的请求,这个必须重启程序,检查程序找到死锁的位置修改错误。

packagecom.lgstudy.interrupt;/*** lgs

*

* 调用阻塞方法时,如何中断线程*/

public classBlockInterrupt {private static volatile boolean on = true;private static class WhenBlock implementsRunnable {

@Overridepublic voidrun() {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 voidcancel() {

on= false;

Thread.currentThread().interrupt();

}

}

}

如何让我们的代码既可以响应普通的中断,又可以关闭底层的套接字呢?

覆盖线程的interrupt方法,在处理套接字异常时,再用super.interrupt()自行中断线程。

packagecom.lgstudy.interrupt;importjava.io.IOException;importjava.io.InputStream;importjava.net.Socket;/*** lgs

*

* 如何覆盖线程的interrupt() 方法*/

public class OverrideInterrupt extendsThread {private finalSocket socket;private finalInputStream in;publicOverrideInterrupt(Socket socket, InputStream in) {this.socket =socket;this.in =in;

}private voidt(){

}

@Overridepublic voidinterrupt() {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里的代码不一定执行的。

packagecom.lgstudy;importcom.lgstudy.threadstate.SleepUtils;/*** lgs

*

* 守护线程*/

public classDaemon {public static voidmain(String[] args) {

Thread thread= new Thread(newDaemonRunner());//将线程置为守护线程

thread.setDaemon(true);

thread.start();

}static class DaemonRunner implementsRunnable {

@Overridepublic voidrun() {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后面的语句才会继续执行

packagecom.lgstudy;/*** join的使用*/

public classJoinTest {static class CutInLine implementsRunnable{privateThread thread;publicCutInLine(Thread thread) {this.thread =thread;

}

@Overridepublic voidrun() {try{//在被插队的线程里,调用一下插队线程的join方法

thread.join();

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+" will work");

}

}public static voidmain(String[] args) {

Thread previous=Thread.currentThread();for(int i=0;i<10;i++){

Thread thread=

new Thread(newCutInLine(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变量的变化很固定即变化以后都是一个固定的值。

packagecom.lgstudy.volatiletest;/***

* 测试Volatile型变量的操作原子性*/

public class VolatileThread implementsRunnable {private volatile int a= 0;

@Overridepublic voidrun() {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);

}

}

}

packagecom.lgstudy.volatiletest;public classVolatileTest {public static voidmain(String[] args) {

VolatileThread volatileThread= newVolatileThread();

Thread t1= newThread(volatileThread);

Thread t2= newThread(volatileThread);

Thread t3= newThread(volatileThread);

Thread t4= newThread(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对象。对象锁锁的是当前对象实例。

packagecom.lgstudy.syn;importcom.lgstudy.threadstate.SleepUtils;/*** 类锁和实例锁*/

public classInstanceAndClass {//测试类锁

private static class TestClassSyn extendsThread{

@Overridepublic voidrun() {

System.out.println("TestClass is going...");

synClass();

}

}//测试对象锁

private static class TestInstanceSyn extendsThread{privateInstanceAndClass instanceAndClass;publicTestInstanceSyn(InstanceAndClass instanceAndClass) {this.instanceAndClass =instanceAndClass;

}

@Overridepublic voidrun() {

System.out.println("TestInstance is going..."+instanceAndClass);

instanceAndClass.synInstance();

}

}//测试对象锁

private static class TestInstance2Syn implementsRunnable{privateInstanceAndClass instanceAndClass;publicTestInstance2Syn(InstanceAndClass instanceAndClass) {this.instanceAndClass =instanceAndClass;

}

@Overridepublic voidrun() {

System.out.println("TestInstance2 is going..."+instanceAndClass);

instanceAndClass.synInstance2();

}

}//锁对象的方法

private synchronized voidsynInstance(){

SleepUtils.second(3);

System.out.println("synInstance is going...");

SleepUtils.second(3);

System.out.println("synInstance ended");

}//锁对象的方法

private synchronized voidsynInstance2(){

SleepUtils.second(3);

System.out.println("synInstance2 going...");

SleepUtils.second(3);

System.out.println("synInstance2 ended");

}//锁类的方法

private static synchronized voidsynClass(){

SleepUtils.second(1);

System.out.println("synClass going...");

SleepUtils.second(1);

}public static voidmain(String[] args) {

InstanceAndClass instanceAndClass= newInstanceAndClass();

Thread t1= newTestClassSyn();

Thread t2= new Thread(newTestInstanceSyn(instanceAndClass));

Thread t3= new Thread(newTestInstance2Syn(instanceAndClass));

t2.start();

t3.start();

SleepUtils.second(1);

t1.start();

}

}

2、等待和通知机制

等待方原则:

1、获取对象锁

2、如果条件不满足,调用对象的wait方法,被通知后依然要检查条件是否满足

3、条件满足以后,才能执行相关的业务逻辑

Synchronized(对象){

While(条件不满足){

对象.wait()

}

业务逻辑处理

}

通知方原则:

1、 获得对象的锁;

2、 改变条件;

3、 通知所有等待在对象的线程

Synchronized(对象){

业务逻辑处理,改变条件

对象.notify/notifyAll

}

packagecom.lgstudy.bq;importjava.util.LinkedList;importjava.util.List;/***

* 有界阻塞队列/有界缓存队列*/

public class BlockingQueueWN{//当前队列

private List queue = new LinkedList<>();//队列支持的最大容量

private final intlimit;//外部修改队列的容量

public BlockingQueueWN(intlimit) {this.limit =limit;

}//入队

public synchronized void enqueue(T item) throwsInterruptedException {//如果当前队列的容量已经满了的话就要等待

while(this.queue.size()==this.limit){

wait();

}//如果当前队列的容量为0的话,可以肯定有出队的线程正在等待,需要他可以准备出队了

if (this.queue.size()==0){

notifyAll();

}//开始入队

this.queue.add(item);

}//出队

public synchronized T dequeue() throwsInterruptedException {//如果当前队列的容量为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) throwsInterruptedException {long future = System.currentTimeMillis() +mills;long remaining =mills;//当超时大于0并且result返回值不满足要求

while ((result == null) && remaining > 0) {

wait(remaining);

remaining= future -System.currentTimeMillis();

}returnresult;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值