黑马程序员————Java基础日常笔记---对多线程的理解

<span style="font-family: SimSun; background-color: rgb(255, 255, 255);"> ------</span><a target=_blank href="http://www.itheima.com" target="blank" style="font-family: SimSun; background-color: rgb(255, 255, 255);">Java培训、Android培训、iOS培训、.Net培训</a><span style="font-family: SimSun; background-color: rgb(255, 255, 255);">、期待与您交流!</span>

1,线程的由来:

1.1:进程与线程的理解:
进程是一个执行中的程序,
每一个进程执行都有一个执行的顺序,该顺序是一个执行路径,或者叫一个控制单元;
进程:
应用程序只要是启动,都会在内存中分配一片空间(地址),进程就是用来标识这个空间的,
用于封装里面代码的控制单元。
因此:线程才是进程中的真正执行的部分;
也就是说一个进程至少有一个线程(控制单元)。
如果这个线程运行的代码存在于main方法中,那么这个线程叫主线程。
主线程不是单线程,当JVM启动时, 就是一个多线程,
原因:
在执行主函数是, 执行的是主线程, 但是在内存中, 栈堆中会建立很多的对象, 如果对象不被使用, 会垃圾回收,主线程还在执行下面的操作, 但是被垃圾回收的对象就被干掉了,这就是同时在进行,  
主线程在继续执行的同时 , 这个垃圾被垃圾回收了,所以JVM这时至少有两个线程
一个是主线程, 
一个是负责垃圾回收的线程

1.2 多线程存在的意义

多个线程的出现,可以让一个程序中的多个代码同时执行, 这样就可以提高程序运行的效率;
比如:
如果就是一个主线程,
那内存中产生的垃圾没有回收,那么就有很多的垃圾,直到内存放不下了, 就会出问题, 
这时JVM就会停下来, 即主线程先停在这个位置,先要把垃圾处理,然后在执行,这样就不合理了, 
但是如果这个主线程一直逐条执行,而另一个线程在处理垃圾,有两个控制单元(执行路径),这样就会效率更快;

2 线程的创建:

多线程的创建就是为了让某些代码能够同时执行,

那如何才能在自定义的代码中自定义控制单元呢?

首先, 创建这个控制单元, 就是执行路径,他本身也是一类事物,因此就可以把它描述成一个类了;
然后创建这个控制单元的类的对象, 这样就可以调用这个类的方法(功能),来创建多条执行路径,实现代码的同步执行。
这执行路径是在进程中的, 进程是系统Windows所创建的, 因此进程中的线程也是windows帮忙创建的,而jvm依赖于系统,只需要调用系统中的内容,既可以完成动作。
而java提供了对线程对象的体现,被java虚拟机封装成了对象,因此可以直接调用。
就需要找对象, 在API中查找,java就已经提供了对线程这类事物的描述,就是Thread类。


java虚拟机允许应用程序并发地允许多个执行线程。

2.1 创建线程方法一:继承Thread类

这种方法是将类声明为Thread的子类, 该子类应重写Thread类的run()方法。

通过对API的查找, java已经提供了对线程这类事物的描述,就是Thread类。

代码如下

class Demo extends Thread//继承Thread对象,让他成为Thread类的子类,这样就可以使用Thread类中的方法了
{
	public void run(){//将需要执行的代码放在这里
		for(int i=0;i<68;i++){
			System.out.println("Demo run.."+i);
		}
	}
}
class ThreadDemo_1 
{
	public static void main(String[] args) 
	{
		Demo d=new Demo();//创建了一个线程
		//d.run();//如果这样的话,就是调用demo的run()方法, 只有一个主线程
		d.start();//调用这个方法, 就是创建这个线程, 并启动了这个线程,让线程开始执行,JVM就会调用这个线程的run()方法了
		for(int i=0;i<68;i++){
			System.out.println("main..."+i);
		}
	}
}
 

2.1.1 继承thread类创建线程——分析

Jvm创建了主线程,

到了d=newDemo();,就创建了新的控制单元,线程被创建了,被主线程所开启的   那么这个程序就多了一个执行路径。


