Java-day08-线程

一:线程

  • 多线程

  •   定义:

   Java-day08-线程_第1张图片

  •  多线程的内存展示:

    Java-day08-线程_第2张图片

  • 多线程工作原理

    Java-day08-线程_第3张图片

  • 代码
package com.qf.test;

public class Demo1 {
	//主线程的任务区所在方法
	public static void main(String[] args) {//一个线程
		new Test();
		/*
		 * 手动运行垃圾回收器
		 * 原理:当执行gc时,会触发垃圾回收机制,开启垃圾回收线程,执行finalize方法
		 * cpu的特性:多个线程之间是抢cpu的关系cpu有随机性
		 */
		System.gc();//两个线程
		
		System.out.println("main");
	}//主函数结束,主任务区结束,主线程随着任务的结束而结束,线程随着任务的开始而开始.当线程还在工作的时候,进程不能结束.
}

class Test{
	@Override
	/*
	 * 重写finalize方法
	 * 正常情况下,这个函数是由系统调用的,这里只是为了更好的观察多线程的发生
	 * 当Test对象被释放的时候,会自动的调用finalize方法
	 */
	protected void finalize() throws Throwable {
		System.out.println("finalize");
	}
}
  •  线程创建

  •    定义:

            默认情况下,主线程和垃圾回收线程都是由系统创建的,但是我们需要完成自己的功能----创建自己的线程对象

     java将线程面向对象了,形成的类就是Thread,在Thread类内部执行任务的方法叫run()

 eg : 注意:如果想让run作为任务区,必须让他去被自动调用.我们通过执行start()方法,来开启线程,继而实现run方法的自动调用.

  •   线程创建的两种方式

         方式一:继承Thread并重写run方法来定义线程任务

         Java-day08-线程_第4张图片

         Java-day08-线程_第5张图片

    这种方法的两点不足:1.由于java是单继承,这就导致若继承了Thread就不能再继承其他类了,在实际开发中非常不方便,因为无法重用其他类的某些方法

                                        2.由于继承Thread后重写run方法定义了线程要执行的任务,这就导致线程与线程要执行的任务有一个必然的耦合关系,不利于线程重用

        方式二:实现Runnable接口单独定义线程任务

       Java-day08-线程_第6张图片          Java-day08-线程_第7张图片

  • 匿名内部类形式完成线程的两种创建方式

     Java-day08-线程_第8张图片

  • 代码

     

package com.qf.test;
/*
 * 默认情况下,主线程和垃圾回收线程都是由系统创建的,但是我们需要完成自己的功能----创建自己的线程对象
 * java将线程面向对象了,形成的类就是Thread,在Thread类内部执行任务的方法叫run()
 * 
 * 注意:如果想让run作为任务区,必须让他去被自动调用.我们通过执行start()方法,来开启线程,继而实现run方法的自动调用.
 * 
 *  主线程的名字:main   子线程的名字:从Thread-0开始命名
 */
public class Demo2 {
	public static void main(String[] args) {
		//创建自己的线程对象
//		//1.直接使用Thread创建线程对象
//		//分析:由于我们实现的实际功能Thread类是决定不了的,所以没有办法将我们的功能放入Thread的run方法里
//		//所以Thread的run 方法是一个空方法.如果我们想实现自己的功能,可以写Thread类的子类,重写run方法
//		Thread thread1 = new Thread();
//		Thread thread2 = new Thread();
//		thread1.start();
//		thread2.start();
		//手动调用,不能让run充当任务区
//		thread1.run();
//		thread2.run();
		
		//2.使用子类创建对象
		MyThread t0 = new MyThread("t0");
		MyThread t1 = new MyThread("t1");
		t0.start();//开启线程
		t1.start();//开启线程
//		
//		//当我们手动调用run的时候,他失去了任务区的功能,变成了一个普通的方法.
//		//当run作为一个普通方法时,内部对应的线程跟调用他的位置保持一致.
//		t0.run();
//		t1.run();
//		
		for (int i = 0; i < 10; i++) {
			System.out.println("  main   "+Thread.currentThread().getName()+"   i:"+i);//有三个线程(暂时忽略垃圾回收线程)
		}
	}
}

class MyThread extends Thread{
	String name1;
	//重写run方法,实现我们的功能.run就是我们的任务区
	/*
	 * Thread.currentThread():获取的是当前的线程
	 * Thread.currentThread().getName():线程的名字
	 */
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.name1+"  MyThread   "+Thread.currentThread().getName()+"   i:"+i);
		}
	}
	public MyThread(String name1) {
		this.name1 = name1;
	}
}
  •    售票员问题

    

package com.qf.test;
/*
 * 实现四个售票员售票
 * 将四个售票员看做四个线程
 * 数据:一份
 * 
 * 实现多线程的方式两种:
 * 第一种方式:通过创建Thread子类的方式实现功能----线程与任务绑定在了一起,操作不方法
 * 第二种:将任务从线程中分离出来,哪个线程需要工作,就将任务交给谁,操作方便
 */
