admin管理员组

文章数量:1122849

多线程..

多线程

概念:

线程 : 程序中执行的顺序流

多线程 : 多任务执行,多路径执行

​ 多任务之间可以实现同时执行

		优点 : 提高效率,提高性能            

​ 缺点 :设计更复杂,可能会造成数据不安全

了解 : 三高网站 : 高可能 高性能 高并发

进程与线程的区别 :

​ 进程 : 资源分配的最小单位,每个进程都具有自己的代码与数据空间(进程上下文),进程之间的切换资源消耗大

​ 线程 : cpu调度与执行的最小单位,一个进程至少包含1~n个线程,同一个进程的多个线程共享这个进程的代码数据空间,每个线程都有自己的运行栈与程序计数器,线程的切换消耗小

线程创建方式 :

继承Thread类

重写run() + start()开启线程

public class Class002_Thread extends Thread {/*定义线程体*/@Overridepublic void run() {for(int i =1;i<=20;i++){System.out.println("一边喝水");try {Thread.sleep(1);  //让出cpu资源} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {//主线程//创建线程Class002_Thread th = new Class002_Thread();//开启线程th.start();  //告诉cpu,我们线程可以被调度for(int i =1;i<=20;i++){System.out.println("一边吃饭...");try {Thread.sleep(1);  //让出cpu资源} catch (InterruptedException e) {e.printStackTrace();}}}
}

实现Runnable接口

重写run() + start()开启线程 --> *****

​ 优点 :

​ 接口多实现,类只能单继承

​ 实现资源共享

public class Class003_Thread implements Runnable{public static void main(String[] args) {Class003_Thread ct = new Class003_Thread();//创建线程对象Thread th = new Thread(ct);//HR代理角色     Manager真实角色//静态代理实现 : 代理角色与真实角色实现相同的接口     代理角色持有真实角色的引用      代理行为//new Hr(manager).addUser();//开启线程th.start();//new Thread(new Class003_Thread()).start();for(int i=1;i<=20;i++){System.out.println("一边打游戏...");try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}/*定义线程体*/@Overridepublic void run() {for(int i=1;i<=20;i++){System.out.println("一边陪女朋友...");try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}
}

实现juc包下Callable接口

重写call() + 线程池 --> 了解

​ 优点 : 1.抛出异常 2.定义返回值

public class Class005_Race implements Callable<Integer> {//记录赢的人的名字private String winner = null;  //控制游戏结束,线程的结束/*返回值: 参赛者的总步数*/@Overridepublic Integer call() {for(int i=1;i<=100;i++){System.out.println(Thread.currentThread().getName()+"正在跑第"+i+"步....");try {Thread.sleep(2);  //模拟跑步所耗时长} catch (InterruptedException e) {e.printStackTrace();}if("pool-1-thread-1".equals(Thread.currentThread().getName()) && i%10==0){try {Thread.sleep(10); //如果是兔子,修改10ms} catch (InterruptedException e) {e.printStackTrace();}}//判断是否还要继续if(!checkOver(i)){return i;}}return null;}//判断是否进行下一次步//返回值: false比赛结束  true比赛继续public boolean checkOver(int step){if(winner!=null){return false;}if(step==100){winner = Thread.currentThread().getName();return false;}return true;}public static void main(String[] args) throws ExecutionException, InterruptedException {//一场比赛Class005_Race race = new Class005_Race();//两个参赛者//1)创建一个固定大小2个线程的线程池--> 得到一个线程池提供的执行服务ExecutorService server = Executors.newFixedThreadPool(2);//2)提交任务  submit(Callable<T> task)Future<Integer> result1 = server.submit(race);Future<Integer> result2 =server.submit(race);//3)得到结果 V get()Integer num1 = result1.get();Integer num2 = result2.get();System.out.println(num1);System.out.println(num2);//4)关闭服务server.shutdown();}
}

通过内部类定义线程体

/*通过内部类定义线程体 :*/
public class Class006_Thread {//静态内部类static class Inner1 implements Runnable{@Overridepublic void run() {for(int i=1;i<=20;i++){System.out.println("一边玩手机...");try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {//局部内部类class Inner2 implements Runnable{@Overridepublic void run() {for(int i=1;i<=20;i++){System.out.println("一边吃饭...");try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}}new Thread(new Inner1()).start();new Thread(new Inner2()).start();//匿名内部类new Thread(new Runnable() {@Overridepublic void run() {for(int i=1;i<=20;i++){System.out.println("一边聊天...");try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}}).start();//lambdanew Thread(()->{for(int i=1;i<=20;i++){System.out.println("一边看电视...");try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
}

线程的状态

新生状态

新生状态 : new

就绪状态

就绪状态 : start(),进入到就绪队列,等待cpu的调度

如何让一个线程进入到就绪状态 : 1.start() 2.阻塞解除 3.yield 礼让线程 4.cpu调度的切换

/*yield : 礼让线程static void yield() 向调度程序提示当前线程是否愿意产生其当前使用的处理器。当调用yield方法,让一个线程从运行状态进入到就绪状态,让出cpu的资源*/
public class Class002_Yield implements Runnable{public static void main(String[] args) {Class002_Yield cy = new Class002_Yield();new Thread(cy,"A").start();new Thread(cy,"B").start();}@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"开始执行....");Thread.yield();System.out.println(Thread.currentThread().getName()+"结束执行....");}
}

运行状态

运行状态 : 当cpu调度一个线程,线程就进入到了运行状态

阻塞状态

阻塞状态 : sleep

如何让一个线程进入到阻塞状态 : 1.sleep() 2.join() 插队线程 3.wait() 等待 4.IO

sleep(ms) : 线程休眠,让出cpu的资源,会抱着对象的锁资源睡觉

作用 : 1.放大问题出现的可能性 2.模拟网络延迟

阻塞状态解除,无法立刻恢复到运行,因为需要cpu调度才能进入到运行,会直接会恢复到就绪状态

/*join() 插队线程void join() 等待这个线程死亡。void join(long millis) 此线程最多等待 millis毫秒。void join(long millis, int nanos) 此线程最多等待 millis毫秒加上 nanos纳秒。注意 : 当前线程如果被其他线程插队,当前线程就会进入到阻塞状态,等待插队线程执行完毕|等待指定时间接触阻塞先开启线程然后插队*/
public class Class003_Join {public static void main(String[] args) {new Thread(new Father()).start();}
}
class Father implements Runnable{@Overridepublic void run() {System.out.println("想抽烟了....");System.out.println("找儿子去买烟....");//创建儿子线程Thread son = new Thread(new Son());//开启线程son.start();//儿子线程插队执行try {son.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("接过烟吸一口....");}
}
class Son implements Runnable{@Overridepublic void run() {System.out.println("接过钱去买烟...");System.out.println("路边路过一家币子厅...进去玩10s种..");for(int i=1;i<=10;i++){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(i+"s过去了....");}System.out.println("赶紧去买烟..");System.out.println("把烟递给老爸...");}
}

终止状态

​ 如何让一个线程进入到终止状态 : 1.正常执行完毕 2.类似stop()等方法不推荐–>已经过时 3.添加标识判断的方式

注意 :

​ 一个线程如果一旦进入终止状态,永远不能恢复

getState()

/*线程状态 : Thread.State getState() 返回此线程的状态。Enum Thread.State线程状态。 线程可以处于以下状态之一:NEW : 新生状态尚未启动的线程处于此状态。RUNNABLE : 就绪状态在Java虚拟机中执行的线程处于此状态。BLOCKED : 等待获取锁的状态被阻塞等待监视器锁定的线程处于此状态。WAITING : wait() ,join()..无限期等待另一个线程执行特定操作的线程处于此状态。TIMED_WAITING : 与时间相关的等待 sleep(ms),join(ms),wait(ms)..正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。TERMINATED : 终止状态已退出的线程处于此状态。*/
public class Class004_getState implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"开始执行啦...");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"结束执行啦...");}public static void main(String[] args) throws InterruptedException {//创建线程Thread th = new Thread(new Class004_getState(),"A");System.out.println(th.getState());  //NEWth.start();System.out.println(th.getState());while(true){Thread.sleep(10);System.out.println(th.getState());if(th.getState()==Thread.State.TERMINATED){break;}}}
}