这里需要注意:
这两个线程在CPU不会同时执行的,Cpu在某一个时刻只执行一个执行路径,那么CPU在这些程序之间做着快速的切换,速度很快,所以你会感觉同时执行,
所以CPU 在切换进程, 也就是在切换每个线程。
多个线程在争抢CPU的资源,真正的是CPU 在执行,一个意思;
当主线程完了, 但是d还没有完, 搞定后,才停止。
但是这个程序的运行结果每次的都不同,为啥?
原因:
因为多个线程都在获取cpu 的执行权, cpu执行到谁, 谁就运行,明确一点, 在某一个时刻, 只能有一个程序在运行,(多核除外)
我们可以形象把多线程的运行行为在互相抢夺Cpu的执行权
这就是多线程的一个特性: 随机性 谁抢到谁执行,至于执行多长,cpu说的算
这样Cpu就优化资源。
为什么要覆盖run方法呢?
Thread类用于描述线程
该类就定义了一个功能, 用于存储线程要运行的代码 该存储功能就是run方法;
同样,主线程要运行的代码在main方法中, 这是JVM定义,存放在main方法中;
如果直接在主线中run()方法
可以直接调用run ,但是在run()方法中没有任何东西;
开启线程的目的是为了运行指定的代码,父类提供了空间,你有代码的话,怎么做才能被运行到呢?
复写run(),并将需要运行的代码放在run() 这样就可以运行到了;
编译的时候, 运行的是start()调用的是父类的run()方法, 但是子类把父类的run()方法给覆盖了,所以调用的是子类的run()方法,
从而就继承thread的类并复写run()方法了。
总结: 继承Thread类创建线程:流程:
1,定义类来继承Thread;
2,复写Thread类中的run()方法,
             目的:将自定义的代码存储在run()方法中, 让线程运行。
3,主函数中创建Thread子类的对象, 然后调用这个线程的start()方法,
             该方法的作用:a: 启动线程
                          b:调用run()方法

2.2 线程的运行状态
没有执行资格的情况下是冻结状态;
有执行资格的情况下是临时状态;
既有执行资格又有执行权, 是运行状态;
程序结束了,就是消亡状态;
图解:

细节:


2.3 创建线程方法二——实现Runnable接口
问题来源:
如果仍然继承Extends类,
如果一个程序中有多个线程同时需要共享数据, 
共享数据可以把他定义成静态,但是这样,他的生命周期会很长,不建议;
因此可以定义一个线程,并让这个线程都去开启执行start(),然而这样做是没有意义的, 
就会提示线程状态出错,因为已经运行的程序不需要再次开启的,所以继承Extends类就不太靠谱了,
 
所以,采用创建线程的第二种方法, 声明实现Runnable接口的类, 
原理:
Runnable接口应该由那些打算通过某一线程执行这个实现Runnable接口的类的实例来实现
他为非Thread子类的类(执行这个实现Runnable接口的)提供了一种激活方式。
Runnable接口中的抽象方法只有一个run(),
这个run()的作用:将需要执行另一条路径的代码(明确要运行什么代码)存储在这里, 
然后实现Runnable接口的类的对象,创建一个线程启动该线程将导致在独立执行的线程中执行这个对象的run方法,
如何来创建这个线程呢?
通过创建Thread类的对象,来调用这个有参的构造方法  thread的构造函数中,就有这个那么就在new thread对象的同时, 就指定run方法所属对象 ,这样就ok了。

实现Runnable类来创建线程,原因在上面已经分析清楚,这里总结下实现Runnable类来创建线程的过程:
1,定义类实现Runnable接口;
2,覆盖Runnable接口中的run()方法
        将线程要运行的代码存放在该run方法中, 
3,通过Thread类建立线程对象;
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中, 
      为什么要将Runnable接口的子类对象传递给Thread的构造方法。
      因为,自定义的run()方法所属的对象是Runnable接口的子类对象, 所以要让线程去指定对象的run()方法, 
      就必须明确该run()方法所属的对象
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
2.3.1 创建线程——实现方式和继承方式的区别
个人理解:
继承Thread类, 比较方便, 但是有局限, 
他的子类只能继承单一的线程,有局限性, 
所以在定义线程时, 建议使用实现的方式,
因为一个类可以多实现, 可以作为实际参数传递到Thread类的构造方法中, 
区别:
继承Thread:线程代码存放在Thread子类run()中;
实现Runnable:线程代码存放在接口的子类的run()中;
2.4线程中的安全问题和解决方案:
线程中的安全问题
安全问题的由来:

当一个程序中有多个线程同时运行的时候, 并且是共享同一数据资源,
那么多个线程会争取抢夺CPU的执行权,当其中的一个线程使用了这个共享数据,但是只是执行了一部分, 还有一部分的
代码没有执行完, 另一个线程就争抢到了CPU的执行权, 同样执行那这一部分,从而导致共享数据的错误。

解决方案:
对多个线程共享数据的语句只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行,即使其他线程有了CPU的执行
权了无法进来执行, 这就需要一个门来限制,叫做同步代码块;
synchronized(对象)
{
需要被同步的代码     //哪些代码需要同步, 就要看那些代码在操作共享数据,
}

