Java——多线程详解(二)

引言

2021年3月5日14:20:28
知识均总结与: Java全栈学习网站. 以及自己的一些心得体会

线程同步

多个线程操作同一个资源

相关概念:

  1. 并发:同一对象被多个线程同时操作,如上万人同时抢一张票
  2. 锁机制:
    同一进程的多个线程共享同一块存储空间,为了保证数据在方法中被访问时的正确性,在访问时加入了锁机制synchronized
    当一个线程对象获得排它锁,独占资源,其他线程必须等待,使用后释放锁即可

线程同步是一种等待机制,多个需要同时访问此对象的线程进入一个对象等待池
形成队列,等待前面线程使用完毕后,下一个线程再使用

锁机制的问题:

  1. 持有锁的线程会导致其他需要锁的线程挂起
  2. 加锁,释锁会导致调度延时,引发性能问题

不安全案例:
不安全买票

package work;


//不安全买票
public class Test_7 {
	public static void main(String[] args) {
		BuyTicket t1 = new BuyTicket();
		new Thread(t1,"mg").start();
		new Thread(t1,"qh").start();
		new Thread(t1,"whs").start();
		new Thread(t1,"tlr").start();
	}
}


class BuyTicket implements Runnable{

	private int ticketNums = 10;
	private boolean flag = true;
	
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		//买票
		while(flag) {
			buy();
		}

		
		
		
	}
	
	private void buy() {
		//判断是否有票
		if (ticketNums <=0) {
			return;
		}
		
		//买票
		System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums-- + "票");
	}
	
	
	
	
}

银行取钱

package work;
//不安全的取钱
public class Test_8 {
	public static void main(String[] args) {
		
		Account account = new Account(100,"结婚基金");
		
		Drawing you = new Drawing(account, 50,"你");
		Drawing girlFriend = new Drawing(account, 100,"girlFriend");
		
		you.start();
		girlFriend.start();
	
		
	}
}

class Account{
	int money; //余额
	String name; //卡名
	public Account(int money,String name){
		this.money = money;
		this.name = name;
	}
}

class Drawing extends Thread{
	Account account; //账号
	
	int drawingMoney; //取钱数目
	
	int nowMoney; //余额
	
	
	public Drawing(Account account,int drawingMoney,String name) {
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}
	
	public void run() {
		//判断有没有钱
		if(account.money - drawingMoney <0) {
			System.out.println(Thread.currentThread().getName()+"钱不够,取不了" );
			return;
		}
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		
		account.money = account.money - drawingMoney;
		nowMoney  = nowMoney + drawingMoney;
		
		System.out.println(account.name +":余额为"+account.money);
		System.out.println(this.getName()+":手里的钱为"+nowMoney);
	}
}

添加东西到同一位置

package work;

import java.util.List;
import java.util.ArrayList;

//线程不安全集合
public class Test_9  {
	public static void main(String[] args) {
		
		List<String> list = new ArrayList<String>();
		
		for(int i = 0;  i < 10000; i++) {
			new Thread(()->{
				list.add(Thread.currentThread().getName());
				}).start();;
		}
		System.out.println(list.size());
	}
}

解决同步问题

通过private关键字来保证数据对象只能被方法访问

因此利用同步方法、同步块——》均是synchronized关键字,来解决同步问题

synchronized方法:

public synchronized void  run() {
  1. 控制对象的访问,每个对象对应一把锁
  2. 每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程就会阻塞
  3. 方法一旦执行,就独占锁,直到方法返回才释放锁
  4. 影响效率
package work;


//不安全买票
public class Test_7 {
	public static void main(String[] args) {
		BuyTicket t1 = new BuyTicket();
		new Thread(t1,"mg").start();
		new Thread(t1,"qh").start();
		new Thread(t1,"whs").start();
	}
}


class BuyTicket implements Runnable{

	private int ticketNums = 1000;
	private boolean flag = true;
	
	@Override
	public  void  run() {
		// TODO 自动生成的方法存根
		//买票
		while(flag) {
			buy();
		}

		
		
		
	}
	
	//同步方法,锁的是BuyTicket本身
	private synchronized void buy() {
		//判断是否有票
		if (ticketNums <=0) {
			return;
		}
		
		//买票
		System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums-- + "票");
	}
	
	
	
	
}

同步块:

同步块:synchronized(Obj) {}

Obj称为同步监视器:

  1. Obj可是任何对象,推荐使用共享资源作为同步监视器
  2. 同步方法中无需指定同步监视器,同步方法的同步监视器就是this,就是对象本身,或者class【反射中讲解】

同步监视器执行过程:
3. 第一个线程访问,锁定同步监视器,执行其中代码
4. 第二个线程访问,发现同步监视器被锁定,无法访问
5. 第一个线程访问完毕,解锁同步监监视器
6. 第二个线程访问,发现同步监视器没锁定,执行其中代码

