19、多线程

1、进程与线程

1. 什么是进程

  • 进程是指在系统中正在运行的一个应用程序,是代码在数据集合上的一次运行活动,是系统进行资源分配和cpu调度的基本单位;它是动态。它是资源分配及调度基本单位

2. 什么是线程

  • 线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。

  • 线程是进程中的一个实体

  • 举例

    • 进程类比成工厂
    • 线程就类比成工厂里面的员工
    • main方法里面的线程为主线程

3.进程与线程的关系

  • 一个进程至少包括一个线程(主线程),通常将该线程称为主线程,没有线程此进程也无法运行。一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。线程属于进程的实体,也就是说明线程是必须依赖于进程。

4、如何创建线程

写法一:编写一个类,此类要继承Thread类

  • 编写一个类,此类要继承Thread类

  • 重写父类(Thread类)的run方法

  • 调用start方法,启动此线程

  • 举例实例


class MyThread extends Thread{  //编写一个类,此类要继承Thread类
	/*
	 * 实现线程的执行主体
	 * */
	@Override
	public void run() {  //重写父类(Thread类)的run方法
	
		//获取当前线程类对象
		Thread t1=Thread.currentThread();
		//输出当前线程的ID和线程名
		System.out.println("输出线程ID="+t1.getId()+"  线程名="+t1.getName());
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		//获取主线程对象
		Thread mainThread=Thread.currentThread();
		//输出当前主线程的ID和线程名
		System.out.println("输出线程ID="+mainThread.getId()+"  线程名="+mainThread.getName());
		
		MyThread t1=new MyThread();
		//它只不过调用了一个普通的run方法,并没有创建线程
		t1.run();
		new MyThread().run();
		/*//创建线程
		MyThread t1=new MyThread();
		//启动线程,start方法只能调用一次
		t1.start();
		
		//再次创建子线程
		new MyThread().start();
		//再次创建子线程
		new MyThread().start();
		//再次创建子线程
		new MyThread().start();*/
	}
}

写法二:编写一个类,此类要实现Runnable接口

  • 编写一个类,此类要实现Runnable接口

  • 重写run方法

  • 创建Thread类的对象,将实现类的对象作为Thread类构造器参数

  • 调用start方法

  • 举例

package 创建线程写法二;

class MyRunnable implements Runnable
{
	/*
	 * 子线程执行主体
	 * */
	@Override
	public void run() {

		System.out.println("子线程名字="+Thread.currentThread().getName()+"  子线程ID="+Thread.currentThread().getId());
		
	}
}


public class MainTest {

	public static void main(String[] args) {
		
		System.out.println("主线程名字="+Thread.currentThread().getName()+"  主线程ID="+Thread.currentThread().getId());
		
		//创建实现类对象
		MyRunnable r=new MyRunnable();
		//创建Thread类对象
		Thread t1=new Thread(r);
		//启动子线程
		t1.start();
		
		
		//局部内部类的写法:		//不常用
		class LocalMyRunnable implements Runnable
		{
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("子线程名字="+Thread.currentThread().getName()+"  子线程ID="+Thread.currentThread().getId());
			}
		}
		
		LocalMyRunnable localR=new LocalMyRunnable();
		new Thread(localR).start();
		
		
		
		//通过匿名内部类,创建子线程
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println("子线程名字="+Thread.currentThread().getName()+"  子线程ID="+Thread.currentThread().getId());
			}
		}).start();
		
		//lambda表达式创建子线程
		new Thread(()->System.out.println("子线程名字="+Thread.currentThread().getName()+"  子线程ID="+Thread.currentThread().getId())).start();
		
	}
}

写法三:通过Callable接口实现线程

package 创建线程写法三;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<Long>
{
	@Override
	public Long call() throws Exception {
		
		System.out.println("子线程 id="+Thread.currentThread().getId()+"  子线程名="+Thread.currentThread().getName());
		
		return Thread.currentThread().getId();
	}
	
}

public class MainTest {