//线程与任务不分离
//public class Demo5 {
//	public static void main(String[] args) {
//		SubThread t0 = new SubThread();
//		SubThread t1 = new SubThread();
//		SubThread t2 = new SubThread();
//		SubThread t3 = new SubThread();
//		//开启线程
//		t0.start();
//		t1.start();
//		t2.start();
//		t3.start();
//	}
//}
//
//class SubThread extends Thread{
//	static int  sum = 20;//大家共用这个变量
//	@Override
//	public void run() {
//		for (int i = 0; i < 5; i++) {
//			System.out.println("剩余 票数:"+ --sum);
//		}
//	}
//}

//当线程与任务分离后
//这里Thread内部默认有一个run,又通过ticket传入一个run,为什么优先调用的是传入的run
//如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
public class Demo3 {
	public static void main(String[] args) {
		//任务对象
		Ticket ticket = new Ticket();
		//将任务与线程绑定
		Thread t0 = new Thread(ticket);
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		Thread t3 = new Thread(ticket);
		//开启线程
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}

//任务类

class Ticket implements Runnable{
     int sum = 20;
     boolean flag = true;
	public void run() {

		while (flag) {
			//让当前的线程睡100毫秒
			//作用:让他暂时让出cpu 
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (sum > 0) {
				System.out.println("剩余 票数:"+ --sum);
			}else {
				flag = ! flag;
			}
			
		}
	}
}
  •  线程安全问题(售票员问题)

        问题:实现四个售票员将四个售票员看作四个线程,但是数据只有一份

        分析:4个线程共用一个数据,出现了-1,-2,-3等错误的数据

                  具体分析:1.共用了一个数据

                                    2.共享语句有多条,一个线程使用cpu,没有使用完,cpu被抢走,当再次抢到cpu的时候,直接执行后面的语句,造成了错误的发生

         解决方案:

                 在代码中使用同步代码块(同步锁)

                 解释:在某一段任务中,同一时间只允许一个线程执行任务,其他线程即使抢到cpu,也无法进入当前的任务区间,只有当当前线程将任务执行完后其他线程才能有资格进入

         同步代码块的构成

        synchronized(锁(对象)){

               同步的代码

        }

 作为锁的对象的要求:1.必须是对象 2.必须保证被多个线程共享

 可以充当锁的:1.一个普通的对象  2.当前对象的引用    3.类的字节码文件

 同步代码块儿的特点:1.可以保证线程的安全     2.由于每次都要进行判断处理,所以降低了执行效率

 总结:什么时候使用同步代码块

   1.多个线程共享一个数据

   2.至少有两个线程

  • 代码

   

package com.qf.test;
/*
 * 实现四个售票员售票
 * 将四个售票员看做四个线程
 * 数据:一份
 * 
  /* 线程安全问题:
* 分析:4个线程共用了一个数据,出现了-1,-2,-3等错误的数据
* 
* 具体分析:1.共用了一个数据
* 2.共享语句有多条,一个线程使用cpu,没有使用完,cpu被抢走,当再次抢到cpu的时候,直接执行后面的语句,造成了错误的发生.
* 
* 解决:
* 在代码中使用同步代码块儿(同步锁)
* 解释:在某一段任务中,同一时间只允许一个线程执行任务,其他的线程即使抢到了cpu,也无法进入当前的任务区间,只有当当前的线程将任务执行完后,
* 其他的线程才能有资格进入

同步代码块儿的构成:
* synchronized(锁(对象)){
* 	  同步的代码
* }
 

* 对作为锁的对象的要求:1.必须是对象      2.必须保证被多个线程共享
* 可以充当锁的:1.一个普通的对象      2.当前对象的引用--this    3.类的字节码文件
* 
* 同步代码块儿的特点:1.可以保证线程的安全     2.由于每次都要进行判断处理,所以降低了执行效率
* 
* 总结:什么时候使用同步代码块儿
* 1.多个线程共享一个数据
* 2.至少有两个线程
*/
public class Demo4 {
	public static void main(String[] args) {
		//任务对象
		Ticket1 ticket = new Ticket1();
		//将任务与线程绑定
		Thread t0 = new Thread(ticket);
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		Thread t3 = new Thread(ticket);
		//开启线程
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}

//任务类

class Ticket1 implements Runnable{
     int sum = 20;
     boolean flag = true;
     //让Object类型的对象临时充当锁
     Object object = new Object();
	public void run() {
		while (flag) {
			//让当前的线程睡100毫秒
			//作用:让他暂时让出cpu 
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			/*
			 * 锁的条件:
			 * 1.锁必须是对象      普通的对象/this/字节码文件
			 * 2.要被所有的线程共享
			 * 
			 * 注意:字节码文件的使用范围太大,一般不建议使用.
			 */
			synchronized (this) {//线程互斥
				if (sum > 0) {
					System.out.println("剩余 票数:"+ --sum);
				}else {
					flag = ! flag;
				}
			}
		}
	}
}
  •   同步代码块和同步函数的比较

     区别: 