当然门上需要有锁来标识的和控制的,这里的“对象”,就相当于这把门上的锁, 也叫监视器。
这个锁是如何操作的呢?
当然在第一个线程进来的时候, 这个锁当然是开着的, 当这个线程进来之后, 就会把这个门关上,程序中就是同步代码块。
通过同步代码块中的对象来标识,
对象如同锁,  持有锁的线程可以在同步中执行, 没有持有锁的线程即使获取了cpu 的执行权了,也进不去,  因为没有获取锁,
因此同步就可以解决问题了。
2.4.1 线程的同步的前提——重点
1, 必须要有两个或者两个以上的线程有共享数据的操作
2,必须是多个线程使用同一个锁。
必须保证同步中只有一个线程在运行, 
有些代码需要同步, 有些代码不需要同步, (看是否在共享数据,因为安全问题是由于共享数据所产生的)
同步的好处:
解决了多线程的安全问题
弊端:
多个线程需要判断锁, 较为消耗资源。

2.4.2 线程——同步代码块和同步函数
当一个函数中的代码都共享的数据,并且不会出现异常, 那么就可以把这个函数同步。 
原因:
同步代码块用来封装需要同步的共享数据的代码。
函数:也是用来封装代码的, 如果这个函数中的代码都是共享的数据时, 那么就可以将这个函数同步, 

2.4.3 线程——同步函数,静态函数
首先,看程序中是否有安全问题, 是否需要同步?如何找问题:
1,明确哪些代码是多线程运行的代码;
2,明确共享数据
3,明确多线程运行代码中哪些语句是操作共享数据的。
同步函数的锁是this,
静态函数的锁是class字节码对象。

2.5 线程—单例设计模式下的安全问题

终身指向newSingle(),所以不管线程多少, 都不会产生安全问题。


单例设计模式下懒汉式的作用:延迟加载,

问题的产生:

当有多个线程执行到if语句时, 
这时被其他的线程抢夺了CPU, 那么这样的话, 就会产生多个对象, 这样就会产生安全隐患。
因为单例设计中,只能有一个该类对象被创建。

那如何解决呢?
这里就需要同步, 前提: 多个线程在操作共享资源。

 

2.png (20.11 KB, 下载次数: 0)

下载附件  保存到相册

昨天 08:32 上传


这样就可以解决, 
但是这样的话, 每次都要先判断这个锁,这样会比较低效,
因此:在锁的上面,再加一个判断,

 

3.png (14.26 KB, 下载次数: 0)

下载附件  保存到相册

昨天 08:34 上传


这样双重判断,减少了锁的判断次数, 就会提高效率了,

2.6 线程——死锁问题:

在同步中, 有时会产生死锁,这样我们需要尽量避免死锁的发生,
什么是死锁呢?——同步里面嵌套同步;

你持有一个锁,我也有一个锁, 我要到你那里去运行,我要拿你的锁,但是谁都不放锁, 就导致程序死锁了,程序死锁, 程序就挂着不动了,
如何产生死锁呢?
代码如下:
class Test implements Runnable
{
	private boolean flag;
	Test(boolean flag){
		this.flag=flag;
	}
	public void run(){
		if(flag){
			while(true)
			{
				synchronized(Mylock.locka){//同步中嵌套同步
					System.out.println("a锁说:这是我的锁");
					synchronized(Mylock.lockb){
						System.out.println("a锁说:谢谢你的b锁");
					}
				}
			}
		}
		else{
			while(true)
			{
				synchronized(Mylock.lockb){
					System.out.println("b锁说:这是我的锁");
					synchronized(Mylock.locka){
						System.out.println("b锁说:谢谢你的a锁");
					}
				}
			}
		}
	}
}
//为了方便
class Mylock
{
	static Object locka=new Object();//静态的目的为了方便调用
	static Object lockb=new Object();
}
class ThreadDeadTest 
{
	public static void main(String[] args) 
	{
		Thread t1= new Thread(new Test(true));
		Thread t2= new Thread(new Test(false));
		t1.start();
		t2.start();
	}
}
运行结果:
 
从代码分析, 开始线程二抢到CPU的执行权, b锁向a锁要到了锁, 所以就
 出现了和谐的状态;