	public static void main(String[] args) {
		
		System.out.println("主线程 id="+Thread.currentThread().getId()+"  主线程名="+Thread.currentThread().getName());

		//创建Callable对象
		MyCallable t1=new MyCallable();
		//创建FutureTask对象
		FutureTask<Long> task=new FutureTask<>(t1);
		
		//使用lambda表达式实现
		FutureTask<Long> task2=new FutureTask<>(()->{
			System.out.println("lambda 子线程 id="+Thread.currentThread().getId()+"  子线程名="+Thread.currentThread().getName());
			return Thread.currentThread().getId();
		}) ;
		
		//创建子线程
		new Thread(task2).start();
		
		try {
			Long v=task.get();
			System.out.println(v);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

写法一与写法二区别

  • 实现Runnable接口方式创建线程,能够实现多线程共享Runnable实现类对象
    在这里插入图片描述

5、线程的生命周期

  • 从线程的创建到销毁的过程

1. 线程的生命周期状态图

在这里插入图片描述
有以下的状态

  • 新建态

    • 当创建线程类对象,它就会处于新建态
  • 就绪态

    • 当线程类对象调用start方法,线程就会从“新建态”—>“就绪态”
  • 运行态

    • 根据cpu调度机制算法,选中处于就绪态的线程执行
  • 阻塞态

    • 使到线程处于阻塞状态
  • 死亡态

    • 当线程的执行体(run方法/call方法),执行完成,则此线程处于死亡态

2.使线程处于阻塞状态(例子)

1. 调用Thread.sleep方法
//创建一个线程类
class MyThread extends Thread
{
	//重写
	@Override
	public void run() {

		System.out.println("my thread start run");
		//从运行态-----》阻塞
		
		//使线程睡眠 1 s,期间处于阻塞态
		try {
			Thread.sleep(1000);//
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
//唤醒的条件:时间执行完1秒,则
//此线程从阻塞态----》就绪态--->cpu调度算法选中此线程----->运行态

		//当run方法执行完成,从运行态----》死亡态
		System.out.println("my thread end run");
		
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		//处于“新建”态
		MyThread t1=new MyThread();
		//从新建----》就绪-----》运行
		t1.start();	
		//就绪-----》运行。去运行重写的run方法
	}
}

2.等待用户输入(IO流的阻塞)
import java.util.Scanner;

//创建一个线程类
class MyThread2 extends Thread
{
	Scanner sc=new Scanner(System.in);
	
	//重写
	@Override
	public void run() {
		
		System.out.println("请输入内容:");
		
		//等待用户键盘输入内容,此时此线程处于阻塞态,直到从键盘键入回车
		String line=sc.nextLine();
		
		System.out.println("内容信息="+line);	
	}
}

public class MainTest2 {

	public static void main(String[] args) {
			
		MyThread2 t2=new MyThread2();
		t2.start();
	}
}
3.调用join方法
  • 等待join线程结束后,才执行
      如果当前线程被加入join其他线程,则会先执行join入的那个线程,那个线程执行完才会继续执行原来的进程。

  • 同步锁机制

    • 直到获取锁对象,此线程才执行,否则阻塞
package 线程的生命周期.阻塞态;

class Account
{
	//账号名
	private String accountNo;
	//余额
	private double amount;
	
	public Account(String accountNo, double amount) {
		this.accountNo = accountNo;
		this.amount = amount;
	}

	public String getAccountNo() {
		return accountNo;
	}
	
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}

	public double getAmount() {
		return amount;
	}
	
	public void setAmount(double amount) {
		this.amount = amount;
	}
		
}
//创建一个线程类
class MyThread3 extends Thread
{
	private Account account;
	private double money;
	
	public MyThread3(Account account,double money) {
		this.account = account;
		this.money=money;
	}

	//重写
	@Override
	public void run() {
		
		//加锁
		synchronized (account) {
			
			double m=this.account.getAmount()+money;
			
			//睡眠处理
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			account.setAmount(m);
			
			System.out.println("用户="+account.getAccountNo()+"  余额="+account.getAmount());
			
		}	
	}
}

public class MainTest3 {

	public static void main(String[] args) {
		
		//创建账户对象
		Account account=new Account("s001", 1000);
		
		MyThread3 t1=new MyThread3(account,100);
		t1.start();
		
		MyThread3 t2=new MyThread3(account,100);
		t2.start();
		
		MyThread3 t3=new MyThread3(account,100);
		t3.start();
	}
}

4.调用wait方法
  • 使线程处于等待状态,等待唤醒
package 线程的生命周期.阻塞态;

import java.util.Scanner;

//创建一个线程类
class MyThread4 extends Thread
{
	//它设置为共享对象(多个线程所共享)
	private Object obj;
	
	public MyThread4(Object obj)
	{
		this.obj=obj;
	}
	
	//重写
	@Override
	public void run() {
		
		//调用wait方法,使到此线程处于“阻塞”
		synchronized (obj) {
			try {
				System.out.println("此用户进行阻塞状态,等待唤醒");
				obj.wait();
				System.out.println("此子线程已经唤醒。。。。");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class MainTest4 {

	public static void main(String[] args) {
		
		Object o=new Object();
		
		MyThread4 t=new MyThread4(o);
		t.start();
		
		//从键盘输入“yes”,唤醒等待线程
		Scanner sc=new Scanner(System.in);
		String receiveMessage=null;
		
		System.out.println("是否唤醒子线程,如果输入yes,则唤醒子线程,请输入:");
		while((receiveMessage=sc.nextLine())!=null)
		{
			System.out.println("是否唤醒子线程,如果输入yes,则唤醒子线程,请输入:");
			if(receiveMessage.equals("yes"))
			{
				System.out.println("唤醒等待线程");
				//唤醒等待线程
				synchronized (o) {
					o.notifyAll();
				}
			}
		}
	}
}

3.使线程处于死亡状态(例子)

  • 当线程的执行体(run方法/call方法),执行完成,则此线程处于死亡态
package 线程的生命周期.死亡态;

class MyThread extends Thread
{
	//添加 一个终止线程的标记变量
	public volatile boolean isFlag=false;
	
	@Override
	public void run() {
		
		for(int i=0;i<100;i++)
		{
			System.out.println("i="+i);
			
			//睡眠1秒,执行一次
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			if(isFlag)
			{
				break;
			}
		}
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		//创建线程类的对象 
		MyThread t=new MyThread();
		t.start();
		
		try {
			//主线程睡眠5秒
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		t.isFlag=true;
		
		//不要使用stop方法终止线程,有风险
		//t.stop();
	}
}

6、控制线程方法

1.sleep方法

  • 简介

    • sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会
  • 特点

    • 使到线程从运行态----》阻塞态
    • 设置睡眠时间,如果睡眠时间到期,则从阻塞态----》就绪态
    • sleep最好不要作用于同步锁机制,因为此sleep在睡眠期间是不会释放锁,导致其他线程也是执行不了
package sleep用法;

//创建子线程类
class MyThread extends Thread
{
	private Object obj;
	
	public MyThread(Object obj)
	{
		this.obj=obj;
	}
	
	@Override
	public void run() {
		
		/*
		 * 如果sleep作用于同步锁机制,此sleep在睡眠期间是不会释放锁,导致其他线程也是执行不了
		 * */
		synchronized (obj) {
			
			try {
				//睡眠10秒,它是不会释放锁,一直占用此锁对象
				Thread.sleep(1000*10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		synchronized (obj) {
			
			try {
				//将锁释放
				obj.wait(1000*10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class MainTest {

	public static void main(String[] args) {

		Object o=new Object();
		
		MyThread t1=new MyThread(o);
		t1.start();
		
		MyThread t2=new MyThread(o);
		t2.start();
		
		MyThread t3=new MyThread(o);
		t3.start();	
	}
}

(1)例子:龟兔赛跑问题(转载于:学步园)
  • 编写龟兔赛跑多线程程序,设赛跑长度为100米,赛跑过程中,每跑完10米输出一次结果。

由于兔子和乌龟每跑完10米,调用Thread类sleep方法执行线程休眠的时间不确定,有长有短,故每次运行程序,谁胜谁负结果不尽相同。

为了分开两列输出兔子和乌龟线程的运行结果,在run方法内部有关“乌龟跑了XX米”的开头加了两个制表键"\t\t",使得每行文字开头空8个字符。

class Animal extends Thread{//代码转载于:学步园
	public Animal(String name)
	{
		super(name);//这里为啥要用super
	}

public void run()
{
	for(int i=0;i<=100;i+=10)
	{
		if(this.getName().equals("乌龟")){
			System.out.println("\t\t乌龟跑了"+i+"米");
		}
		else
		{
			System.out.println(this.getName()+"跑了"+i+"米");
		}
		try
		{
			Thread.sleep((long)(Math.random()*1000));
		}
		catch(InterruptedException e){}
		
	}
	
}
}
public class Example1 {

	public static void main(String[] args) {
		Animal rabbit=new Animal("兔子");
		Animal tortoise=new Animal("乌龟");
		rabbit.start();
		tortoise.start();
	}
}
  • 由于JAVA的单一继承性,如果要编写的线程类本身需要继承另一个父类,则不能再继承Thread类。这时就要采用实现Runnable接口的
    方法编写与线程运行相关的类(该类不属于线程类,知识相关而已),然后以该类的对象为参数构建Thread类对象。Thread对象就是线程。
(2)采用实现Runnable接口的方法编写龟兔赛跑多线程程序(转载于:学步园)
class Animal2 implements Runnable{//代码转载于:学步园

	private String name;
	
	public Animal2(String name){
		this.name = name;
	}
	
	public String getName(){
		return name;
	}
	
	public void run() {
		for (int i = 0; i <= 100; i+=10) {
			if (this.getName().equals("乌龟")) {
				System.out.println("\t\t"+this.getName()+"跑了"+i+"米");			
			}
			else{
				System.out.println(this.getName()+"跑了"+i+"米");
			}
			try {
				Thread.sleep((long)(Math.random()*1000));
			} catch (Exception e) {
				
			}
		}
	}
}

public class Example2 {
	public static void main(String[] args) {
		
		Thread rabbit = new Thread(new Animal2("兔子"));
		Thread tortoise = new Thread(new Animal2("乌龟"));
		rabbit.start();
		tortoise.start();
	}
}
(3)改进龟兔赛跑多进程程序,通过改变优先级,并减掉休眠时间,使得兔子先跑完100米(转载于:学步园)
class Animal3 extends Thread{//代码转载于:学步园
	public Animal3(String name)
	{
		super(name);
	}
public void run()
{
	for(int i=0;i<=100;i+=10)
	{
		if(this.getName().equals("乌龟")){
			System.out.println("\t\t乌龟跑了"+i+"米");
		}
		else
		{
			System.out.println(this.getName()+"跑了"+i+"米");
		}
	}
}
}
public class Example3 {

	public static void main(String[] args) {
		Animal3 rabbit=new Animal3("兔子");
		//设置兔子最大优先级
		rabbit.setPriority(Thread.MAX_PRIORITY);
		Animal3 tortoise=new Animal3("乌龟");
		//设置兔子最小优先级
		tortoise.setPriority(Thread.MIN_PRIORITY);
		rabbit.start();
		tortoise.start();
	}
}
(4)简单的售票程序,多个窗口同时售票(转载于:学步园)
/**
 * 需求:简单的售票程序,多个窗口同时售票。
 * 
 * 创建线程的第二种方式:实现Runnable接口
 * 
 * 步骤:
 * 1.定义类实现Runnable接口
 * 
 * 2.覆盖Runnable几口中的run方法。
 *  (将线程要运行的代码存放在该run方法中)
 * 
 * 3.通过Thread类建立线程对象。
 * 
 * 4.将Runnable接口的子类对象作为实际参数传递给Thread的构造函数
 *  (为什么要将Runnable接口的子类对象传递给Thread的构造函数
 *    因为:自定义的run方法所属的对象是Runnable接口的子类对象,
 *    所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。)
 * 
 * 5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
 * 
 */

class Ticket implements Runnable{//代码转载于:学步园
	
	//private static int tick = 100;加入静态可以有效控制票只被出售一次
	private int tick = 100;
	
	public void run()
	{
		while(true)
		{
			if(tick>0)
			{
				System.out.println(Thread.currentThread().getName()+"sale:"+tick--);	
			}
		}
	}
}
public class Example5 {
	
	public static void main(String[] args)
	{
		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
	
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

2.join方法

  • Thread提供了让一个线程等待另一个线程完成的方法
package join方法;

/*
 * 定义的线程类
 * */
class MyThread extends Thread
{
	@Override
	public void run() {
		
		for(int i=0;i<10;i++)
		{
			System.out.println("thread id="+Thread.currentThread().getId()+"  i="+i);
			
			//睡眠1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}	
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		System.out.println("主线程的main开始执行");
		//创建及启动子线程
		MyThread t=new MyThread();
		t.start();
		
		try {
			//等待子线程执行完成,才继续执行主线程
			//当前主线程是main线程,join了一个子线程
			t.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("主线程的main结束执行");
	}
}

3.interrupt方法

  • 简介

    • 给受阻塞的线程发出一个中断信号,中断阻塞,这样受阻线程就得以退出阻塞的状态,回到就绪态??
  • 此方法作用于

    • sleep
    • join
    • wait
package interrupt方法;

import java.util.Scanner;

//创建子线程类
class MyThread extends Thread
{
	private Object obj;
	
	public MyThread(Object obj)
	{
		this.obj=obj;
	}
	
	@Override
	public void run() {
		
		/*
		 * 如果sleep作用于同步锁机制,此sleep在睡眠期间是不会释放锁,导致其他线程也是执行不了
		 * */
		synchronized (obj) {
			
			try {
				System.out.println("此子线程睡眠10秒");
				//睡眠10秒,它是不会释放锁,一直占用此锁对象
				Thread.sleep(1000*10);
			} catch (InterruptedException e) {
				System.out.println("我已经收到了中断信号,已经唤醒了");
			}
		}

	}
}

public class MainTest {

	public static void main(String[] args) {

		Object o=new Object();
		
		MyThread t1=new MyThread(o);
		t1.start();
		
		Scanner sc=new Scanner(System.in);
		System.out.println("是否发送中断信号给子线程,中断睡眠?");
		String rec=sc.nextLine();
		
		if(rec.equals("y"))
		{
			//发送一个中断信号 ,给睡眠的子线程,该醒了
			t1.interrupt();
		}
	
		
	}

}

4. yield方法

  • yield方法使到当前线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态,让系统的线程调度器重新调度器重新调度一次
package yield方法;

//创建一个线程类
class MyThread extends Thread
{
	@Override
	public void run() {
		
		for(int i=0;i<100;i++)
		{
			System.out.println("子线程ID="+Thread.currentThread().getId()+"  i="+i);
			if(i==30)
			{
				System.out.println("子线程调用yield方法");
				Thread.yield();
			}
			
		}
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		MyThread t1=new MyThread();
		t1.start();
		
		for(int j=0;j<100;j++)
		{
			if(j==60)
			{
				//当前线程暂停(从运行态---->就绪态)
				System.out.println("主线程调用yield方法");
				Thread.yield();
			}
			System.out.println("主线程ID="+Thread.currentThread().getId()+"  j="+j);
		}
	}
}

7、线程的优先级

  • 每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会(不是百分之百执行),而优先级低的线程则获得较少的机会。
  • 每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,有main线程创建的子线程野具有普通优先级。
  • Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其中setPriority()方法的参数是一个整数,范围在1~10之间
package 线程的优先级;


//创建一个线程类
class MyThread extends Thread
{
	@Override
	public void run() {
		
		for(int i=0;i<100;i++)
		{
			System.out.println("子线程ID="+Thread.currentThread().getId()+"  i="+i);
			if(i==30)
			{
				System.out.println("子线程调用yield方法");
				Thread.yield();
			}
		}
	}
}

public class MainTest {

	public static void main(String[] args) {

		
		MyThread t1=new MyThread();
		//设置线程的优先级
		t1.setPriority(Thread.MIN_PRIORITY);
		t1.start();
		
		for(int j=0;j<100;j++)
		{
			if(j==60)
			{
				//当前线程暂停(从运行态---->就绪态)
				System.out.println("主线程调用yield方法");
				Thread.yield();
			}
			System.out.println("主线程ID="+Thread.currentThread().getId()+"  j="+j);
		}
	}
}

8、后台线程

    • 有一种线程,它在后台运行,它的任务是为其他的线程提供服务,这种线程被称为“后台线程”(Daemon Thread)。
  • 后台线程有个特征

    • 后台线程有个特征,如果所有的前台线程都死亡,后台线程会自动死亡。
  • 设置后台线程方法: setDaemon(true)

package 后台线程;

class MyThread extends Thread
{
	@Override
	public void run() {
		
		for(int i=0;i<10;i++)
		{
			//睡眠1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			System.out.println("thread id="+Thread.currentThread().getId()+" i="+i);
		}
		
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		MyThread t=new MyThread();
		//将子线程设置为后台线程
		t.setDaemon(true);
		//启动线程
		t.start();
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("main thread end ");
	}
}

9、解决同步问题

1.共享资源

  • 什么是共享资源,所谓共享资源是说该资源被多个线程所共享,多个线程都可以去访问或者修改的资源。
    在这里插入图片描述
  • 当多个线程访问共享资源,就很容易出现线程安全问题
  • 解决办法:
        对多条操作共享数据的语句,只能让一个线程都执行完。在某个线程执行过程中,其他线程不可以参与执行。

2.举例描述线程安全问题

场景:银行取款问题

package 线程同步问题;

/*
 * 定义一个Account账号类
 * */
class Account
{
	//账户名 
	private String accountName;
	//余额
	private double balance;
	
	public Account() {//无参构造器,new对象时不传参数
		
	}

	public Account(String accountName, double balance) {//有参构造器,new对象时创建有属性的对象
		this.accountName = accountName;
		this.balance = balance;
	}

	public String getAccountName() {
		return accountName;
	}

	public void setAccountName(String accountName) {
		this.accountName = accountName;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

	@Override
	public String toString() {
		return "Account [accountName=" + accountName + ", balance=" + balance + "]";
	}
	
}

/*
 * 定义一个线程类(取款线程):针对账号进行取款操作
 * */
class DrawThread extends Thread
{
	private Account account;//账户名
	private double money;//取多少钱

	public DrawThread(Account account,double money) {
		this.account = account;
		this.money=money;
	}
	
	@Override
	public void run() {
		
		if(account.getBalance()>=money)//在余额大于等于要取的钱时
		{
			//局部变量(每个线程分配一份m的变量存储)
			double m=account.getBalance()-money;
			
			//睡眠1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			//account的balance属性才是共享变量值
			account.setBalance(m);
			
			System.out.println("账号名="+account.getAccountName()+" 取款金额="+money+"  当前余额="+account.getBalance()+" 线程id="+Thread.currentThread().getId()+"  m="+m);
		}else
		{
			System.out.println("账号名="+account.getAccountName()+"  余额不足,无法正常取款");
		}
	}
	
}

public class MainTest {

	public static void main(String[] args) {
		
		//创建一个用户账号
		Account account=new Account("张三", 1000.00);
		
		DrawThread t1=new DrawThread(account, 800.00);
		DrawThread t2=new DrawThread(account, 200.00);
		t1.start();
		t2.start();
	}
}

3.如何解决线程安全问题

(1)同步代码块实现
  • Java对于多线程的安全问题提供了专业的解决方式:同步代码块

同步:★★★★★

好处:解决了线程安全问题。

弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

定义同步是有前提的:

1,必须要有两个或者两个以上的线程,才需要同步。

2,多个线程必须保证使用的是同一个锁。

/**
			 * 同步代码块,将需要同步的代码放在里面
			 * 
			 * synchronized(对象)
			 * {
			 * 
			 *     需要被同步的代码
			 * }
			 * 
			 * */
			synchronized(obj)
			{
			    if(tick>0)
			    {
				    try{Thread.sleep(10);}catch(Exception e){} //这里!
				    System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
			    }
			}
  • 为了防止多个线程同时访问和修改同一个对象,Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。同步代码块的语法格式如下:
//添加同步代码块
synchronized(同步监听器对象)
{
    ......
    
}

同步监听器对象

  • 条件:必须是对象
  • 条件:属于共享对象
package 线程同步问题;

/*
 * 定义一个Account账号类
 * */
class Account
{
	//账户名 
	private String accountName;
	//余额
	private double balance;
	
	public Account() {
		
	}

	public Account(String accountName, double balance) {
		this.accountName = accountName;
		this.balance = balance;
	}

	public String getAccountName() {
		return accountName;
	}

	public void setAccountName(String accountName) {
		this.accountName = accountName;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

	@Override
	public String toString() {
		return "Account [accountName=" + accountName + ", balance=" + balance + "]";
	}
	
}

/*
 * 定义一个线程类:针对账号进行取款操作
 * */
class DrawThread extends Thread
{
	private Account account;
	private double money;

	public DrawThread(Account account,double money) {
		this.account = account;
		this.money=money;
	}
	
	@Override
	public void run() {
		
		//添加同步代码块
		//mutex:同步监听器
		/*
		 * 同步监听器设置的条件
		 * 1、必须是对象
		 * 2、共享资源
		 * */
		synchronized (account) {
			
			if(account.getBalance()>=money)//添加同步代码块
			{
				//局部变量(每个线程分配一份m的变量存储)
				double m=account.getBalance()-money;
				
				//睡眠1秒
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				//account的balance属性才是共享变量值
				account.setBalance(m);
				
				System.out.println("账号名="+account.getAccountName()+" 取款金额="+money+"  当前余额="+account.getBalance()+" 线程id="+Thread.currentThread().getId()+"  m="+m);
			}else
			{
				System.out.println("账号名="+account.getAccountName()+"  余额不足,无法正常取款");
			}
		}
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		//创建一个用户账号
		Account account=new Account("张三", 1000.00);
		
		DrawThread t1=new DrawThread(account, 800.00);
		DrawThread t2=new DrawThread(account, 200.00);
		t1.start();
		t2.start();
	}
}
(2)同步方法实现
  • Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。对于同步方法而言,无需显式指定同步监视器,同步方法的同步监视器是this,也就是改对象本身。
  • 同步方法:其实就是将同步关键字定义在方法上,让方法具备了同步性。

同步方法是用的哪个锁呢?

通过验证,函数都有自己所属的对象this,所以同步方法所使用的锁就是this锁。

当同步方法被static修饰时,这时的同步用的是哪个锁呢?

静态方法在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。

所以静态加载时,只有一个对象存在,那么静态同步方法就使用的这个对象。

这个对象就是 类名.class

  • 作用域

    • 作用范围:方法体
package 同步方法实现;

/*
 * 定义一个Account账号类
 * */
class Account
{
	//账户名 
	private String accountName;
	//余额
	private double balance;
	
	public Account() {
		
	}

	public Account(String accountName, double balance) {
		this.accountName = accountName;
		this.balance = balance;
	}

	public String getAccountName() {
		return accountName;
	}

	public void setAccountName(String accountName) {
		this.accountName = accountName;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

	@Override
	public String toString() {
		return "Account [accountName=" + accountName + ", balance=" + balance + "]";
	}
	
	/*
	 * 定义一个同步 方法
	 * 默认的同步监听器:this
	 * */
	public synchronized void drawMoney(double money)
	{
		if(this.getBalance()>=money)
		{
			//局部变量(每个线程分配一份m的变量存储)
			double m=this.getBalance()-money;
			
			//睡眠1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			//account的balance属性才是共享变量值
			this.setBalance(m);
			
			System.out.println("账号名="+this.getAccountName()+" 取款金额="+money+"  当前余额="+this.getBalance()+" 线程id="+Thread.currentThread().getId()+"  m="+m);
		}else
		{
			System.out.println("账号名="+this.getAccountName()+"  余额不足,无法正常取款");
		}
	}
	
}


/*
 * 定义一个线程类:针对账号进行取款操作
 * */
class DrawThread extends Thread
{
	private Account account;
	private double money;

	public DrawThread(Account account,double money) {
		this.account = account;
		this.money=money;
	}
	
	@Override
	public void run() {
		account.drawMoney(money);
	}
	
}


public class MainTest {

	public static void main(String[] args) {
		
		//创建一个用户账号
		Account account=new Account("张三", 1000.00);
		
		DrawThread t1=new DrawThread(account, 800.00);
		DrawThread t2=new DrawThread(account, 200.00);
		t1.start();
		t2.start();
	}
}

  • 同步代码块和同步函数的区别?

同步代码块使用的锁可以是任意对象。

同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。

/**
 * 
 * 需求:银行存款
 *       两人存款,每次存100,各存三次。
 * @author cofen
 *
 */

class Bank
{
	//银行总金额
	private int sum;
	Object obj = new Object();
	
	public void add(int n)
	{
		synchronized (obj) {
			sum = sum + n;
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println("sum="+sum);
		}
	}
	
}

class Cus implements Runnable
{
	private Bank b = new Bank();
	
	public void run()
	{
		for(int x=0;x<3;x++){
			b.add(100);
		}
	}
}

public class Example6 {
	
	public static void main(String[] args)
	{
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}

}
使用同步函数达到同样效果

//同步函数
	public synchronized void add(int n)
	{
//		synchronized (obj) {
			sum = sum + n;
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println("sum="+sum);
//		}
	}
(3)显示加锁(调用lock方法)
  • 在实现线程安全的控制中,比较常见的是ReentrantLock(可重现锁)。使用该Lock对象可以显式地加锁、释放锁。
package 显式加锁;

import java.util.concurrent.locks.ReentrantLock;

/*
 * 定义一个Account账号类
 * */
class Account
{
	//账户名 
	private String accountName;
	//余额
	private double balance;
	
	//定义一个可重现锁
	private ReentrantLock lock=new ReentrantLock();
	
	public Account() {
		
	}

	public Account(String accountName, double balance) {
		this.accountName = accountName;
		this.balance = balance;
	}

	public String getAccountName() {
		return accountName;
	}

	public void setAccountName(String accountName) {
		this.accountName = accountName;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

	@Override
	public String toString() {
		return "Account [accountName=" + accountName + ", balance=" + balance + "]";
	}
	
	/*
	 * 实现取款功能
	 * */
	public void drawMoney(double money)
	{
		//加锁
		lock.lock();
		
		if(getBalance()>=money)
		{
			//局部变量(每个线程分配一份m的变量存储)
			double m=getBalance()-money;
			
			//睡眠1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			//account的balance属性才是共享变量值
			setBalance(m);
			
			System.out.println("账号名="+getAccountName()+" 取款金额="+money+"  当前余额="+getBalance()+" 线程id="+Thread.currentThread().getId()+"  m="+m);
		}else
		{
			System.out.println("账号名="+getAccountName()+"  余额不足,无法正常取款");
		}
		
		//解锁
		lock.unlock();
	}
}

/*
 * 定义一个线程类:针对账号进行取款操作
 * */
class DrawThread extends Thread
{
	private Account account;
	private double money;

	public DrawThread(Account account,double money) {
		this.account = account;
		this.money=money;
	}
	
	@Override
	public void run() {
		
		account.drawMoney(money);
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		//创建一个用户账号
		Account account=new Account("张三", 1000.00);
		
		DrawThread t1=new DrawThread(account, 800.00);
		DrawThread t2=new DrawThread(account, 200.00);
		t1.start();
		t2.start();
	}
}

4.死锁问题

  • 当两个线程相互等待对方释放同步监视器就会发生死锁,导致两个线程都会处于“阻塞态”。
package 死锁;

class A
{
	/*
	 * 访问A类的foo方法,首先获取此方法的锁:A类的对象
	 * */
	public synchronized void foo(B b)
	{
		
		System.out.println("进入 A类的foo方法");
		
		try {
			Thread.sleep(2*1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//要获取B类对象的锁
		b.last();
		
	}
	
	
	/*
	 * 访问A类的last方法,首先获取此方法的锁:A类的对象
	 * */
	public synchronized void last()
	{
		System.out.println("A 类的last方法");
	}
}

class B
{
	/*
	 * 访问B类的bar方法,首先获取此方法的锁:B类的对象
	 * */
	public synchronized void bar(A a)
	{
		System.out.println("进入 B类的bar方法");
		
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
        //要获取a类对象的锁
		a.last();
	}
	
	/*
	 * 访问B类的last方法,首先获取此方法的锁:B类的对象
	 * */
	public synchronized void last()
	{
		System.out.println("B 类的last方法");
	}
}

/*
 * 线程类
 * */
class MyThread extends Thread
{
	private A a;
	private B b;
	
	public MyThread(A a,B b)
	{
		this.a=a;
		this.b=b;
	}
	
	/*
	 * 主线程
	 * */
	public void init()
	{
		a.foo(b);
	}
	
	/*
	 * 子线程
	 * */
	@Override
	public void run() {
		
		b.bar(a);
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		//创建A类的对象
		A a=new A();
		//创建B类的对象
		B b=new B();
		//创建线程类对象
		MyThread t=new MyThread(a,b);
		//启动线程
		t.start();
		//init方法属于主线程调用
		t.init();
	
	}

}

10、多线程间通信问题

10.1 简介

什么是多线程通讯问题?

  • 两个线程之间的交互通讯过程

  • 举例

    • 开发一个播放器

    • 播放器主要是播放视频文件

      • 存在两个线程
        • 播放音频
        • 播放视频
    • 由于音频的解码耗时比视频解码耗时要短,则导致音、视频不同步问题,如何解决?

      • 需要采用多线程通讯解决

        • 采用等待、唤醒机制
          • 等待:wait
          • 唤醒:notify
      • 当音频解码完成一帧数据,则处于等待状态,等待直到视频解码一帧数据完成,就被唤醒,被视频线程唤醒
        在这里插入图片描述

10.2 核心方法

  • wait():导致当前线程等待,直到其他线程调用同步监视器的notify()方法来唤醒该线程
  • notify():唤醒在此同步监视器上等待的单个线程。
  • notifyAll():唤醒在此同步监视器上等待的所有线程。
    • 上述方法使用,必须要依赖于同步监视器,也就是说要作用于同步机制

  • 作用于同步代码块的用法
package wait和notify用法;

import java.util.Scanner;

/*
 * 编写一个线程类
 * */
class MyThread extends Thread
{
	private Object obj;

	public MyThread(Object obj)
	{
		this.obj=obj;
	}
	
	@Override
	public void run() {
		
		//添加同步代码块
		synchronized (obj) {
			
			try {
				System.out.println("此子线程处于等待状态");
				//wait必须依赖于同步监听器
				//wait一般只作用于同步方法及同步代码块
				obj.wait();
				System.out.println("子线程结束等待");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class MainTest {

	public static void main(String[] args) {

		//创建共享对象,此共享对象可以作为同步监听器对象
		Object obj=new Object();
		MyThread t=new MyThread(obj);
		//启动线程
		t.start();
		
		System.out.println("是否需要唤醒等待的子线程,请输入?");
		Scanner sc=new Scanner(System.in);
		
		String rec=sc.nextLine();

		if(rec.equals("yes"))
		{
			synchronized (obj) {
				//唤醒子线程
				obj.notify();
			}
		}
	}
}

  • 作用于同步方法的用法
package wait和notify用法二;

import java.util.Scanner;

/*
 * 定义一个共享对象
 * */
class ShareObject
{
	public synchronized void waitThread()
	{
		try {
			//this.wait();
			System.out.println("子线程等待");
			wait();
			System.out.println("子线程唤醒,结束等待状态");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyThread()
	{
		
		//this.nofity();
		System.out.println("唤醒子线程");
		notify();
	}
}

/*
 * 编写一个线程类
 * */
class MyThread extends Thread
{
	private ShareObject obj;

	public MyThread(ShareObject obj)
	{
		this.obj=obj;
	}
	
	@Override
	public void run() {
		
		this.obj.waitThread();
		
	}
}

public class MainTest {

	public static void main(String[] args) {

		//创建共享类对象
		ShareObject shareObject=new ShareObject();
		//创建线程类对象
		MyThread t=new MyThread(shareObject);
		//创建子线程
		t.start();
		
		System.out.println("是否需要唤醒等待的子线程,请输入?");
		Scanner sc=new Scanner(System.in);
		
		String rec=sc.nextLine();

		if(rec.equals("yes"))
		{
			shareObject.notifyThread();//唤醒线程:调用上面自己定义的方法,里面有notify()方法,用于唤醒线程
			
		}
	}
}
  • 唤醒所有等待的子线程
package wait和notify用法二;

import java.util.Scanner;

/*
 * 定义一个共享对象
 * */
class ShareObject
{
	public synchronized void waitThread()
	{
		try {
			//this.wait();
			System.out.println("子线程名="+Thread.currentThread().getName()+"子线程等待");
			wait();
			System.out.println("子线程名="+Thread.currentThread().getName()+"子线程唤醒,结束等待状态");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyThread()
	{
		//this.nofity();
		System.out.println("唤醒子线程");
		notify();
	}
	
	public synchronized void notifyAllThread()
	{
		//this.nofity();
		System.out.println("唤醒所有子线程");
		//唤醒作用于this的同步监听器的所有处于等待状态的子线程
		notifyAll();
	}
}

/*
 * 编写一个线程类
 * */
class MyThread extends Thread
{
	private ShareObject obj;

	public MyThread(ShareObject obj)
	{
		this.obj=obj;
	}
	
	@Override
	public void run() {
		
		this.obj.waitThread();
		
	}
}

public class MainTest {

	public static void main(String[] args) {

		//创建共享类对象
		ShareObject shareObject=new ShareObject();
		//创建线程类对象
		MyThread t=new MyThread(shareObject);
		//创建子线程
		t.start();
		
		//创建线程类对象
		MyThread t2=new MyThread(shareObject);
		//创建子线程
		t2.start();
		
		System.out.println("是否需要唤醒等待的子线程,请输入?");
		Scanner sc=new Scanner(System.in);
		
		String rec=sc.nextLine();

		if(rec.equals("yes"))
		{
			shareObject.notifyAllThread();		
		}
	}
}

10.3实例:银行存取实例

需求

  • 银行存取实例

    • 要实现先存款,再取款的功能

    • 两个线程

      • 一个线程:存款
      • 一个线程:取款
      • 如何体现线程间通讯问题?
        • 先运行存款线程,存款后再通知唤醒取款线程,此存款线程处于等待状态,当取款线程取款完毕就会通知唤醒存款线程,该存款
    • 实现原理

      • 两个线程并行执行

      • 先保证存款线程先存款操作,后取款线程取款操作

思路

flag=true

存款线程:
    if(!flag)
    {
       存款处于等待,直到被取款线程唤醒
           
    }else
    {
       操作余额=余额+money
       flag=false;
       唤醒取款线程,你该取款
    }

取款线程:
    if(flag)
    {
        取款线程处于等待,直到被存款线程唤醒
    }else
    {
        操作余额=余额-money
	    flag=true;
        唤醒存款线程,你该存款
    }
    

实例

package 多线程通讯问题;

/*
 * 定义一个账号类
 * */
class Account {
	private String name;
	private double amount;
	private boolean flag=true;

	public Account() {

	}

	public Account(String name, double amount) {
		this.name = name;
		this.amount = amount;
	}

	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}


	public double getAmount() {
		return amount;
	}
	public void setAmount(double amount) {
		this.amount = amount;
	}

	@Override
	public String toString() {
		return "Account [name=" + name + ", amount=" + amount + "]";
	}
	
	/*
	 * 定义取款的方法
	 * */
	public synchronized void draw(double money)
	{
		if(flag)
		{
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {
			setAmount(getAmount() - money);
			System.out.println("取款成功 当前余额=" + getAmount() + "  取款=" + money);
			flag=true;
			//唤醒存款线程,你该存款
			notify();
		}
	}
	
	/*
	 * 定义存款的方法
	 * */
	public synchronized void doposit(double money)
	{
		//flag默认等于true
		if(!flag)
		{
			//存款处于等待,直到被取款线程唤醒
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}else
		{
			setAmount(getAmount() + money);
			System.out.println("存款成功 当前余额=" + getAmount() + "  存款=" + money);
			flag=false;
			//唤醒取款的线程,你该取款
			notify();
		}
	}
}

/*
 * 取款线程
 */
class DrawThread extends Thread {
	private Account account;
	private double money;

	public DrawThread(Account account, double money) {
		this.account = account;
		this.money=money;
	}

	@Override
	public void run() {

		for(int i=0;i<11;i++)
		{
			this.account.draw(money);
		}
	}
}

/*
 * 存款线程
 */
class Dosipoit extends Thread {
	private Account account;
	private double money;

	public Dosipoit(Account account, double money) {
		this.account = account;
		this.money=money;
	}

	@Override
	public void run() {
		
		for(int i=0;i<11;i++)
		{
			this.account.doposit(money);
		}
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		Account account=new Account("张三", 0);
		Dosipoit d=new Dosipoit(account,100);
		DrawThread f=new DrawThread(account, 100);
		d.start();
		f.start();
	}
}

11、Condition控制线程通信

11.1 简介

  • 通过Condition实现线程间的通信,主要作用于显式加锁(上面是用于同步代码块和同步方法,区分好)

11.2 核心方法

  • 如何获取Condition对象

    • Lock对象调用newCondition方法
  • Condition对象的方法

    • await()
      • 等同于wait方法,导致线程阻塞
    • signal()
      • 类似于notify
    • signalAll()
      • 类似于notifyAll()

11.3 实例

package Condition用法;

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

/*
 * 定义一个账号类
 * */
class Account {
	private String name;
	private double amount;
	private boolean flag=true;
	
	//定义一个可重现锁对象
	ReentrantLock lock;
	//定义一个Condition对象
	Condition condition;

	public Account() {
		lock=new ReentrantLock();
		condition=lock.newCondition();
		
	}

	public Account(String name, double amount) {
		this.name = name;
		this.amount = amount;
		
		lock=new ReentrantLock();
		condition=lock.newCondition();
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public double getAmount() {
		return amount;
	}

	public void setAmount(double amount) {
		this.amount = amount;
	}

	@Override
	public String toString() {
		return "Account [name=" + name + ", amount=" + amount + "]";
	}
	
	/*
	 * 定义取款的方法
	 * */
	public void draw(double money)
	{
		//显式加锁
		lock.lock();
		if(flag)
		{
			try {
				condition.await();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {
			setAmount(getAmount() - money);
			System.out.println("取款成功 当前余额=" + getAmount() + "  取款=" + money);
			flag=true;
			//唤醒存款线程,你该存款
			condition.signal();
		}
		
		//释放锁
		lock.unlock();
	}
	
	
	/*
	 * 定义存款的方法
	 * */
	public void doposit(double money)
	{
		//显式加锁
		lock.lock();
		
		//flag默认等于true
		if(!flag)
		{
			//存款处于等待,直到被取款线程唤醒
			try {
				condition.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}else
		{
			setAmount(getAmount() + money);
			System.out.println("存款成功 当前余额=" + getAmount() + "  存款=" + money);
			flag=false;
			//唤醒取款的线程,你该取款
			condition.signal();
		}
		
		//释放锁对象
		lock.unlock();
	}

}

/*
 * 取款线程
 */
class DrawThread extends Thread {
	private Account account;
	private double money;

	public DrawThread(Account account, double money) {
		this.account = account;
		this.money=money;
	}

	@Override
	public void run() {

		for(int i=0;i<11;i++)
		{
			this.account.draw(money);
		}

	}
}

/*
 * 存款线程
 */
class Dosipoit extends Thread {
	private Account account;
	private double money;

	public Dosipoit(Account account, double money) {
		this.account = account;
		this.money=money;
	}

	@Override
	public void run() {
		
		for(int i=0;i<11;i++)
		{
			this.account.doposit(money);
		}
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		Account account=new Account("张三", 0);
		Dosipoit d=new Dosipoit(account,100);
		DrawThread f=new DrawThread(account, 100);
		d.start();
		f.start();
	}
}

12、线程池的应用

12.1 简介

  • 其实线程池就是一段内存空间,里面已经存储了创建好的线程对象,提交任务,直接从内存空间获取线程即可处理,此线程处理完成之后回归此内存空间,因此线程就可以反复的复用。 这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

12.2 线程池的工作原理

  • 1、如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任

  • 2、如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。

  • 3、如果在步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行。

  • 4、如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务。
    在这里插入图片描述

12.3 分析ThreadPoolExecutor类

  • 简介

    • 自定义线程池

    • 分析构造器

      new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);

      • corePoolSize:线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态

      • maximumPoolSize:线程池允许创建的最大线程数。简单理解,即核心线程都被占用,但还有任务要做,就创建非核心线程

      • ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

      • keepAliveTime:非核心线程空闲时的超时时长,超过这个时长,非核心线程就会被(垃圾回收机制)回收。

      • TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

      • runnableTaskQueue:用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。

        • ArrayBlockingQueue;
        • LinkedBlockingQueue;
        • SynchronousQueue;
      • handler:表示当拒绝处理任务时的策略,有以下四种取值:

        • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
        • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
        • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
        • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

12.4 自定义线程池举例

package 自定义线程池;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

class MyRunnable implements Runnable
{
	@Override
	public void run() {
		
		System.out.println("子线程id="+Thread.currentThread().getId()+"  子线程名="+Thread.currentThread().getName());
		
		//睡眠
		try {
			Thread.sleep(20*1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		//创建一个线程池对象
		/*
		 * 参数一:corePoolSize:线程池的核心线程数
		 * 参数二:maximumPoolSize:线程池允许创建的最大线程数
		 * 
		 * */
		
		//储存10个任务
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(3);
        
        //拒绝任务提交,抛出异常
        RejectedExecutionHandler handler =  new ThreadPoolExecutor.AbortPolicy();
        
		ThreadPoolExecutor executor=new ThreadPoolExecutor(
                3,//核心线程数
                6,//最大线程数,包括核心线程数和普通线程数
                5,//非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。
                TimeUnit.SECONDS,//(线程活动保持时间的单位)
                queue,//:用于保存等待执行的任务的阻塞队列
                handler);//表示当拒绝处理任务时的策略
		
		//提交任务1
		executor.submit(new MyRunnable());
		//提交任务2
		executor.submit(new MyRunnable());
		//提交任务3
		executor.submit(new MyRunnable());
		//提交任务4
		executor.submit(new MyRunnable());
		//提交任务5
		executor.submit(new MyRunnable());
		//提交任务6
		executor.submit(new MyRunnable());
		//提交任务7
		executor.submit(new MyRunnable());
		//提交任务8
		executor.submit(new MyRunnable());
		//提交任务9
		executor.submit(new MyRunnable());
		//提交任务10
		executor.submit(new MyRunnable());
		//提交任务11
		executor.submit(new MyRunnable());
		//提交任务12
		executor.submit(new MyRunnable());
	}
}

12.5 jdk内置线程池用法

package jdk自带线程池;

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

class MyRunnable implements Runnable
{
	@Override
	public void run() {
		
		System.out.println("子线程id="+Thread.currentThread().getId()+"  子线程名="+Thread.currentThread().getName());
		
		//睡眠
		try {
			Thread.sleep(20*1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

public class MainTest {

	public static void main(String[] args) {
		
		/*
		 * core thread num=3
		 * 普通线程=0,既没有普通线程
		 * 等待队列长度:无限大
		 * */
		ExecutorService executorService=Executors.newFixedThreadPool(3);
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());
		executorService.submit(new MyRunnable());		
	}
}

13、ThreadLocal类应用

13.1 简介

  • ThreadLocal为解决多线程程序的并发问题提供了一种新的思路,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

13.2 举例

package ThreadLocal用法;

class MyRunnable implements Runnable {
    
	private int i = 0;
	
	//ThreadLocal默认初始化数据值0
    ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {

        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

	@Override
	public void run() {

		for (int j = 0; j < 10; j++) {

			// i++
			//i++;
             //每个线程都有一份threadId变量的副本
             //针对副本+1操作
			threadId.set(threadId.get()+1);

			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			// i--
			//i--;
             //针对副本-1操作
			threadId.set(threadId.get()-1);

			System.out.println("thread id=" + Thread.currentThread().getId() + "  i=" + threadId.get());

		}

	}
}

public class MainTest {

	public static void main(String[] args) {

		MyRunnable r = new MyRunnable();

		// 创建线程一
		new Thread(r).start();
		// 创建线程二
		new Thread(r).start();

	}

}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值