              同步代码块儿使用更加的灵活,只给需要同步的部分代码同步即可,而同步函数是给这个函数内的所有代码同步. 由于处于同步的代码越少越好,所以最好使用同步代码块儿

    注意点:  1.当在一个类中同时存在多个synchronized修饰的代码块儿或函数时,要想安全,就必须让他们后面的对象一致。因为只有同一把锁才能安全。

 同步函数的锁:this
                    2.静态同步函数在进内存的时候不会创建对象,但是存在其所属类的字节码文件对象,属于class类型的对象,所以静态同步函数的锁是其所属类的字节码文件对象

    理解synchronized关键字

        1.synchronized关键字的作用域有两种:

        1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时
访问相同类的另一个对象实例中的synchronized方法; 
        2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

        2.除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中

        表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;

        3.synchronized关键字是不能继承的

         synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

      Java-day08-线程_第9张图片

  • 代码
package com.qf.test;
/*
 * 实例:两个人同时向银行同一个账户存钱
 * 两个人---两个线程     一份数据
 */
public class Demo5 {
	public static void main(String[] args) {
		//1.创建任务类对象
		SaveMoney saveMoney = new SaveMoney();
		//2.创建两个线程当做两个人
		Thread t0 = new Thread(saveMoney);
		Thread t1 = new Thread(saveMoney);
		//3.开启线程
		t0.start();
		t1.start();
	}
}

class Bank {
	int money;
	//使用同步代码块儿
//	public void addMoney(int money) {
//		synchronized (Bank.class) {
//		    this.money += money;
//		    System.out.println(this.money);
//		}
//	}
	//使用同步函数
	//非静态的同步函数
	//在synchronized后面默认有一个this
	public synchronized void addMoney(int money) {
	    this.money += money;
	    System.out.println(this.money);
	}
	//静态的同步函数
	//在synchronized后面默认有一个当前类的字节码文件-----Bank.class
//	public synchronized static void addMoney(int money) {
//	    this.money += money;
//	    System.out.println(this.money);
//	}
}

class SaveMoney implements Runnable{
	Bank bank = new Bank();
	@Override
	public void run() {
		
		for (int i = 0; i < 3; i++) {
			bank.addMoney(100);
		}
		
	}
}
  • 线程通信(生产者消费者问题)

  •   打印机问题-----一次输入一次输出
  • 唤醒等待机制

     wait():让当前的线程变成等待的状态,放入一个池子(线程池),失去了抢cpu的能力,.等待唤醒(锁相当于给当前的线程做了一个标记)
     notify():让当前的线程从等待状态唤醒,相当于从池子中取出线程.(唤醒的是同一把锁下的任意一个线程)
     notifyAll():唤醒的是同一把锁下的所有线程