但是之后, 就出现了死锁。  ,谁都不给谁, 就僵持着,哎!!!
2.7 多线程间的通信:
多线程间的通信:
就是多个线程在操作同一个资源,但是操作的动作不同。
如果不同步, 那么线程之间在抢夺CPU的执行权的时候, 就会出现数据的错误。
所以前提是在需要数据共享的代码中同步。
由于线程之间的功能不同, 所以即使写了synchronized, 也无法保证是值是正确的, 因此需要考虑同步中的另一个
前提,是否是同一个锁,而锁是一个任意的对象,由于他们是共享同一个资源, 所以这个锁可以是这个资源描述的类的对象;
2.7.1 等待唤醒机制
多线程——等待唤醒机制接着线程之间的通信, 这里有个问题, 同步了, 但是运行的话, 会执行一大片, 但是一般都是存一个,输出一个,
为什么会执行一大片?
一个线程可能一直拥有CPU的执行权;
如何解决?(参考代码)
这里可以用一个标记来标识(flag),当存一个完了之后, 就改变这个标识,当下次再执行时, 就不会再存了,
此时可以将这个线程1给冻结(sleep(时间(毫秒)),wait()),如果用sleep(),时间不确定, 所以就用wait(),当执行完了之后,
标识发生改变,再次执行时, 就处于冻结状态了,失去执行资格。
那什么时候恢复执行资格呢?
当另一个线程2进来将这个存的值,取走了, 就恢复执行资格,如何恢复?
当取走后, 标识再次发生改变,然后用notify(),唤醒线程1,此时如果这个线程2再次执行时, 由于没有了值, 那么就将线程2冻结,wait()。
这个过程就是线程间的等待唤醒机制。

代码如下:
<pre name="code" class="java">class Res //共享资源的类
{
	String name;
	String sex;
	boolean flag=false;
}
/*
等待的线程在哪里呢?
线程运行的时候, 内存中会建立一个叫做线程池的,等待线程都存放在线程池中, 
当notify的时候, 都是唤醒的是线程池中的线程,当线程池中有许多的线程都在等待,
通常是唤醒第一被等待的线程, 按照顺序来叫醒

*/
class Input implements Runnable//需要用到一个资源,
{
	private Res r;
	Input(Res r){//传递过来一个资源类的对象
		this.r=r;
	}
	public void run(){
		int x=0;
		while(true)
		{
			synchronized(r)//这里需要同步,
			{	
				if(r.flag){
					try{r.wait();}catch(Exception e){}//当为真时, 就等待, 不能再传值了,因为已经有值
				}
				if(x==0){
				r.name="mike";
				r.sex="man";
			}
				else{
					r.name="小王";
					r.sex="女女女女女女";
				}
				x=(x+1)%2;
				r.flag=true;
				r.notify();//在线程池中叫醒Output线程
			}
		}
	}
}
class Output implements Runnable
{
	private Res r;
	Output(Res r){
		this.r=r;
	}
	public void run(){
		while(true)
		{
			synchronized(r){//这里也需要同步,并且锁需要是同一个锁。
				if(!r.flag){
					try{r.wait();}catch(Exception e){}
				}
//Wait(), notify(), notifyAll(), 全部用在同步中,因为需要锁必须标识wait()线程所处的锁
				System.out.println(r.name+"....."+r.sex);
				r.flag=false;
				r.notify();
			}
		}
	}
}
class ThreadInputOutput2
{
	public static void main(String[] args) 
	{
		Res r=new Res();
		Input in=new Input(r);
		Output out=new Output(r);
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		t1.start();
		t2.start();
	}
}

 
         2.7.2 多线程——线程池 
         
当线程进入冻结状态是, 这个等待的线程存放在哪里呢?
线程运行的时候, 内存中会建立一个叫做线程池的,等待线程都存放在线程池中, 当notify的时候, 都是唤醒的是线程池中的线程,当线程池中有许多的线程都在等待,通常是唤醒第一被等待的线程,按照顺序来叫醒,
notify()只能唤醒线程池中的第一个等待的线程,如果需要全部唤醒的话,就需要notifyAll();
唤醒线程池中所有等待的线程(不管是本方的线程,还是另一个线程);
说明:想wait(),notify(),notifyAll(),这些都不是Thread类中的方法,而是继承来自Object类中的方法,
定义线程的,怎么定义在Object类中呢?
这里需要调用这些方法, 该线程必须拥有此对象的监视器(锁),而同步才有锁, 而且是同一个对象的锁,
而两个对象的锁是不一样的, 因此需要来标识是哪个对象的锁。
而锁是任意的对象,所以可以被任意对象调用的方法(wait(),notify(),notifyAll()),就定义在objec类中了。
2.7.3 多线程的通信-生产者消费者

生产一个消费一个,

代码如下:

