Java 多线程

文章目录

  • 进程
  • 线程
    • 多线程的实现方式1
    • 设置和获取线程名称
    • 线程调度
    • 线程控制
    • 线程生命周期
    • 多线程的实现方式2
  • 线程同步
    • 同步代码块
    • 案例:卖票
    • 同步方法
    • 线程安全的类
    • Lock锁
    • 生产者消费模式
    • 生产者消费者案例

进程

是正在运行的程序。
是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。

线程

是进程中的单个顺序控制流,是一条执行路径。
单线程:一个进程如果只有一条执行路径,则成为单线程程序。
多线程:一个进程如果有多条执行路径,则成为多线程程序。

多线程的实现方式1

方式1:继承Thread类

  • 定义一个类MyThread继承Thread类
  • 在MyThread类中重写run()方法
  • 创建MyThread类的对象
  • 启动线程
public class test02 {
	public static void main(String[] args)  {
		MyTread myTread=new MyTread();
		MyTread myTread1=new MyTread();
		myTread.start();
		myTread1.start();
	}
}
class MyTread extends Thread{
	@Override
	public void run() {
//		super.run();
		for (int i = 0; i < 100; i++) {
			System.out.println(i);
		}
	}
}

为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程,然后由JVM调用线程的run()方法

设置和获取线程名称

Thread类中设置和获取线程名称的方法

  • void setName(String name):将此线程的名称更改为等于参数name
  • String getName():返回此线程的名称
  • 通过构造方法也可以设置线程名称

获取main()方法所在的线程名称?
public static Thread currentThread():返回对当前正在执行的线程对象的引用

public class test02 {
	public static void main(String[] args)  {
     	//无参构造器创建对象
		MyTread myTread=new MyTread();
		MyTread myTread1=new MyTread();
//		//设置线程名
		myTread.setName("线程1");
		myTread1.setName("线程2");
		/****************************/
		//带参构造器创建对象
		MyTread myTread=new MyTread("线程1");
		MyTread myTread1=new MyTread("线程2");
		myTread.start();
		myTread1.start();
		//static Thread currentThread(),返回对当前正在执行的线程对象的引用
		System.out.println(Thread.currentThread().getName());
	}
}
class MyTread extends Thread{
	//无参构造
	public MyTread() {
	}
	//带参构造
	public MyTread(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}
	//重写run()方法
	@Override
	public void run() {
//		super.run();
		for (int i = 0; i < 100; i++) {
			System.out.println(getName()+":"+i);
		}
	}
}

线程调度

两种调度模型:
分成调度模型:所以线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。
Java使用的是抢占式调度模型
多线程程序的执行是有随机性的,因为谁抢到CPU的使用权是不一定的。

Thread类中设置和获取线程优先级的方法
public final int getPriority():返回此线程的优先级
public final void setPriority():更改此线程的优先级

线程默认优先级是5,线程优先级的范围是1-10

线程优先级高仅仅表示获取CPU时间片的几率高,并不是一定要跑在前面。
但是要在次数比较多,或者多次运行之后才能看到想要的结果

//带参构造器创建对象
MyTread myTread=new MyTread("线程1");
MyTread myTread1=new MyTread("线程2");
MyTread myTread2=new MyTread("线程3");
//默认优先级
System.out.println(myTread.getPriority());  //5
System.out.println(myTread1.getPriority()); //5
System.out.println(myTread2.getPriority()); //5
//设置优先级
myTread.setPriority(1);
myTread1.setPriority(5);
myTread2.setPriority(10);
System.out.println(myTread.getPriority());  //1
System.out.println(myTread1.getPriority()); //5
System.out.println(myTread2.getPriority()); //10
myTread.start();
myTread1.start();
myTread2.start();

线程控制

方法名 说明
static void sleep(long millis) 使当前正在执行的线程停留(暂停)执行的毫秒数
void join() 等待这个线程死亡
void setDaemon() 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

static void sleep(long millis) :使当前正在执行的线程停留(暂停)执行的毫秒数