守护线程(了解)

/*线程 :  了解用户线程 :创建的所有线程默认都为用户线程所有的用户线程全部执行完毕,程序才会终止守护线程 : 用来守护用户线程的需要手动设置void setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程。当所有的用户线程执行完毕,守护线程会自动停止注意 :垃圾回收机制就是典型的守护线程先设置守护再设置就绪*/
public class Class005_Daemon implements Runnable {@Overridepublic void run() {while(true){try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("守护线程.....");}}public static void main(String[] args) {//主线程 : 用户线程Thread th = new Thread(new Class005_Daemon());;//设置守护线程th.setDaemon(true);//就绪th.start();for(int i=1;i<=10;i++){System.out.println(i);try {Thread.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}}}
}

线程优先级(了解)

/*线程优先级 :  Priority   1~10  了解放大线程优先执行的概率默认优先级 : 5static int MAX_PRIORITY 线程可以拥有的最大优先级。static int MIN_PRIORITY 线程可以拥有的最低优先级。static int NORM_PRIORITY 分配给线程的默认优先级。void setPriority(int newPriority) 更改此线程的优先级。int getPriority() 返回此线程的优先级。*/
public class Class006_Priority implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行了....");}public static void main(String[] args) {Class006_Priority  cp = new Class006_Priority();Thread th1 = new Thread(cp,"A");Thread th2 = new Thread(cp,"B");Thread th3 = new Thread(cp,"C");th1.setPriority(1);th3.setPriority(Thread.MAX_PRIORITY);System.out.println(th1.getPriority());System.out.println(th2.getPriority());System.out.println(th3.getPriority());th1.start();th2.start();th3.start();}
}