package work;
//不安全的取钱
public class Test_8 {
	public static void main(String[] args) {
		
		Account account = new Account(100,"结婚基金");
		
		Drawing you = new Drawing(account, 50,"你");
		Drawing girlFriend = new Drawing(account, 100,"girlFriend");
		
		you.start();
		girlFriend.start();		
	}
}

class Account{
	int money; //余额
	String name; //卡名
	public Account(int money,String name){
		this.money = money;
		this.name = name;
	}
}

class Drawing extends Thread{
	Account account; //账号
	
	int drawingMoney; //取钱数目
	
	int nowMoney; //余额
	
	
	public Drawing(Account account,int drawingMoney,String name) {
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}
	
	public void run() {
		
		synchronized (account) {  //同步块
			//判断有没有钱
				if(account.money - drawingMoney <0) {
					System.out.println(Thread.currentThread().getName()+"钱不够,取不了" );
					return;
				}
			
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			
				account.money = account.money - drawingMoney;
				nowMoney  = nowMoney + drawingMoney;
				
				System.out.println(account.name +":余额为"+account.money);
				System.out.println(this.getName()+":手里的钱为"+nowMoney);
			}
		}
		
		
		
}

注:锁的对象都是变化的量

补充:JUC实现安全集合

package work;

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型的集合
public class Test_11 {

	public static void main(String[] args) {
		CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
		
		for(int i = 0;  i < 10000; i++) {
			new Thread(()->{
					list.add(Thread.currentThread().getName());
			}).start();;
		}
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		System.out.println(list.size());
	}
}

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形——死锁

例子:下面就会产生死锁

package work;

import java.awt.List;

//死锁,多个线程互相持有对方需要的资源,然后相互等待
public class Test_12 {
	
	public static void main(String[] args) {
		Makeup g1 = new Makeup(0, "灰姑娘");
		Makeup g2 = new Makeup(1, "白雪公主");
		
		g1.start();
		g2.start();
	}
}

class Lipstick{
	
}

//镜子
class Mirror{
	
}

class Makeup extends Thread{
	//需要的资源只有一份,用static来保证只有一份
	static Lipstick lipstick = new Lipstick();
	static Mirror mirror = new Mirror();
	
	int choice; //选择
	String girName; //人名
	
	public Makeup(int choice,String girName) {
		this.choice = choice;
		this.girName = girName;
	}
	
	public void run() {
		try {
			makeup();
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}
	
	//化妆,互相持有对方想要的资源,口红与镜子
	public void makeup() throws InterruptedException {
		if(choice == 0) {
			synchronized (lipstick) { //获得口红的锁
				System.out.println(this.girName+"获得口红的锁");
				Thread.sleep(1000);
				
				synchronized (mirror) {
					System.out.print(this.girName+"获得镜子的锁");
				}
			}
		}else {
			synchronized (mirror) { //获得镜子的锁
				System.out.println(this.girName+"获得镜子的锁");
				Thread.sleep(2000);
				
				synchronized (lipstick) {
					System.out.print(this.girName+"获得口红的锁");
				}
			}
		}
	}
	
}

产生死锁的四个必要条件:

  1. 互斥条件,一个资源只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:进程已获得资源,未使用完之前,不能强行剥夺
  4. 循环等待:若干进程之间形成一种头尾相接的循环等待资源的关系。

破除其中的一个或多个条件,我们就可以避免死锁的发生。

Lock锁
  • JDK5.0开始,Java提供的线程同步机制,通过显示定义同步锁对象实现同步,同步锁使用Lock对象充当。

  • java.utilconcurrent.Lock接口是控制多个线程对资源共享进行访问的工具,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应获得Lock对象。

  • ReentrantLock(可重复锁) 类实现了Lock,它拥有与synchronized相同的并发性与内存语义,比较常用ReentrantLock,可以显示加锁、释放锁

package work;

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class Test_13 {
	public static void main(String[] args) {
		TestLock2 t1 = new TestLock2();
		
		new Thread(t1).start();
		new Thread(t1).start();
		new Thread(t1).start();
	}
}

class TestLock2 implements Runnable{

	int ticketNums = 10;
	
	//定义lock锁
	private final ReentrantLock lock = new ReentrantLock();
	
	
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		while(true) {
			try {
				
				lock.lock();  //加锁
				if(ticketNums > 0) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO 自动生成的 catch 块
						e.printStackTrace();
					}
					System.out.println(ticketNums--);
				}else {
					break;
				}	
			}
			finally {
				// TODO: handle finally clause
				//解锁
				lock.unlock();
			}
		
		}
	
	}
}
生产者消费者(线程协作)

生产者:生产者将生产出的产品放入仓库,没有产品之前通知消费者等待,有了产品后,通知消费者
消费者:消费后,通知生产者消费结束,需要生产新的产品以供消费。

该问题中:仅使用synchronized是不够的。

Java提供的几个方法解决现存之间的通信

Column 1Column 2
wait()表示线程一直等待,直到其他线程通知,与sleeo不同,会释放锁
wait(long timeout))指定等待毫秒
notify()唤醒一个处于等待的线程
notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级高的先调度