<pre name="code" class="java">class Resource
{
	private String name;
	private int count=1;
	private boolean flag=false;
	public synchronized void set(String name){
		if(flag){
			try{this.wait();}
			catch(Exception e){}
		}
		this.name=name+"....."+count++;
		System.out.println(Thread.currentThread().getName()+"....生产...."+this.name);
		flag=true;
		this.notify();
	}
	public synchronized void out(){
		if(!flag){
			try{this.wait();}
			catch(Exception e){}
		}
		System.out.println(Thread.currentThread().getName()+"........消费........"+this.name);
		flag=false;
		this.notify();
	}
}
class Producer implements Runnable
{	
	private Resource res;
	Producer(Resource res){
		this.res=res;
	}
	public void run()
	{
		while(true){
			res.set("+商品+");
		}
	}
}
class Customer implements Runnable
{
	private Resource res;
	Customer(Resource res){
		this.res=res;
	}
	public void run(){
		while(true){
			res.out();
		}
	}
}
class  ThreadProCus
{
	public static void main(String[] args) 
	{
		Resource res=new Resource();
		Producer pro=new Producer(res);
		Customer cus=new Customer(res);
		Thread t1=new Thread(pro);
		Thread t2=new Thread(cus);
		t1.start();
		t2.start();
	}
}
 
          

结果如下:


这里有两个线程, 但是实际开发有多个线程来生产消费;

但是一般来说, 实际开发是有多个线程来生产消费的, 
 运行的话,就会有问题,
 。这样就不对了, 为啥?
原因:线程可能唤醒的是本方的线程, 而将前一个给覆盖了, 这时就需要将这个线程再次判断一下, 所以需要把if变成while循环。
而这样, 可能会将这些线程都给冻结了, 因此需要将notify(),改成notifyAll().

代码如下;