中断机制

/*void interrupt() 为线程添加中断标识。boolean isInterrupted() 测试此线程是否已被中断,是否曾经调用过interrupt方法添加了中断标识,是返回true,不是返回false。static boolean interrupted() 测试当前正在执行的线程是否已被中断,是否曾经调用过interrupt方法添加了中断标识,是返回true,不是返回false。同时会复位标识注意 :sleep方法让一个线程处于指定时间的休眠状态时,如果有任何线程中断了当前线程,调用interrupt方法添加了中断标识,遇到InterruptedException - 。 抛出此异常时,将清除当前线程的中断状态 。*/
public class Class007_Interrupt implements Runnable {@Overridepublic void run() {System.out.println("测试线程开始....");for(int i=1;i<=100;i++){System.out.println(i);/*try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}*/if(Thread.currentThread().isInterrupted()){System.out.println("判断是否添加同时复位"+Thread.interrupted());System.out.println("复位过之后中断标识的情况"+Thread.currentThread().isInterrupted());}}System.out.println("测试线程结束....");}public static void main(String[] args) {Thread th = new Thread(new Class007_Interrupt());System.out.println(th.isInterrupted());th.start();try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}th.interrupt(); //设置中断标识System.out.println(th.isInterrupted());}
}

中断线程

线程的 thread.interrupt() 方法是 中断线程 ,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会
中断一个正在运行的线程。

判断线程是否被中断

判断某个线程是否已被发送过中断请求,请使用Thread.currentThread().isInterrupted() 方法(因为它将线程中断标示位设置为true后,不会立刻清除中断标示位,即不会将中断标设置为false),而不要使用thread.interrupted()(该方法调用后会将中断标示位清除,即重新设置为false)方法来判断。

如何中断线程

如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、1.5中的
condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示
时如果发现中断标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可
中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程
的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程
序员有足够的时间来处理中断请求。
注,synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参
考后面的测试例子)。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断
的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它
获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit
unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特
性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个
超时设为无限的tryLock方法。
没有任何语言方面的需求一个被中断的线程应该终止。中断一个线程只是为了引起该线程的注意,被中
断线程可以决定如何应对中断。某些线程非常重要,以至于它们应该不理会中断,而是在处理完抛出的
异常之后继续执行,但是更普遍的情况是,一个线程将把中断看作一个终止请求