注:均为Object类的方法,只能在同步方法或者同步代码块中使用。否则抛出异常

缓冲区:生产者将产品放入缓冲区,消费者从缓冲区取走数据

管程法(利用缓冲区)
package work;

import java.util.concurrent.CountDownLatch;

//测试生参者,消费者模型——>利用缓冲区解决:管程法
public class Test_14 {
	public static void main(String[] args) {
		SynContainer container = new SynContainer();
		
		new Productor(container).start();
		new Consummer(container).start();
	}
}


//生产者
class Productor extends Thread{
	SynContainer container;
	
	public Productor(SynContainer container) {
		this.container = container;
	}
	
	//生产
	public void run() {
		for(int i = 0; i< 100; i++) {
			System.out.println("生产了" + i + "只鸡");
			container.push(new Chicken(i));
		}
	}
	
}	

//消费者
class Consummer extends Thread{
	SynContainer container;
	
	public Consummer(SynContainer container) {
		this.container = container;
	}
	
	//消费
	public void run() {
		for(int i = 0; i< 100; i++) {
			System.out.println("消费了----》" + container.pop().id + "只鸡");
		}
	}
}

//产品
class Chicken{
	int id; //产品编号
	
	public Chicken(int id) {
		this.id = id;
	}
}

//缓冲区
class SynContainer{
	
	//需要一个容器大小
	Chicken[] chickens = new Chicken[10];
	
	int count = 0;
	
	//生产者放入产品
	public synchronized void push(Chicken chicken) {
		
		//如果容器满了,就需要等待消费者
		if(count == chickens.length){
			//通知消费者消费
			try {
				this.wait();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		
		//如果没有满,我们就需要丢人产品
		chickens[count] = chicken;
		count++;
		
		//通知消费者消费
		this.notifyAll();
		return ;
	}
	
	//消费者消费产品
	public synchronized Chicken pop() {
		//判断是否消费
		if(count == 0) {
			//等待生产者生产
			try {
				this.wait();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		
		//如果可以消费
		count--;
		Chicken chicken = chickens[count];
		
		//通知生产者,我吃完了
		this.notifyAll();
		
		return chicken;
	}
}

信号灯法

利用一个标志位来解决消费者问题

package work;

//信号灯法
public class Test_15 {
	public static void main(String[] arg) {
		TV tv = new TV();
		new Player(tv).start();
		new Wathcer(tv).start();
	}
	
}


//生产者--》演员
class Player extends Thread{
	TV tv = new TV();
	public Player(TV tv) {
		this.tv = tv;
	}
	
	public void run() {
		for(int i = 0; i < 20; i++) {
			if (i%2 == 0 ) {
				this.tv.play("快乐大本营播放着");
			}else {
				this.tv.play("抖音记录美好生活");
			}
		}
	}
}

//消费者--》观众
class Wathcer extends Thread{
	TV tv = new TV();
	public Wathcer(TV tv) {
		this.tv = tv;
	}
	public void run() {
		for(int i = 0; i < 20; i++) {
				this.tv.watch();
		}
	}
	
	
}

// 产品--》节目
class TV{
	//演员表演,观众等待
	//观众观看,演员等待
	
	String voice; //表演的节目
	boolean flag = true;  //信号灯法,利用标志位
	
	//表演
	public synchronized void play(String voice) {
		
		if(!flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
		
		System.out.println("演员表演了");
		
		//通知观众观看
		this.notifyAll();
		this.voice = voice;
		this.flag = !this.flag;
	}
	
	//观看
	public synchronized void watch() {
		if(flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
		
		
		System.out.println("观看完了"+voice);
		
		//通知演员
		this.notifyAll();
		this.flag = !this.flag;
	}
	
}


线程池

经常创建和消耗线程会消耗大量资源,对性能影响很大

线程池:
1.提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中
2.提高响应速度,降低资源消耗,便于线程管理

corePoolSize------核心池大小
maximumPoolSize-------最大线程数
keepAliveTime--------线程没有任务时最多保持多长时间后会终止

JDK5.0提供了线程相关API:ExecutorService 和 Executors

  1. ExecutorService:真正的线程池接口,常见的子类ThreadPoolExecutor
  2. void execute( Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
  3. Future submit(Callable task);执行任务,有返回值,一般来执行Callable(前面我们用的创建线程的第3种方式)
  4. void shutdown():关闭连接池

Executors:工具类,线程池的工厂,用于创建并返回不同类型的线程池

package work;

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

//测试线程池
public class Test_16 {
	public static void main(String[] args) {
		//创建服务,创建线程池
		ExecutorService service = Executors.newFixedThreadPool(10); //参数为线程池大小
	
		//执行
		service.execute(new MyThread());
		service.execute(new MyThread());
		service.execute(new MyThread());
		service.execute(new MyThread());
		
		//关闭连接池
		service.shutdown();
	}
	
}

class MyThread implements Runnable{

	@Override
	public void run() {
		// TODO 自动生成的方法存根

			System.out.println(Thread.currentThread().getName());

	}
	
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值