public class test02 {
	public static void main(String[] args)  {
		ThreadSleep ts1=new ThreadSleep("曹操");
		ThreadSleep ts2=new ThreadSleep("刘备");
		ThreadSleep ts3=new ThreadSleep("孙权");
		ts1.start();
		ts2.start();
		ts3.start();
	}
}
class ThreadSleep extends Thread{
	public ThreadSleep() {
	}
	public ThreadSleep(String name) {
		super(name);
	}
	@Override
	//重写run方法
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(getName()+":"+i);
			try {
				Thread.sleep(1000); //每执行一次,停留一秒后再执行
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

void join():等待这个线程死亡

public class test02 {
	public static void main(String[] args) throws InterruptedException  {
		ThreadJoin  ts1=new ThreadJoin ("康熙");
		ThreadJoin  ts2=new ThreadJoin ("四阿哥");
		ThreadJoin  ts3=new ThreadJoin ("八阿哥");
		ts1.start();
		ts1.join();  //等待ts1执行结束后,ts2和ts3才能执行
		ts2.start();
		ts3.start();
	}
}

void setDaemon():将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

ThreadDaemon  ts1=new ThreadDaemon ("关羽");
ThreadDaemon  ts2=new ThreadDaemon ("张飞");
//设置守护线程
ts1.setDaemon(true);
ts2.setDaemon(true);
ts1.start();
ts2.start();

//设置主线程为“刘备”
Thread.currentThread().setName("刘备");
for(int i=0;i<10;i++) {
	System.out.println(Thread.currentThread().getName()+":"+i);
}
//主线程执行完毕后,守护线程很快执行完毕

线程生命周期

Java 多线程_第1张图片

多线程的实现方式2

实现Runnable接口

  • 定义一个类MyRunnable实现Runnable
  • 在MyRunnable类中重写run()方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
  • 启动线程

相比继承Thread类,实现Runnable接口的好处:

  1. 避免了Java单继承的局限性。
  2. 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想。
public class test02 {
	public static void main(String[] args) throws InterruptedException  {
		Thread thread1=new Thread(new MyRunnable(),"进程1");
		Thread thread2=new Thread(new MyRunnable(),"进程2");
		thread1.start();
		thread2.start();
	}
}
class MyRunnable implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

线程同步

同步代码块

锁多条语句操作共享数据,可以使用同步代码块实现。
格式:
synchronized(任意对象){
多条语句操作共享数据的代码
}
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁。

好处和弊端:
好处:解决了多线程的数据安全问题
弊端:大概线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

案例:卖票

某电影院目前正在上映国产大片,共有100张票,而他有三个窗口卖票,设计一个程序模拟该电影院卖票。
思路

  • 定义一个SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets=100;
  • 再SellTicket类中重写run()方法卖票:
    • 判断票数大于0,就卖票,并告知是哪个窗口卖的
    • 卖了票之后,总票数就要减一
    • 票没有了,也可能有人来问,所以这里通过死循环让卖票的动作一直执行。
  • 定义一个 测试类SellTicketDemo,里面有main方法,代码步骤如下:
    • 创建SellTicket类的对象
    • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
    • 启动线程
public class test02 {
	public static void main(String[] args) throws InterruptedException  {
		SellTicket sellTicket=new SellTicket();
		Thread t1=new Thread(sellTicket,"窗口1");
		Thread t2=new Thread(sellTicket,"窗口2");
		Thread t3=new Thread(sellTicket,"窗口3");
		t1.start();
		t2.start();
		t3.start();
	}
}
class SellTicket implements Runnable{
	private int tickets=100;
	private Object obj=new Object();
	@Override
	public void run() {
		while (true) {
			//tickets=100;
			//t1,t2,t3,
			//假设t1抢到了cpu的执行权
			synchronized (obj) {
				//t1进来后,就会把这段代码锁起来
				if (tickets>0) {
					try {
						Thread.sleep(100);
						//t1休息100毫秒
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//窗口1正在出售第100张票
					System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
					tickets--; //tickets=99;
				}
			}
			//t1出来了,这段代码的锁就被释放了
			//t1,t2,t3继续抢cpu的执行权然后锁代码
		}
	}
}

同步方法

就是把synchronized关键字加到方法上
格式:
修饰符 synchronized 返回值类型 方法名(方法参数){ }

public class test02 {
	public static void main(String[] args) throws InterruptedException  {
		SellTicket sellTicket=new SellTicket();
		Thread t1=new Thread(sellTicket,"窗口1");
		Thread t2=new Thread(sellTicket,"窗口2");
		Thread t3=new Thread(sellTicket,"窗口3");
		t1.start();
		t2.start();
		t3.start();
	}
}
class SellTicket implements Runnable{
	private int tickets=100;
	private Object obj=new Object();
	private int x=0;
	@Override
	public void run() {
		while (true) {
			if (x%2==0) {
					sellTicket();
			}else {
				sellTicket();
			}
			x++;
		}
	}
	private synchronized void sellTicket() {
			if (tickets>0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
	 				e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
				tickets--; 
			}
		}
}

线程安全的类

StringBuffer

  • 线程安全,可变的字符序列
  • 从版本JDK5开始,被StringBuilder替代,通常应该使用StringBuilder类,因为他支持所有相同的操作,但他更快,因为它不执行同步

Vector

  • 该类改进了List接口,使其成为Java Collections Framework的成员,与新的集合实现不同,Vector被同步,如果不需要线程安全的实现,建议ArrayList代替Vector

HashTable

  • 该类实现了一个哈希表,它将键映射到值,任何非nu对象都可以用键或者值。
  • 该类实现了Map接口,使其成为Java Collections Framework的成员,于心的集合实现不同。Hashtable被同步,如果不需要线程安全的实现建议使用Hashmap代替hashtable

Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里上了锁,在哪里释放了锁。
为了更清晰的表达如何释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比synchronize方法和语句可以获得更广泛的锁定操作。
Lock中提供了获得锁和释放锁的办法
void lock():获得锁
void unlock():释放锁

Lock是接口不能直接实例化,这里采用他的实现类ReentrantLock来实例化

public class test02 {
	public static void main(String[] args) throws InterruptedException  {
		SellTicket sellTicket=new SellTicket();
		Thread t1=new Thread(sellTicket,"窗口1");
		Thread t2=new Thread(sellTicket,"窗口2");
		Thread t3=new Thread(sellTicket,"窗口3");
		t1.start();
		t2.start();
		t3.start();
	}
}
class SellTicket implements Runnable{
	private int tickets=100;
	private Object obj=new Object();
	private Lock lock=new ReentrantLock();
	@Override
	public void run() {
		while (true) {
			try {
				lock.lock();
				if (tickets>0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
		 				e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
					tickets--; 
				}
			}finally {
				lock.unlock();
			}
		}
	}
}

生产者消费模式

所谓生产者消费者问题,实际上主要是包含了两类线程:

  • 一类是生产者线程用于生产数据
  • 一类是消费者线程用于消费数据

为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

  • 生产者生产数据之 后直接放置在共享数据区中,环需要关心消费者的行为
  • 消费者只需要从共享数据区中去获取数据, 并不需要关心生产者的行为

为了提现生产和消费过程中的等待和唤醒,Java就提供了几个犯法供我们使用,这几个方法在Object类中

方法名 说明
void wait() 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程

生产者消费者案例

  • 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
  • 生产者类(Producer):实现Runnable接口,重写run()方法,调用存牛奶的操作
  • 消费者类(Customer):实现Runnable方法,重写run()方法,调用获取牛奶的操作
  • 测试类(Demo):创建奶箱对象,这是共享数据区域
    创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
    创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
    创建两个线程对象,分别吧生产者对象和消费者对象作为构造方法参数传递
public class test02 {
	public static void main(String[] args) throws InterruptedException  {
	//创建奶箱对象,这是共享数据区域
		Box box=new Box();
	//创建生产者消费者对象
		Customer customer=new Customer(box);
		Producter producter=new Producter(box);
		//创建两个线程对象
		Thread t1=new Thread(producter);
		Thread t2=new Thread(customer);
		//启动线程
		t1.start();
		t2.start();
	}
}
//定义奶箱类
class Box{
	//定义一个成员变量,表示第x瓶奶
	private int milk;
	//定义一个成员变量,表示奶箱的状态
	private boolean flag=false;
	//提供存储牛奶和获取牛奶的操作
	public synchronized void getMilk() throws InterruptedException {
		//如果没有牛奶,等待生产
		if (!flag) {
			wait();
		}
		//如果有牛奶,取出牛奶
		System.out.println("用户拿到第"+this.milk+"瓶奶");
		//修改奶箱状态
		flag=false;
		//唤醒其他等待的线程
		notifyAll();
	}
	public synchronized void setMilk(int milk) throws InterruptedException {
		//如果有牛奶,等待消费
		if (flag) {
			wait();
		}
		//如果没有牛奶,就生产牛奶
		this.milk = milk;
		System.out.println("第"+this.milk+"瓶奶放入奶箱");
		//生产完毕之后,修改奶箱状态
		flag=true;
		//唤醒其他等待的线程
		notifyAll();
	}
}
//定义生产者类
class Producter implements Runnable{
	private Box b;
	public Producter(Box b) {
		this.b=b;
	}
	//重写run()方法
	@Override
	public void run() {
		while (true) {
			try {
				b.getMilk();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
//定义消费者类
class Customer implements Runnable{
	private Box b;
	public Customer(Box b) {
		this.b=b;
	}
	//重写run()方法
	@Override
	public void run() {
		for (int i = 1; i <=5; i++) {
			try {
				b.setMilk(i);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

你可能感兴趣的:(java)