  • 代码
package com.qf.test;
/*
 * 两个线程的通信
 * 
 * 实例:打印机打印----一次输入一次输出
 * 两个线程:输入线程和输出线程
 * 两个任务区:输入任务,输出任务
 * 一份数据
 * 
   *使用唤醒等待机制---notify()/notifyAll()/wait()
 *wait():让当前的线程变成等待的状态,放入一个池子(线程容器),失去了抢cpu的能力,.等待唤醒(锁相当于给当前的线程做了一个标记)
 *
 *notify():让当前的线程从等待状态唤醒,相当于从池子中取出线程.(唤醒的是同一把锁下的任意一个线程)
 *
 *notifyAll():唤醒的是同一把锁下的所有线程
 **/
 
public class Demo7 {
	public static void main(String[] args) {
		//0.创建数据对象
		Des1 des = new Des1();
		//1.创建输入任务对象和输出任务对象
		Input1 input = new Input1(des);
		Output1 output = new Output1(des);
		//2.创建线程
		Thread in = new Thread(input);
		Thread out = new Thread(output);
		//3.开启线程
		in.start();
		out.start();
	}
}

//数据类
class Des1{
	String name;
	String sex;
	boolean flag = false;//用于唤醒和等待之间的切换
}

//输入任务
class Input1 implements Runnable{
	Des1 des;
	public Input1(Des1 des) {
		this.des = des;
	}
	public void run() {
		int i=0;
		while (true) {
			/*
			 * 需要给输入任务和输出任务同时加一把锁,保证两个任务之间是同步的
			 * 给两个任务加一把锁:可以是dies或者Object.class
			 * 分析:
			 * 不建议使用Object.class:由于Object的使用范围太大,可能造成不必要的错误.
			 * 使用des最合适,因为他只被当前的两个任务共享.
			 * 
			 *注意:对于当前的情况只给一个线程加锁,无法实现两个线程的同步.
			 */
			synchronized (des) {
				if (des.flag == true) {//如果是true,让当前的线程处于等待状态
					try {
						//wait必须在同步环境中使用,wait必须使用锁来调用.
						des.wait();//当执行这行代码的时候,这里对应的是哪个线程,就操作的是哪个线程
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
				if (i==0) {
					des.name = "万表哥";
					des.sex = "男";
				}else {
					des.name = "蔡表妹";
					des.sex = "女";
				}
				
				i=(i+1)%2;
				
				des.flag = ! des.flag;
				des.notify();//唤醒的是同一把锁下的线程,因为现在只有一个输入线程,一个输出线程.所以这里唤醒的是输出线程
				//当线程池中没有被当前的锁标记的线程可唤醒时,我们成为空唤醒,空唤醒不影响程序的执行.
			}
		}
	}
}
//输出任务
class Output1 implements Runnable{
	Des1 des;
	public Output1(Des1 des) {
		this.des = des;
	}
	public void run() {
		while (true) {
			
			synchronized (des) {
				if (des.flag == false) {//让输出线程等待
					try {
						des.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println("姓名:"+des.name+"   性别:"+des.sex);
				
				des.flag = ! des.flag;
				des.notify();//唤醒输入线程
			}
		}
	}
}
  •  打印机问题的优化

   优化:

    面向对象的精髓:谁的活谁干,不是你的不要干,将数据准备的工作重输入输出任务提出来,放入数据类

  •    代码

    

package com.qf.test;
/*
 * 两个线程的通信
 * 
 * 优化
 * 面向对象的精髓:谁的活儿谁干,不是你的活儿不要干
 * 将数据准备的活儿从输入任务输出任务提出来,放入数据类
 * 
 * 
 * 实例:打印机打印----一次输入一次输出
 * 两个线程:输入线程和输出线程
 * 两个任务区:输入任务,输出任务
 * 一份数据
 * 
 * 
 *使用唤醒等待机制---notify()/notifyAll()/wait()
 *wait():让当前的线程变成等待的状态,放入一个池子(线程池),失去了抢cpu的能力,.等待唤醒(锁相当于给当前的线程做了一个标记)
 *
 *notify():让当前的线程从等待状态唤醒,相当于从池子中取出线程.(唤醒的是同一把锁下的任意一个线程)
 *
 *notifyAll():唤醒的是同一把锁下的所有线程
 
 **/
public class Demo8 {
	public static void main(String[] args) {
		//两个线程:输入线程和输出线程
		//1.准备数据
		Des2 des = new Des2();
		//2.准备两个任务
		Input2 input = new Input2(des);
		Output2 output = new Output2(des);
		//3.准备两个线程
		Thread in = new Thread(input);
		Thread out = new Thread(output);
		//4.开启线程
		in.start();
		out.start();
	}
}
//创建数据类
class Des2{
	String name;
	String sex;
	boolean flag;//控制唤醒和等待状态的切换
	
	//负责输入
	public void setData(String name,String sex) {
		if (flag == true) {//当flag值为true,就让当前的线程处于等待状态
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}//当执行这行代码的时候,这里对应的是哪个线程,就操作的是哪个线程
		}
		
		this.name = name;
		this.sex = sex;
		
		flag = !flag;
		
		notify();//唤醒的是通一把锁下的线程,因为现在只有一个输入线程,一个输出线程.所以这里唤醒的是输出线程
		//当线程池中没有被当前的锁标记的线程可唤醒时,我们成为空唤醒,空唤醒不影响程序的执行.
	}
	//负责输出
	public void getData() {
		if (flag == false) {//让输出线程等待
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("姓名:"+name+"    性别:"+sex);
		
		flag = ! flag;
		
		notify();//唤醒的是输入线程
	}
}

//两个任务区:输入任务,输出任务
class Input2 implements Runnable{
	Des2 des = null;
	public Input2(Des2 des) {
		super();
		this.des = des;
	}
	public void run() {
		int i=0;
		while (true) {
			/*
			 * 需要给输入任务和输出任务同时加一把锁,保证两个任务之间是同步的
			 * 给两个任务加一把锁:可以是des或者Object.class
			 * 分析:
			 * 不建议使用Object.class:由于Object的使用范围太大,可能造成不必要的错误.
			 * 使用des最合适,因为他只被当前的两个任务共享.
			 * 
			 *注意:对于当前的情况只给一个线程加锁,无法实现两个线程的同步.
			 */
			
			synchronized (des) {
				
				if (i == 0) {
					des.setData("万表哥", "男");
				}else {
					des.setData("蔡表妹", "女");
				}
				
				i=(i+1)%2;
				
			}
		}
	}
}

class Output2 implements Runnable{
	Des2 des = null;
	public Output2(Des2 des) {
		super();
		this.des = des;
	}
	public void run() {
		while (true) {
			synchronized (des) {
				des.getData();
			}
		}
	}
}

  •  单生产者单消费者

  •    代码 
package com.qf.test;
/* 生产者消费者:
* 单生产者单消费者-----会
* 多生产者多消费者-----了解
* 
* 先学习单生产者单消费者
* 需要的线程:两个---一个生产线程一个消费线程
* 需要的任务:两个---一个生产任务一个消费任务
* 需要数据:一份---产品
*/
public class Demo9 {
	public static void main(String[] args) {
		//准备数据
		Product product = new Product();
		//准备任务
		Producer producer = new Producer(product);
		Consumer consumer = new Consumer(product);
		//准备线程
		Thread proThread = new Thread(producer);
		Thread conThread = new Thread(consumer);
		//开启线程
		proThread.start();
		conThread.start();	
	}
}

//创建产品
class Product{
	String name;//产品的名字
	double price;//产品的价格
	int count;//生产的产品数量
	
	//标识
	boolean flag = false;
	
	//准备生产
	public synchronized void setProduce(String name,double price){
		if (flag == true) {
			try {
				wait();//让生产线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		this.name = name;
		this.price = price;
		System.out.println(Thread.currentThread().getName()+"   生产了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
		
		count++;
		flag = ! flag;
		notify();//唤醒消费线程
	}
	//准备消费
	public  synchronized void getConsume() {
		if (flag == false) {
			try {
				wait();//让消费线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"   消费了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
		//唤醒生产线程
		flag = ! flag;
		notify();
	}
}
//创建生产任务
class Producer implements Runnable{
	Product product;
	public Producer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.setProduce("bingbing", 10);
		}
		
	}
}
//创建消费任务
class Consumer implements Runnable{
	Product product;
	public Consumer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.getConsume();
		}
	}
}

  •  多生产者多消费者

         学习多生产者多消费者
         需要的线程:四个---两个生产线程两个消费线程
         需要的任务:两个---一个生产任务一个消费任务
         需要数据:一份---产品
         生产任务与消费任务共用一个数据--产品类

      要求:最终也要实现一次生产一次消费
      错误描述 : 当 有两个生产线程,两个消费线程同时存在的时候,有可能出现生产一次,消费多次或者生产多次消费一次的情况.
      原因 : 当线程被重新唤醒之后,没有判断标记,直接执行了下面的代码

      解决办法:将标记处的if改为while

      问题描述: 继续运行程序,会出现死锁的情况(4个线程同时处于等待状态)
      原因 : 唤醒的是本方的线程,最后导致所有的线程都处于等待状态.

      解决办法: 将notify改为notifyAll,保证将对方的线程唤醒

  •  死锁:出现的情况有两种

        1.所有的线程处于等待状态
        2.锁之间进行嵌套调用

  •  代码
package com.qf.test;

public class Demo10 {
	public static void main(String[] args) {
		//准备数据
		Product1 product = new Product1();
		//准备任务
		Producer1 producer = new Producer1(product);
		Consumer1 consumer = new Consumer1(product);
		//准备线程
		Thread proThread1 = new Thread(producer);
		Thread proThread2 = new Thread(producer);
		Thread conThread1 = new Thread(consumer);
		Thread conThread2 = new Thread(consumer);
		//开启线程
		proThread1.start();
		conThread1.start();	
		proThread2.start();
		conThread2.start();	
	}
}

//创建产品
class Product1{
	String name;//产品的名字
	double price;//产品的价格
	int count;//生产的产品数量
	
	//标识
	boolean flag = false;
	
	//准备生产
	public synchronized void setProduce(String name,double price){
		while (flag == true) {
			try {
				wait();//让生产线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		this.name = name;
		this.price = price;
		System.out.println(Thread.currentThread().getName()+"   生产了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
		
		count++;
		flag = ! flag;
		//notify();//唤醒消费线程
		notifyAll();
	}
	//准备消费
	public  synchronized void getConsume() {
		while (flag == false) {
			try {
				wait();//让消费线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"   消费了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
		//唤醒生产线程
		flag = ! flag;
		//notify();
		notifyAll();
	}
}
//创建生产任务
class Producer1 implements Runnable{
	Product1 product;
	public Producer1(Product1 product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.setProduce("bingbing", 10);
		}
		
	}
}
//创建消费任务
class Consumer1 implements Runnable{
	Product1 product;
	public Consumer1(Product1 product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.getConsume();
		}
	}
}

  •  Lock锁

  •     比较synchronized和lock

    1.synchronized:从jdk1.0就开始使用的同步方法-称为隐式同步
            synchronized(锁对象){//获取锁      我们将锁还可以称为锁旗舰或者监听器
                  同步的代码
             }//释放锁
    2.Lock:从jdk1.5开始使用的同步方法-称为显示同步

         原理:Lock本身是接口,要通过他的子类创建对象干活

              使用过程:

              首先调用lock()方法获取锁

                    进行同步的代码块

              使用unlock()方法释放锁

         使用场景:当进行多生产者多消费者的功能时,使用Lock,其他的都使用synchronized

         使用效率:Lock高于synchronized

         类似获得lock的迭代器:

          //创建锁对象
          Lock lock = new ReentrantLock();
          //用于生产任务的Condition
         Condition proCon = lock.newCondition();
         //用于消费任务的Condition
         Condition conCon = lock.newCondition(); 

       注意:使用Lock获得锁一定记得释放锁

        lock.lock()

        finally{  lock.unlock()  }

        使用lock时等待和唤醒线程用Lock的迭代器调用方法  等待:await()  唤醒:signal()

      Java-day08-线程_第10张图片

  • 代码

   

package com.qf.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo11 {
	public static void main(String[] args) {
		//准备数据
		Product2 product = new Product2();
		//准备任务
		Producer2 producer = new Producer2(product);
		Consumer2 consumer = new Consumer2(product);
		//准备线程
		Thread proThread1 = new Thread(producer);
		Thread proThread2 = new Thread(producer);
		Thread conThread1 = new Thread(consumer);
		Thread conThread2 = new Thread(consumer);
		//开启线程
		proThread1.start();
		conThread1.start();	
		proThread2.start();
		conThread2.start();	
	}
}

//创建产品
class Product2{
	String name;//产品的名字
	double price;//产品的价格
	int count;//生产的产品数量
	
	//标识
	boolean flag = false;
	
	//创建锁对象
	Lock lock = new ReentrantLock();
	//用于生产任务的Condition
	Condition proCon = lock.newCondition();
	//用于消费任务的Condition
	Condition conCon = lock.newCondition();
	
	//准备生产
	public  void setProduce(String name,double price){
		try {
			lock.lock();//获取锁
			while (flag == true) {
				try {
					//wait();//让生产线程等待
					proCon.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			this.name = name;
			this.price = price;
			System.out.println(Thread.currentThread().getName()+"   生产了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
			
			count++;
			flag = ! flag;
			//notify();//唤醒消费线程
			//notifyAll();
			conCon.signal();
		}finally {
			lock.unlock();//释放锁
		}
		
	}
	//准备消费
	public   void getConsume() {
		try {
			lock.lock();
			while (flag == false) {
				try {
					//wait();//让消费线程等待
					conCon.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"   消费了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
			//唤醒生产线程
			flag = ! flag;
			//notify();
			//notifyAll();
			proCon.signal();
		}finally {
			lock.unlock();
		}
	}
}
//创建生产任务
class Producer2 implements Runnable{
	Product2 product;
	public Producer2(Product2 product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.setProduce("bingbing", 10);
		}
		
	}
}
//创建消费任务
class Consumer2 implements Runnable{
	Product2 product;
	public Consumer2(Product2 product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.getConsume();
		}
	}
}

  •    解决懒汉式单例线程不安全问题
//懒汉式,
class SingleInstance2{
	private static  SingleInstance2 s = null;
	private SingleInstance2() {
	}
	public  static SingleInstance2 getInstance() {
		if (s == null) {//尽量减少线程安全代码的判断次数,提高效率
			
			synchronized (SingleInstance2.class) {//使用同步代码块儿实现了线程安全
				if (s == null) {
					s = new  SingleInstance2();
				}
			}
		}
		return s;
	}
}
  •  线程的停止

  •     一共三种方法

      1.通过一个标识结束线程
      2.调用stop方法---因为有固有的安全问题,所以系统不建议使用.
      3.调用interrupt方法----如果目标线程等待很长时间(例如基于一个条件变量),则应使用 interrupt 方法来中断该等待。

  •  代码
//public class Demo14 {
//	public static void main(String[] args) {
//		MyTest myTest = new MyTest();
//		Thread thread  = new Thread(myTest);
//		thread.start();
//		
//		try {
//			Thread.sleep(100);
//		} catch (InterruptedException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//		
//		int i =0;
//		while (true) {
//			if (++i == 10) {//当i==10的时候,我就让子线程结束,让flag=false
//				myTest.flag = false;
//				
//				break;//主线程结束
//			}
//		}
//	}
//}
//
//class MyTest implements Runnable{
//	boolean flag = true;
//	public void run() {
//		while (flag) {
//			System.out.println(Thread.currentThread().getName()+"   明天吃西瓜");
//		}	
//	}
//}

//3.调用interrupt方法----如果目标线程等待很长时间(例如基于一个条件变量),则应使用 interrupt 方法来中断该等待。
public class Demo14 {
	public static void main(String[] args) {
		MyTest myTest = new MyTest();
		Thread thread  = new Thread(myTest);
		thread.start();
		
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		int i =0;
		while (true) {
			if (++i == 10) {//当i==10的时候,我就让子线程结束,直接调用interrupt方法
				thread.interrupt();
				
				break;//主线程结束
			}
		}
	}
}

class MyTest implements Runnable{
	boolean flag = true;
	public synchronized void run() {
		while (flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				flag = false;
			}
			System.out.println(Thread.currentThread().getName()+"   明天吃西瓜");
		}	
	}
}
  • 特殊线程的介绍和一些方法

  •     守护线程: 相当于后台线程.依赖于前台线程.正常情况下,当前台线程结束的时候,不管守护线程有没有结束,都会立刻结束.  典型的守护线程:垃圾回收线程

           调用线程对象的setDaemon方法,参数设为true,则当前线程就变为守护线程,这个方法要在线程的start方法之前调用

  • 代码
package com.qf.test;
/*
 * 守护线程:相当于后台线程.依赖于前台线程.正常情况下,当前台线程结束的时候,不管守护线程有没有结束,都会立刻结束.
 * 典型的守护线程:垃圾回收线程
 *
 */
public class Demo15 {
	public static void main(String[] args) {
		MyTest1 myTest1 = new MyTest1();
		Thread t0 = new Thread(myTest1);
		/*
		 * 当程序调用setDaemon方法时,并且将参数设置成true.当前线程就变成了守护线层.
		 * 注意:这个方法一定要在start方法之前调用
		 */
		t0.setDaemon(true);
		t0.start();
		
		
		int i=0;
		while (true) {
			if (++i == 10) {
				System.out.println(Thread.currentThread().getName()+"   主线程");
				break;
			}
		}
	}
}

class MyTest1 implements Runnable{
	public void run() {
		while (true) {
			System.out.println(Thread.currentThread().getName()+"   子线程");
		}
	}
}

  •  join()方法

        解释:该方法允许一个线程在另一个线程上等待,直到其执行完毕后再继续运行,这样做可以协调线程间的“同步”运行

        同步运行:代码执行有先后顺序(单线程运行是同步的。多线程也可以进行同步操作)

        异步运行:代码各执行各的(多线程下运行代码是异步的)

        注意点:join方法要在线程开启工作后执行

  •   join代码
package thread;

public class JoinDemo {
	//标识图片是否下载完毕的状态
	public static boolean isFinish;
	public static void main(String[] args) {
		Thread download = new Thread() {
			public void run() {
				System.out.println("down:开始下载图片");
				for(int i=1;i<=100;i++) {
					System.out.println("down:"+i+"%");
					try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
				}
			}
			System.out.println("down:图片下载完毕");
			isFinish=true;
		}
	};
	Thread show = new Thread() {
		public void run() {
			System.out.println("show:开始显示图片");
			//等待下载线程将图片下载完毕
			try {
				/*
				 * show线程在调用download.join()方法后
				 * 就进入了阻塞状态,直到download运行完毕
				 * 才会解除阻塞
				 */
				download.join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			if(!isFinish) {
				throw new RuntimeException("图片加载失败");
			}
			System.out.println("show:图片显示完毕!");
		}
	};
	download.start();
	show.start();
}
}
  •  查看线程信息的一些方法
public static void main(String[] args) {
		Thread main = Thread.currentThread();  //获取运行这个方法的线程
		long id = main.getId();
		System.out.println("id:"+id);//线程的ID
		
		String name = main.getName(); //线程的名字
		System.out.println("name:"+name);
		
		int priority = main.getPriority();
		System.out.println("priority:"+priority);//线程的优先级
		
		System.out.println("isAlive:"+main.isAlive());
		System.out.println("isDaemon:"+main.isDaemon());
		System.out.println("isInterrupted:"+main.isInterrupted());
	}
  •  线程的优先级

      解释:线程调用start方法后纳入线程调度统一管理,线程不能主动获取cpu时间片,只能被动分配,调整线程优先级可以最大程度的改善某个线程获取cpu时间片的几率,理论上线程优先级越高的线程获取cpu时间片的次数越多

       线程优先级有十个等级,用整数1-10表示   1最低  5默认  10最高

       void setPriority(int p)  通过该方法设置线程的优先级,线程也提供了三个常量分别表示最低,默认,最高优先级

      

  •  睡眠阻塞

     static void sleep(long ms)
          当一个线程执行sleep方法后就会进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次并发运行,该方法会要求必须处理InterruptedException,即:当一个线程处于睡眠阻塞时被其他线程调用interrupted方法中断时会抛出该中断异常并打断睡眠阻塞
    interrupt()方法是线程的一个方法,用于中断线程,但是若线程处于阻塞状态时是中断阻塞,若线程没有处于阻塞状态则线程直接被中断

  •      代码

    

public class SleepBlockDemo {
	public static void main(String[] args) {
		Thread lin = new Thread() {
			public void run() {
				System.out.println("睡一觉");
				try {
					Thread.sleep(100000);
				} catch (InterruptedException e) {
					System.out.println("lin:干嘛呢");
				}
			}
		};
		Thread huang = new Thread() {
			public void run() {
				System.out.println("黄:开始了");
				for(int i=0;i<5;i++) {
					System.out.println("huang:80");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println("搞定");
				/*
				 * 在JDK1.8之前,由于JVM内存分配问题,要求
				 * 一个方法中的局部内部类若引用这个方法的
				 * 其他局部变量时,要求该变量必须是final的
				 * 所以main方法的局部内部类huang中引用了
				 * main方法的局部变量lin,那么lin必须是final的
				 */
				lin.interrupt();//中断lin线程
			}
		};
		lin.start();
		huang.start();
	}
  •  切换线程的方法

      static void yield()

      Thread.yield();  //主动要求线程切换

  •  线程池

  •    作用:

                    1.管理线程数量:由于每个线程都会占用一定的内存,线程数量过多会导致内存占用大,还有一个问题就是cpu过度切换,导致程序出现“卡顿”

                     2.重用线程

  •  原理:

         线程池能有效的处理多个线程的并发问题,避免大量的线程因为互相强占系统资源导致阻塞现象,能够有效的降低频繁创建和销毁线程对性能所带来的开销。真正线程池的实现是通过ThreadPoolExecutor,ThreadPoolExecutor通过配置不同的参数配置来创建线程池。 

  •  线程池的工作原理

    Java-day08-线程_第11张图片

 从图可以看出,线程池执行所提交的任务过程主要有这样几个阶段:

  1.先判断线程池中核心线程池所有的线程是否都在执行任务。如果不是,则新创建一个线程执行刚提交的任务,否则,核心线程池中所有的线程都在执行任务,则进入第二步。

  2.判断当前阻塞队列是否已满,如果未满,则将提交的任务放置在阻塞阻塞队列中;否则,则进入第3步;

  3.判断线程池中所有的线程是否都在执行任务,如果没有,则创建一个新的线程任务来执行,否则,则交给饱和策略进行处理;

 

  

 

  

  •  线程池的优点:

          1.线程池的重用

             线程的创建和销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。

         2. 控制线程池的并发数

回到线程池,控制线程池的并发数可以有效的避免大量的线程池争夺CPU资源而造成堵塞。

         3. 线程池可以对线程进行管理

 线程池可以提供定时、定期、单线程、并发数控制等功能。比如通过ScheduledThreadPool线程池来执行S秒后,每隔N秒执行一次的任务

  • 线程池的创建

     创建线程池主要是ThreadPoolExecutor类来完成,ThreadPoolExecutor有许多重构的构造方法,通过参数最多的构造方法来理解创建线程池有哪些需要配置的参数。ThreadPoolExecutor的构造方法为:

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

  构造方法中参数的说明:

  1:corePoolSize : 表示核心线程池的大小。当提交一个任务时,如果当前核心线程池的线程个数没有达到corePoolSize,则会创建新的线程来执行所提交的任务,即使当前核心线程池有空闲的线程。如果当前核心线程池的线程个数已经达到了corePoolSize,则不再重新创建线程。如果调用了prestartCoreThread()或者 prestartAllCoreThreads(),线程池创建的时候所有的核心线程都会被创建并且启动。

  2 :maximumPoolSize:表示线程池能创建线程的最大个数。如果当阻塞队列已满时,并且当前线程池线程个数没有超过maximumPoolSize的话,就会创建新的线程来执行任务。

  3 :keepAliveTime:空闲线程存活时间。如果当前线程池的线程个数已经超过了corePoolSize,并且线程空闲时间超过了keepAliveTime的话,就会将这些空闲线程销毁,这样可以尽可能降低系统资源消耗。

  4 : unit:时间单位。为keepAliveTime指定时间单位。

  5 :   workQueue:阻塞队列。用于保存任务的阻塞队列,可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue

  6 :  threadFactory:创建线程的工程类。可以通过指定线程工厂为每个创建出来的线程设置更有意义的名字,如果出现并发问题,也方便查找问题原因。

  7 : handler:饱和策略。当线程池的阻塞队列已满和指定的线程都已经开启,说明当前线程池已经处于饱和状态了,那么就需要采用一种策略来处理这种情况。采用的策略有这几种:

          (1) AbortPolicy: 直接拒绝所提交的任务,并抛出RejectedExecutionException异常; 

          (2) CallerRunsPolicy:只用调用者所在的线程来执行任务;

          (3) DiscardPolicy:不处理直接丢弃掉任务;

          (4) DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务,执行当前任务

 

  • 常用方法:

       void  execute(Runnable commond)//将任务指派给线程池

      shutdown()    //调用后,线程池不再接受新任务,会将已有任务全部执行后停止

      shutdownNow()   //调用后,线程池会调用所有线程的中断方法后立即停止

  •  如何合理配置线程池参数

    想要合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:

        1.任务的性质:cpu密集型任务,IO密集型任务和混合型任务 

        2.任务的优先级:高、中和低

        3.任务的执行时间:长、中和短

        4.任务的依赖性:是否依赖其他系统资源,如数据库连接

任务性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池。IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2xNcpu。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。

依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。

并且,阻塞队列最好是使用有界队列,如果采用无界队列的话,一旦任务积压在阻塞队列中的话就会占用过多的内存资源,甚至会使得系统崩溃。

  • 代码
package thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class ThreadPoolDemo {
	public static void main(String[] args) {
		ExecutorService threadPool = Executors.newFixedThreadPool(2);
		for(int i=0;i<5;i++) {
			Runnable runn = new Runnable() {
				public void run(){
					Thread t = Thread.currentThread();
					System.out.println(t.getName()+":正在运行");
					try {
						Thread.sleep(5000);
					}catch(InterruptedException e) {
						System.out.println("线程被中断");
					}
					System.out.println(t.getName()+":运行任务完毕");
				}
			};
			//将任务指派给线程池
			threadPool.execute(runn);
			System.out.println("将任务指派给了线程池");
		}
		threadPool.shutdownNow();
		System.out.println("线程池停了");
	}
}
  • 线程的五种状态

   Java-day08-线程_第12张图片

  

 

 

你可能感兴趣的:(Java,Java-day08)