class Resource
{
	private String name;
	private int count=1;
	private boolean flag=false;
	public synchronized void set(String name){
		while(flag){//用if判断, 数据错误, while循环,全部等待, 不是活着的, 冻结了
			try{this.wait();}
			catch(Exception e){}
		}
		this.name=name+"....."+count++;
		System.out.println(Thread.currentThread().getName()+"....生产...."+this.name);
		flag=true;
		this.notifyAll();
	}
	public synchronized void out(){
		while(!flag){
			try{this.wait();}
			catch(Exception e){}
		}
		System.out.println(Thread.currentThread().getName()+"........消费........"+this.name);
		flag=false;
		this.notifyAll();
	}
}
class Producer implements Runnable
{	
	private Resource res;
	Producer(Resource res){
		this.res=res;
	}
	public void run()
	{
		while(true){
			res.set("+商品+");
		}
	}
}
class Consumer implements Runnable
{
	private Resource res;
	Consumer(Resource res){
		this.res=res;
	}
	public void run(){
		while(true){
			res.out();
		}
	}
}
class  ThreadProCus1
{
	public static void main(String[] args) 
	{
		Resource res=new Resource();
		Producer pro=new Producer(res);
		Consumer con=new Consumer(res);
		Thread t1=new Thread(pro);
		Thread t2=new Thread(pro);
		Thread t3=new Thread(con);
		Thread t4=new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
总结:
1,对于多个生产和消费:
为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。
2,为什么定义notifyAll().
因为需要将对方的线程唤醒。
因为只用notify(),容易出现只唤醒本方的线程的情况,导致程序中的所有的线程都在等待。  
2.7.4 多线程——生产者和消费者的改进-Lock接口
在上一个代码中,存在一个bug, 就是notifyAll(), 可能会将本方的线程也给唤醒, 如何才能让他不换醒呢?
接口中Lock,特点是替代了synchronized,比使用synchronized方法和语句可获得更广泛的锁定操作。
首先:synchronized,开锁解锁都是隐式的, 而lock就可以一目了然的看到,他是显示的, 
并且可以支持多个相关的Condition对象。
Condition把wait,notify,notifyAll,替代,因此可以将代码改进:

/*
用synchronized,开锁解锁是隐式的, 但是用lock , 就可以一目了然了, 
显式的,
*/
import java.util.concurrent.locks.*;
class Resource
{
	private String name;
	private int count=1;
	private boolean flag=false;
	private Lock lock=new ReentrantLock();//多态, 创建一个锁对象,
	private Condition condition_pro=lock.newCondition();//创建condition的对象
	private Condition condition_con=lock.newCondition();
	public void set(String name)throws InterruptedException{
		//将同步的语句变成了两个方法lock(),unlock().
		lock.lock();//调用lock()方法,获取了一个锁
		//用if判断, 数据错误, while循环,全部等待, 不是活着的, 冻结了
		try
		{
			while(flag){
				condition_pro.await();
			}
			this.name=name+"....."+count++;
			System.out.println(Thread.currentThread().getName()+"....生产...."+this.name);
			flag=true;	
			condition_con.signal();
		}
		finally{
			lock.unlock();//解锁
		}
		
	}
	public void out()throws InterruptedException{
		lock.lock();
		try
		{
			while(!flag){
			condition_con.await();
			}
			System.out.println(Thread.currentThread().getName()+"........消费........"+this.name);
			flag=false;
			condition_pro.signal();
		}
		finally{
			lock.unlock();
		}
	}
}
class Producer implements Runnable
{	
	private Resource res;
	Producer(Resource res){
		this.res=res;
	}
	public void run()
	{
		while(true){
			try
			{
				res.set("+商品+");
			}
			catch (InterruptedException e)
			{
			}	
		}
	}
}
class Consumer implements Runnable
{
	private Resource res;
	Consumer(Resource res){
		this.res=res;
	}
	public void run(){
		while(true){
			try
			{
				res.out();
			}
			catch (InterruptedException e)
			{
			}
		}
	}
}
class  ThreadProCus2
{
	public static void main(String[] args) 
	{
		Resource res=new Resource();
		Producer pro=new Producer(res);
		Consumer con=new Consumer(res);
		Thread t1=new Thread(pro);
		Thread t2=new Thread(pro);
		Thread t3=new Thread(con);
		Thread t4=new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
2.8 多线程——停止线程
首先开启多个线程运行, 一般运行的代码通常是循环结构。如果需要让线程停止, 
只要控制住循环, 就可以让run方法结束了。
让线程停止,这里用到的是intterupt(),以前用的是stop(),但是目前已经停用了。
intterupt()表示强制清除冻结状态,让线程回复到运行状态中来,不是结束线程。
相当于:这时把他给催眠(wait())了, 但是这时催眠的人出国了, 我来了, 一砖头(intterupt())下去,
受伤了, 发生异常了,叫做人家不该醒, 你让他醒,就异常了。
但是这样还是不能让程序结束, 但是能让他执行, 就离程序结束不远了,
在异常的处理语句中, 让flag变成false, 这样while循环就结束了, 那么这个线程就结束了,
代码如下:
class StopThread implements Runnable
{
	private boolean flag=true;
	public synchronized void run(){
		while(flag){
			try
			{
				wait();//这样线程处于冻结状态,主函数结束,但是程序没有结束
				//本来冻结了,但是一砖头(interrupt()),打醒了,就抛出异常
			}
			catch (InterruptedException e)
			{
				System.out.println(Thread.currentThread().getName()+".....Exception");
				flag=false;//一砖头打醒后,会执行,那么while就不会执行了,
			}
			System.out.println(Thread.currentThread().getName()+".....run");
		}
	}
	public void changeFlag(){
		flag=false;
	}
}
class Threadstop
{
	public static void main(String[] args) 
	{
		StopThread st=new StopThread();
		Thread t1=new Thread(st);	
		Thread t2=new Thread(st);
		t1.start();
		t2.start();
		int num=0;
		while(true){
			if(num==60){
				t1.interrupt();
				t2.interrupt();
				//st.changeFlag();
				break;
			}
		System.out.println(Thread.currentThread().getName()+"...."+num++);
		}
		System.out.println("over");
	}
}
总结:
当没有指定的方式让冻结的线程恢复到运行的状态时,  这时,  需要对冻结进行清除, 强制让线程恢复到运行状态中来,  这样就可以让操作标记让线程结束,
Thread 类中提供了该方法, Intertupt。
2.9 多线程——守护线程,join方法
守护线程:也叫后台线程,他需要在线程创建后, 就需要设置。
前台线程:所看到的线程都是前台的线程。
然后在开始执行线程。
在运行的时候, 会共同抢夺CPU的执行权运行, 开启运行时没有区别的;
但是在结束程序上有区别:
当前台的线程都结束后, 后台的线程就会自动结束。
join方法:等待该线程终止。

代码如下:
//线程0, 要申请加入进来, 线程0要CPU的执行权,join的意思就是, 抢夺CPU的执行权。
//这时主线程就把CPU的执行权给了线程0,主线程处于冻结状态
//数据全部打印完, 结束后,主线程才恢复到运行状态,这个就是join 的用法,
//主线程让出了执行权,这时主线程会只等到t1线程执行完,才活过来, 和t2结不结束, 没有关系,
//join可以临时加入线程执行
class Demo implements Runnable
{
	public void run(){
		for(int x=0;x<70;x++){
			System.out.println(Thread.currentThread().toString()+"...."+x);
			Thread.yield();//暂停当前执行的线程程序
		}
	}
}
class ThreadJoin 
{
	public static void main(String[] args)throws InterruptedException
	{
		Demo d=new Demo();
		Thread t1=new Thread(d);
		Thread t2=new Thread(d);
		t1.start();
		//t1.setPriority(Thread.MAX_PRIORITY);//设置优先级
		
		t2.start();
		//t1.join();
		/*
		for(int x=0;x<80;x++){
			System.out.println(Thread.currentThread().getName()+"...."+x);
		}
		*/
	}
}
2.10  多线程——优先级,yield
多线程的优先级:设置setPriority();
把优先级设高点, 但是对于CPU的抢夺, 抢夺的概率会大一点。
yield()方法:
表示暂停当前正在执行的线程对象,并执行其他的线程。
线程释放了CPU的执行权, 就停下了, 另一个线程进来,又停下,这样交替, 
稍微减缓线程的频率, 交替进行,达到线程都有机会运行。如果不这样, 一个线程会输出好几次;














 

3.png (14.26 KB, 下载次数: 0)

下载附件  保存到相册

昨天 08:34 上传




























 -------------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

Java并发编程 背景介绍 并发历史 必要性 进程 资源分配的最小单位 线程 CPU调度的最小单位 线程的优势 (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 (2)建模简单:通过使用线程可以讲复杂并且异步的工作流进一步分解成一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置交互 (3)简化异步事件的处理:服务器应用程序在接受来自多个远程客户端的请求时,如果为每个连接都分配一个线程并且使用同步IO,就会降低开发难度 (4)用户界面具备更短的响应时间:现代GUI框架中大都使用一个事件分发线程(类似于中断响应函数)来替代主事件循环,当用户界面用有事件发生时,在事件线程中将调用对应的事件处理函数(类似于中断处理函数) 线程的风险 线程安全性:永远不发生糟糕的事情 活跃性问题:某件正确的事情迟早会发生 问题:希望正确的事情尽快发生 服务时间过长 响应不灵敏 吞吐率过低 资源消耗过高 可伸缩性较低 线程的应用场景 Timer 确保TimerTask访问的对象本身是线程安全的 Servlet和JSP Servlet本身要是线程安全的 正确协同一个Servlet访问多个Servlet共享的信息 远程方法调用(RMI) 正确协同多个对象中的共享状态 正确协同远程对象本身状态的访问 Swing和AWT 事件处理器与访问共享状态的其他代码都要采取线程安全的方式实现 框架通过在框架线程中调用应用程序代码将并发性引入应用程序,因此对线程安全的需求在整个应用程序中都需要考虑 基础知识 线程安全性 定义 当多个线程访问某个类时,这个类始终能表现出正确的行为,那么就称这个类是线程安全的 无状态对象一定是线程安全的,大多数Servlet都是无状态的 原子性 一组不可分割的作 竞态条件 基于一种可能失效的观察结果来做出判断或执行某个计算 复合作:执行复合作期间,要持有锁 锁的作用 加锁机制、用锁保护状态、实现共享访问 锁的不恰当使用可能会引起程序性能下降 对象的共享使用策略 线程封闭:线程封闭的对象只能由一个线程拥有并修改 Ad-hoc线程封闭 栈封闭 ThreadLocal类 只读共享:不变对象一定是线程安全的 尽量将域声明为final类型,除非它们必须是可变的 分类 不可变对象 事实不可变对象 线程安全共享 封装有助于管理复杂度 线程安全的对象在其内部实现同步,因此多个接口可以通过公有接口来进行访问 保护对象:被保护的对象只能通过特定的锁来访问 将对象封装到线程安全对象中 由特定锁保护 保护对象的方法 对象的组合 设计线程安全的类 实例封闭 线程安全的委托 委托是创建线程安全类的最有效策略,只需要让现有的线程安全类管理所有的状态 在现有线程安全类中添加功能 将同步策略文档化 基础构建模块 同步容器类 分类 Vector Hashtable 实现线程安全的方式 将状态封装起来,对每个公有方法都进行同步 存在的问题 复合作 修正方式 客户端加锁 迭代器 并发容器 ConcurrentHashMap 用于替代同步且基于散列的Map CopyOnWriteArrayList 用于在遍历作为主要作的情况下替代同步的List Queue ConcurrentLinkedQueue *BlockingQueue 提供了可阻塞的put和take方法 生产者-消费者模式 中断的处理策略 传递InterruptedException 恢复中断,让更高层的代码处理 PriorityQueue(非并发) ConcurrentSkipListMap 替代同步的SortedMap ConcurrentSkipListSet 替代同步的SortedSet Java 5 Java 6 同步工具类 闭锁 *应用场景 (1)确保某个计算在其需要的所有资源都被初始化后才能继续执行 (2)确保某个服务在其所依赖的所有其他服务都已经启动之后才启动 (3)等待知道某个作的所有参与者都就绪再继续执行 CountDownLatch:可以使一个或多个线程等待一组事件发生 FutureTask *应用场景 (1)用作异步任务使用,且可以使用get方法获取任务的结果 (2)用于表示一些时间较长的计算 状态 等待运行 正在运行 运行完成 使用Callable对象实例化FutureTask类 信号量(Semaphore) 用来控制同时访问某个特定资源的作数量,或者同时执行某个指定作的数量 管理者一组虚拟的许可。acquire获得许可(相当于P作),release释放许可(相当于V作) 应用场景 (1)二值信号量可用作互斥体(mutex) (2)实现资源池,例如数据库连接池 (3)使用信号量将任何一种容器变成有界阻塞容器 栅栏 能够阻塞一组线程直到某个事件发生 栅栏和闭锁的区别 所有线程必须同时到达栅栏位置,才能继续执行 闭锁用于等待事件,而栅栏用于等待线程 栅栏可以重用 形式 CyclicBarrier 可以让一定数量的参与线程反复地在栅栏位置汇集 应用场景在并行迭代算法中非常有用 Exchanger 这是一种两方栅栏,各方在栅栏位置上交换数据。 应用场景:当两方执行不对称的作(读和取) 线程池 任务与执行策略之间的隐形耦合 线程饥饿死锁 运行时间较长的任务 设置线程池的大小 配置ThreadPoolExecutor 构造参数 corePoolSize 核心线程数大小,当线程数= corePoolSize的时候,会把runnable放入workQueue中 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了” keepAliveTime 保持存活时间,当线程数大于corePoolSize的空闲线程能保持的最大时间。 workQueue 保存任务的阻塞队列 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务 threadFactory 创建线程的工厂 handler 拒绝策略 unit 是一个枚举,表示 keepAliveTime 的单位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7个可选值 线程的创建与销毁 管理队列任务 饱和策略 AbortPolicy DiscardPolicy DiscardOldestPolicy CallerRunsPolicy 线程工厂 在调用构造函数后再定制ThreadPoolExecutor 扩展 ThreadPoolExecutor afterExecute(Runnable r, Throwable t) beforeExecute(Thread t, Runnable r) terminated 递归算法的并行化 构建并发应用程序 任务执行 在线程中执行任务 清晰的任务边界以及明确的任务执行策略 任务边界 大多数服务器以独立的客户请求为界 在每个请求中还可以发现可并行的部分 任务执行策略 在什么(What)线程中执行任务? 任务按照什么(What)顺序执行(FIFO、LIFO、优先级)? 有多少个(How Many)任务能并发执行? 在队列中有多少个(How Many)任务在等待执行? 如果系统由于过载而需要拒绝一个任务,那么应该选择哪一个(Which)任务?另外,如何(How)通知应用程序有任务被拒绝? 在执行一个任务之前或之后,应该进行什么(What)动作? 使用Exector框架 线程池 newFixedThreadPool(固定长度的线程池) newCachedThreadPool(不限规模的线程池) newSingleThreadPool(单线程线程池) newScheduledThreadPool(带延迟/定时的固定长度线程池) 具体如何使用可以查看JDK文档 找出可利用的并行性 某些应用程序中存在比较明显的任务边界,而在其他一些程序中则需要进一步分析才能揭示出粒度更细的并行性 任务的取消和关闭 任务取消 停止基于线程的服务 处理非正常的线程终止 JVM关闭 线程池的定制化使用 任务和执行策略之间的隐性耦合 线程池的大小 配置ThreadPoolExecutor(自定义的线程池) 此处需要注意系统默认提供的线程池是如何配置的 扩展ThreadPoolExector GUI应用程序探讨 活跃度(Liveness)、性能、测试 避免活跃性危险 死锁 锁顺序死锁 资源死锁 动态的锁顺序死锁 开放调用 在协作对象之间发生的死锁 死锁的避免与诊断 支持定时的显示锁 通过线程转储信息来分析死锁 其他活跃性危险 饥饿 要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。 糟糕的响应性 如果由其他线程完成的工作都是后台任务,那么应该降低它们的优先级,从而提高前台程序的响应性。 活锁 要解决这种活锁问题,需要在重试机制中引入随机性(randomness)。为了避免这种情况发生,需要让它们分别等待一段随机的时间 性能与可伸缩性 概念 运行速度(服务时间、延时) 处理能力(吞吐量、计算容量) 可伸缩性:当增加计算资源时,程序的处理能力变强 如何提升可伸缩性 Java并发程序中的串行,主要来自独占的资源锁 优化策略 缩
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值