public void run() {try {...
/*
不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上!Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时。
*/while (!Thread.currentThread().isInterrupted()&& more work to do) {do more work}} catch (InterruptedException e) {
//线程在wait或sleep期间被中断了} finally {
//线程结束前做一些清理工作}
}

上面是while循环在try块里,如果try在while循环里时,因该在catch块里重新设置一下中断标示,因为抛出InterruptedException异常后,中断标示位会自动清除,此时应该这样:

public void run() {while (!Thread.currentThread().isInterrupted()&& more work to do) {try {...sleep(delay);} catch (InterruptedException e) {Thread.currentThread().interrupt();//重新设置中断标示}}
}

底层中断异常处理方式

不要在你的底层代码里捕获InterruptedException异常后不处理,会处理不当

如果你不知道抛InterruptedException异常后如何处理,那么你有如下好的建议处理方式:
1、在catch子句中,调用Thread.currentThread.interrupt()来设置中断状态(因为抛出异常后中断标示会被清除),让外界通过判断Thread.currentThread().isInterrupted()标示来决定是否终止线程还是继
续下去

2、或者,更好的做法就是,不使用try来捕获这样的异常,让方法直接抛出

中断应用

使用中断信号量中断非阻塞状态的线程
中断线程最好的,最受推荐的方式是,使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量,然后有秩序地中止任务。

使用thread.interrupt()中断非阻塞状态线程

使用thread.interrupt()中断阻塞状态线程

死锁状态线程无法被中断

中断I/O操作

线程同步

线程安全 :

​ 在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就有可能造成数据的不准确。

​ 多个线程同时操作同一份资源,才有可能出现线程不安全问题

线程同步:

当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。
同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
“同”字从字面上容易理解为一起动作
其实不是,“同”字应是指协同、协助、互相配合。
在Java里面,通过 synchronized 进行同步的保证。它包括两种用法:synchronized 方法和synchronized 块

同步锁 synchronized : 让多个执行可能会造成数据不安全的代码段时排队执行

​ 同步的条件 : 协调多线程排队执行的条件 --> 对象锁

​ 对象在使用之前要求已经创建|存在

​ 引用指向的对象不能改变

​ 同步的代码段 : 多个线程之间需要排队执行的代码段

​ 同步的代码范围过大,效率低

​ 同步的代码范围过小,锁不住

使用语法 :

​ 同步方法 : 在方法上使用synchronized修饰

​ 同步的代码段为整个方法体

​ 成员方法 : 锁的是调用成员方法的对象

​ 静态方法 : 锁的是类的Class对象

​ 同步块 :

 synchronized(条件){同步的代码段;        }  

​ 条件 : 类的Class对象,this,资源对象

public class Class001_Synchronized {public static void main(String[] args) {//System.out.println(Single.newInstance());//System.out.println(Single.newInstance());//System.out.println(Single.newInstance());new Thread(()->{System.out.println(Single.newInstance());}).start();new Thread(()->{System.out.println(Single.newInstance());}).start();new Thread(()->{System.out.println(Single.newInstance());}).start();}
}
//单例模式 : 懒汉式(线程不安全|不同步)  饿汉式(线程安全|同步)
class Single{//2.私有的静态的该类的引用private static Single single = null;//1.私有的构造器private Single(){}//3.公共的静态的访问方式//同步方法/*public static synchronized Single newInstance(){System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");if(single==null){single = new Single();}return single;}*///同步块public static Single newInstance(){System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");System.out.println("-------------");synchronized (Single.class){if(single==null){single = new Single();}}return single;}
}

synchronized 方法

通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:

public synchronized void accessVal(int newVal);

synchronized 方法控制对类成员变量的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得 该锁,重新进入可执行状态。
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

synchronized 块

在代码块前加上 synchronized 关键字,并指定加锁的对象

synchronized(syncObject){//允许访问控制的代码
}

互斥锁原理。利用互斥锁解决临界资源问题

代码实例:

/*模拟银行柜台叫号系统安全问题*/
public class Class002_Practice implements Runnable{private int num = 1;  //业务序号 1~100/*每个柜台的服务业务*/@Override// a bpublic void run() {while(true){synchronized (this){//线程停止的条件if(num>100){break;}System.out.println(num++ +"号用户请到"+Thread.currentThread().getName()+"办理业务....");try {Thread.sleep(1000);  //模拟办理业务所需时长} catch (InterruptedException e) {e.printStackTrace();}}/*让cpu重新选择执行的线程*/try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {//1.一个系统,100号Class002_Practice cp = new Class002_Practice();//2.2个柜台Thread th1 = new Thread(cp,"一号柜台");Thread th2 = new Thread(cp,"二号柜台");//3.开启线程th1.start();th2.start();}
}

死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。

// 口红
class Lipstick {
}
// 镜子
class Mirror {
}
class Makeup extends Thread {int flag;String girl;static Lipstick lipstick = new Lipstick();static Mirror mirror = new Mirror();@Overridepublic void run() {
// TODO Auto-generated method stubdoMakeup();}void doMakeup() {if (flag == 0) {synchronized (lipstick) {System.out.println(girl + "拿着口红!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (mirror) {System.out.println(girl + "拿着镜子!");}}} else {synchronized (mirror) {System.out.println(girl + "拿着镜子!");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lipstick) {System.out.println(girl + "拿着口红!");}}}}
}
public class TestDeadLock {public static void main(String[] args) {Makeup m1 = new Makeup();m1.girl = "大丫";m1.flag = 0;Makeup m2 = new Makeup();m2.girl = "小丫";m2.flag = 1;m1.start();m2.start();}
}

如何解决死锁问题:

  1. 往往是程序逻辑的问题。需要修改程序逻辑。
  2. 尽量不要同时持有两个对象锁。 如修改成如下:
void doMakeup(){if(flag==0){synchronized (lipstick) {System.out.println(girl+"拿着口红!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (mirror) {System.out.println(girl+"拿着镜子!");}}else{synchronized (mirror) {System.out.println(girl+"拿着镜子!");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (lipstick) {System.out.println(girl+"拿着口红!");}}
}

线程通信

每个线程之间的执行互不影响,想要通过线程通信实现多个线程的执行好似商量好符合逻辑需求

通过线程通信实现生产者消费者模式

wait() notify() notifyAll() 配合使用,在同步环境下使用

对象.wait() 当前正在执行的线程就会进入到与对象相关的等待池中进行等待,等待被唤醒,这是一种阻塞状态

​ 让出cpu的资源同时让出对象的锁

对象.notify() 唤醒对象等待中正在等在的线程,被唤醒的线程会从阻塞恢复到就绪状态,等待cpu的调用并同时获取对象的锁才能执行

注意 : 以上的方法要求使用在同步环境下,作用是协调多线程对共享数据存储问题,否则会遇到异常java.lang.IllegalMonitorStateException

/*通过信号灯法模拟人车共用街道Street 街道   信号灯boolean    ns南北    we东西人 : true绿灯:ns南北车 : false:红灯:we东西*/
public class Class003_Street {public static void main(String[] args) {//一个街道Street street = new Street();//人与车线程new Thread(new Person(street)).start();new Thread(new Car(street)).start();}
}
class Street{private boolean flag = false; //信号灯//ns南北public  void ns(){if(flag){try {Thread.sleep(1000);  //人过街道的总时长} catch (InterruptedException e) {e.printStackTrace();}System.out.println("人走 .... ");flag = false; //红灯this.notify(); //唤醒等待池中等待的线程try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}//we东西public  void we(){if(!flag){try {Thread.sleep(1000);  //车过街道的总时长} catch (InterruptedException e) {e.printStackTrace();}System.out.println("车走 .... ");flag = true; //绿灯this.notify(); //唤醒等待池中等待的线程try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}
}
class Person implements Runnable{private Street street = null;public Person(Street street) {this.street = street;}@Overridepublic void run() {while(true){street.ns();}}
}
class Car implements Runnable{private Street street = null;public Car(Street street) {this.street = street;}@Overridepublic void run() {while(true){street.we();}}
}

生产者/消费者模式

在常见的多线程问题解决中,同步问题的典型示例是“生产者-消费者”模型,也就是生产者线程只负责生产,消费者线程只负责消费,在消费者发现无内容可消费时则睡觉
同步解决问题的另一种典型方式:生产者/消费者模式:
在常见的多线程问题解决中,同步问题的典型示例是“生产者-消费者”模型,也就是生产者线程只负责生产,消费者线程只负责消费,在消费者发现无内容可消费时则睡觉。下面举一个例子。

public class Demo004Synchronized4 {public static void main(String[] args) {SyncStack stack = new SyncStack();ShengChan sc = new ShengChan(stack);Chi chi = new Chi(stack);sc.start();chi.start();}
}// 馒头类
class Mantou {
}// 有生产、有消费功能的工厂
class SyncStack {List<Mantou> list = new ArrayList<Mantou>(); // 容器,10就满了// 生产馒头的方法public synchronized void push(Mantou mantou) {// 我认为现在已经放满了if (list.size() == 10) {try {// 通知别人可以来买了this.notify();//如果不唤醒的话。以后这两个线程都会进入等待线程,没有人唤醒。// 满了之后就停止生产this.wait();} catch (InterruptedException e) {e.printStackTrace();}} else {list.add(mantou);System.out.println("生产第" + +list.size() + "个馒头");try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}// 吃馒头的方法public synchronized void pop() {// 吃完了if (list.size() == 0) {try {// 通知别人该生产了this.notify(); //唤醒在当前对象等待池中等待的第一个线程。notifyAll叫醒所有在当前对象等待池中等待的所有线程。// 停下吃的动作this.wait();//wait后,线程会将持有的锁释放。sleep是即使睡着也持有互斥锁。} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}list.remove(list.size() - 1);System.out.println("现在还剩下 " + list.size() + " 个馒头");try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}// 生产馒头的线程
class ShengChan extends Thread {SyncStack stack;public ShengChan(SyncStack stack) {this.stack = stack;}@Overridepublic void run() {// 一直生产馒头while (true) {Mantou mantou = new Mantou();stack.push(mantou);}}
}class Chi extends Thread {SyncStack stack;public Chi(SyncStack stack) {this.stack = stack;}@Overridepublic void run() {
// 一直吃馒头while (true) {this.stack.pop();}}
}

可重入锁

定义

ReentrantLock:可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。就是可以重复获取相同的锁,synchronized和ReentrantLock都是可重入的
ReentrantLock 和 synchronized 不一样,需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样
ReentrantLock在同一个时间点只能被一个线程获取(当某线程获取到“锁”时,其它线程就必须等待);
ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。
在“公平锁”的机制下,线程依次排队获取锁;
公平锁保证一个阻塞的线程最终能够获得锁,因为是有序的,所以总是可以按照请求的顺序获得锁。
而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。
非公平锁意味着后请求锁的线程可能在其前面排列的休眠线程恢复前拿到锁,这样就有可能提高并发的性能。
总结
因为通常情况下挂起的线程重新开始与它真正开始运行,二者之间会产生严重的延时。因此非公平锁就可以利用这段时间完成操作。
这是非公平锁在某些时候比公平锁性能要好的原因之一。

源码分析

// 创建一个 ReentrantLock ,默认是“非公平锁”。
ReentrantLock()
// 创建策略是fair的 ReentrantLock。fair为true表示是公平锁,fair为false表示是非公平锁。
ReentrantLock(boolean fair)
// 查询当前线程保持此锁的次数。
int getHoldCount()
// 返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
protected Thread getOwner()
// 返回一个 collection,它包含可能正等待获取此锁的线程。
protected Collection<Thread> getQueuedThreads()
// 返回正等待获取此锁的线程估计数。
int getQueueLength()
// 返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
protected Collection<Thread> getWaitingThreads(Condition condition)
// 返回等待与此锁相关的给定条件的线程估计数。
int getWaitQueueLength(Condition condition)
// 查询给定线程是否正在等待获取此锁。
boolean hasQueuedThread(Thread thread)
// 查询是否有些线程正在等待获取此锁。
boolean hasQueuedThreads()
// 查询是否有些线程正在等待与此锁有关的给定条件。
boolean hasWaiters(Condition condition)
// 如果是“公平锁”返回true,否则返回false。
boolean isFair()
// 查询当前线程是否保持此锁。
boolean isHeldByCurrentThread()
// 查询此锁是否由任意线程保持。
boolean isLocked()
// 获取锁。
void lock()
// 如果当前线程未被中断,则获取锁。
void lockInterruptibly()
// 返回用来与此 Lock 实例一起使用的 Condition 实例。
Condition newCondition()
// 仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
boolean tryLock()
// 如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
boolean tryLock(long timeout, TimeUnit unit)
// 试图释放此锁。
void unlock()

加锁和释放次数不一样导致的死锁

import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
public class WhatReentrant3 {public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();new Thread(new Runnable() {@Overridepublic void run() {try {lock.lock();System.out.println("第1次获取锁,这个锁是:" + lock);int index = 1;while (true) {try {lock.lock();System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);try {Thread.sleep(new Random().nextInt(200));} catch (InterruptedException e) {e.printStackTrace();}if (index == 10) {break;}} finally {// lock.unlock();// 这里故意注释,实现加锁次数和释放次数不一样}}} finally {lock.unlock();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {try {lock.lock();for (int i = 0; i < 20; i++) {System.out.println("threadName:" +Thread.currentThread().getName());try {Thread.sleep(new Random().nextInt(200));} catch (InterruptedException e) {e.printStackTrace();}}} finally {lock.unlock();}}}).start();}
}

由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
让加锁和释放次数一样,就没问题了

try {lock.lock();System.out.println("第1次获取锁,这个锁是:" + lock);int index = 1;while (true) {try {lock.lock();System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);
... 代码省略节省篇幅...} finally {
// lock.unlock();// 这里故意注释,实现加锁次数和释放次数不一样}}
} finally {lock.unlock();
// 在外层的finally里头释放9次,让加锁和释放次数一样,就没问题了for (int i = 0; i < 9; i++) {lock.unlock();}
}

RL与SY的区别

Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition。
Condition类能实现synchronized和wait、notify搭配的功能,另外比后者更灵活,Condition可以实现多路通知功能,也就是在一个Lock对象里可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择的进行线程通知,在调度线程上更加灵活。而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在这个对象上。线程开始notifyAll时,需要通知所有的WAITING线程,没有选择权,会有相当大的效率问题。
Condition是个接口,基本的方法就是await()和signal()方法。
Condition依赖于Lock接口,生成一个Condition的基本代码是ondition(),参考下图。

​ 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。
​ Conditon中的await()对应Object的wait(),Condition中的signal()对应Object的notify(),

​ Condition中的signalAll()对应Object的notifyAll()。

线程池

线程池的概念

​ 线程池是一种多线程处理形式,处理过程中将任务加入到队列,然后在创建线程后自己主动启动这些任务。线程池线程都是后台线程。每一个线程都使用默认的堆栈大小,以默认的优先级执行。并处于多线程单元中。
​ 假设某个线程在托管代码中空暇(如正在等待某个事件),则线程池将插入还有一个辅助线程来使全部处理器保持繁忙。
​ 假设全部线程池线程都始终保持繁忙,但队列中包括挂起的工作,则线程池将在一段时间后创建还有一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程能够排队,但他们要等到其它线程完毕后才启
线程池包括一下几个部分:
1、线程池管理器(ThreadPoolManager):用于创建并管理线程池
2、工作线程(WorkThread): 线程池中线程
3、任务接口(Task):每一个任务必须实现的接口。以供工作线程调度任务的运行。
4、任务队列:用于存放没有处理的任务。提供一种缓冲机制。

线程池的工作机制

在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。
一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

使用线程池的原因

在面向对象编程中,创建和销毁对象是非常费时间的,由于创建一个对象要获取内存资源或者其他很多其他资源。在Java中更是如此,虚拟机将试图跟踪每个对象。以便可以在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能降低创建和销毁对象的次数,特别是一些非常耗资源的对象创建和销毁。
怎样利用已有对象来服务就是一个须要解决的关键问题,事实上这就是一些"池化资源"技术产生的原因。比如后面会了解到的数据库连接池正是遵循这一思想而产生的。

线程池的分类

在Java5之后,并发编程引入了一堆新的启动、调度和管理线程的API。Executor框架便是Java 5中引入的,其内部使用了线程池机制,它在 java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作

newFixedThreadPool

作用:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
特征:
(1)线程池中的线程处于一定的量,可以很好的控制线程的并发量
(2)线程可以重复被使用,在显示关闭之前,都将一直存在
(3)超出一定量的线程被提交时候需在队列中等待
创建方式:
(1)Executors.newFixedThreadPool(int nThreads);//nThreads为线程的数量
(2)Executors.newFixedThreadPool(int nThreads,ThreadFactory threadFactory);//nThreads为线程的数量,threadFactory创建线程的工厂方式

public class ThreadPoolTest {public static void main(String[] args)throws Exception {Thread.currentThread().setName("主线程");System.out.println(Thread.currentThread().getName() + ": 输出的结果" );// 通过线程池工厂创建线程数量为2的线程池ExecutorService service = Executors.newFixedThreadPool(2);//执行线程,execute()适用于实现Runnable接口创建的线程service.execute(new MyThread());//submit()适用于实现Callable接口创建的线程Future<Integer> task = service.submit(new MyCallable());System.out.println(task.get());// 关闭线程池		service.shutdown();}
}

newCachedThreadPool

作用:创建一个可根据需要创建新线程的线程池【缓存线程池】,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。
特征:
(1)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
(2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
(3)当线程池中,没有可用线程,会重新创建一个线程
创建方式: Executors.newCachedThreadPool();

newSingleThreadExecutor

作用:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
创建方式:
(1)Executors.newSingleThreadExecutor() ;
(2)Executors.newSingleThreadExecutor(ThreadFactory threadFactory);// threadFactory创建线程的工厂方式

newScheduleThreadPool

作用: 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
特征:
(1)线程池中具有指定数量的线程,即便是空线程也将保留
(2)可定时或者延迟执行线程活动
创建方式:
(1)Executors.newScheduledThreadPool(int corePoolSize);// corePoolSize线程的个数
(2)newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);// corePoolSize线程的个数,threadFactory创建线程的工厂

newSingleThreadScheduledExecutor

作用: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
(2)可定时或者延迟执行线程活动
创建方式:
(1)Executors.newSingleThreadScheduledExecutor() ;
(2)Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory);//threadFactory创建线程的工厂

System.out.println(task.get());
// 关闭线程池
service.shutdown();
}
}


#### newCachedThreadPool作用:创建一个可根据需要创建新线程的线程池【缓存线程池】,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。
特征:
(1)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
(2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
(3)当线程池中,没有可用线程,会重新创建一个线程
创建方式: Executors.newCachedThreadPool();#### newSingleThreadExecutor作用:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
创建方式:
(1)Executors.newSingleThreadExecutor() ;
(2)Executors.newSingleThreadExecutor(ThreadFactory threadFactory);// threadFactory创建线程的工厂方式#### newScheduleThreadPool作用: 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
特征:
(1)线程池中具有指定数量的线程,即便是空线程也将保留
(2)可定时或者延迟执行线程活动
创建方式:
(1)Executors.newScheduledThreadPool(int corePoolSize);// corePoolSize线程的个数
(2)newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);// corePoolSize线程的个数,threadFactory创建线程的工厂#### newSingleThreadScheduledExecutor作用: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
(2)可定时或者延迟执行线程活动
创建方式:
(1)Executors.newSingleThreadScheduledExecutor() ;
(2)Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory);//threadFactory创建线程的工厂

本文标签: 多线程