Java多线程并发(二): 线程常用方法以及线程锁

线程常用方法

1.生产者消费者模式

中间队列

package queue;

//中间队列
public class QueueBuffer {
	private int number=0; //商品
	
	/**
	 * 有一个资源,里面维护一个成员变量number,默认值为0,有两个线程,一个对number做++
	 * 一个对number--
	 * 业务需求: 这个number永远不停的为0,或者1
	 * 	1. 考虑互斥  0-->1    1-->0 依次循环
	 *  2. 考虑通讯  wait() notifall()
	 */
	
	/**
	 * synchronized仅仅只能做到互斥不能做到通讯
	 * increase()执行完毕释放锁后,会有两种情况:1. increase()得到锁继续执行 2. 没有得到锁执行 decrease()
	 * 所有我们应该还用wait()来保证通讯
	 */
	
	
	public synchronized void increase() throws InterruptedException{ 
		while(number != 0){ //有资源,不用生产
			this.wait();
		}
		number ++;
		System.out.println("生产后有"+number);
		notify();
	}
	public synchronized void decrease() throws InterruptedException{
		while(number==0){ //没有资源,不用消费
			this.wait();
		}
		number --;
		System.out.println("消费后有"+number);
		notify();
	}
}

生产者

package production;

import queue.QueueBuffer;

//生产者
public class Production extends Thread{
	
	private QueueBuffer queue; //传入自定义队列
	public Production(QueueBuffer queue) { //用构造方法进行初始化
		this.queue = queue; 
	}
	
	@Override
	public void run() {
		while(true){
			try {
				Thread.sleep(2000);
				queue.increase(); //生产资源
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

消费者

package consumption;

import queue.QueueBuffer;

//消费者
public class Consumption extends Thread{
	private QueueBuffer queue; //传入自定义队列
	public Consumption(QueueBuffer queue) {//用构造方法进行初始化
		this.queue = queue;
	}
	
	@Override
	public void run() {
		while(true){
			try {
				Thread.sleep(2000);
				queue.decrease(); //消费资源
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

启动类实现

package RUN;

import consumption.Consumption;
import production.Production;
import queue.QueueBuffer;

//启动线程类
public class runThread {
	public static void main(String[] args) {
		QueueBuffer queue = new QueueBuffer(); //队列对象
		
		new Production(queue).start(); //生产者线程
		new Consumption(queue).start(); //消费者线程
		
		new Production(queue).start(); //生产者线程
		new Consumption(queue).start(); //消费者线程
	}
}




2. 线程等待(wait)

调用该方法的线程进入Waiting状态,只有等待另一个线程的通知或被中断才会返回,需要注意的是调用wait()方法后,会释放对象的锁。因此,wait方法一般用在同步方法或同步代码块中

3. 线程休眠(sleep)

sleep导致当前线程休眠,与wait方法不同的是sleep不会释放当前占有的锁,sleep(long)会导致线程进入TIMED_WAITING状态

4. sleep与wait的区别?

  1. 对于sleep()方法,它属于Thread类;sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),该线程不会丢失锁;
  2. wait()方法它属于Object类,而且是final修饰的,因此会被所有的Java类继承但无法重写.wait()方法要求当前线程必须获得此对象的锁,因此它的调用需要放在synchronized方法或块中.当线程执行了wait()时,它会释放掉对象的锁,直到其他线程调用notify()或notifyAll(),该线程重新获得锁然后继续执行

5. 线程让步(yield)

yeild会使当前线程让出CPU执行时间片,与其他线程一起重新竞争CPU时间片。一般情况下,线程优先级高的线程有更大的可能性成功竞争得到CPU时间片,但这不是绝对的

package ThreadTest;

import java.util.Date;

public class MyThread{
	public static void main(String[] args) throws InterruptedException {
		Thread1 t1 = new Thread1("t1");
		Thread1 t2 = new Thread1("t2");
		t1.start();
		t2.start();
	}	
}

class Thread1 extends Thread {
 
	public Thread1(String arg0) {
		super(arg0);
		
	}
 
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=1;i<20;i++){
			System.out.println(getName()+" " + new Date() + ":" + i);
			if(i%5==0){
				Thread.yield();
			}
		}
	}
}
 

6. 线程中断(interrupt)



7. Join等到其他线程终止

t.join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其他线程完成再结束main()主线程

package ThreadTest;


public class MyThread{
	
	public static void main(String[] args) throws InterruptedException {
		
		Thread1 t1 = new Thread1("t1");
		t1.start();
		//t1.join();		
		System.out.println(t1.a);		
	}	
}
class Thread1 extends Thread {
	static volatile Integer a;
	public Thread1(String arg0) {
		super(arg0);
		
	}
	
	@Override
	public void run() {
		a =10;
	}
}

4. 线程唤醒(notify、notifyAll)

notify随机唤醒一个等待的线程,而notifyAll方法将唤醒所有线程

3. start()与run()方法

start()与run()的区别?

  1. start()来启动线程真正实现了多线程运行,无需等待run方法体代码执行完毕,就可以继续执行下面的代码,通过调用Thread类的start()方法来启动一个线程,此时线程处于就绪状态,并没有运行

  2. 如果直接调用线程对象的run()方法,系统把线程对象当成普通对象,而run()方法也是一个普通方法,而不是线程执行体




Java锁

在这里插入图片描述

1. 悲观锁与乐观锁 :是一种广义概念,体现的是看待线程同步的不同角度

悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,在获取数据的时候会先加锁,确保数据不会被别的数据修改

锁实现 : 关键字synchronized、接口Lock的实现类
适用场景 : 写操作较多,先加锁可以保证写操作的数据正确
在这里插入图片描述



乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据

锁实现 : CAS算法,列如AtomicInteger类的原子自增是通过CAS自旋实现
适用场景 : 读操作较多,不加锁的特点能够使其读操作的性能大幅提升
在这里插入图片描述



2. CAS 全名:Compare And Swap(比较与替换)

无锁算法: 基于硬件原语实现,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步

计算机进程的控制通常由原语完成。所谓原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断

CAS可以将比较与交换转换为原子操作,这个原子操作直接由处理器保证

算法涉及到了三个操作数:
需要读写的内存值V
进行比较的值(预估值)A
要写入的新值(更新值)B

在这里插入图片描述
CAS这种机制我们也可以称之为乐观锁,综合性能较好
  CAS获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰,结合CAS和volatile可以实现无锁并发,适用与竞争不激烈、多和cpu的场景下

  1. 因为没有使用synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
  2. 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响

2.1. CAS操作demo

JDK中实现: java.util.concurrent包中的原子类(AtomicInteger)就是通过CAS来实现了乐观锁

package ThreadTest;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;


public class Test05Atomicity_ACS {
	//1. 共享变量
	private static AtomicInteger number = new AtomicInteger();
	public static void main(String[] args) throws InterruptedException {
		// 2.对number进行1000次++
		Runnable increment = () -> {
			for(int i =0;i<1000;i++){
				number.incrementAndGet();//变量赋值的原子性
			}
		};
		List<Thread> list = new ArrayList<>();
		
		
		//3.使用5个线程来进行
		for(int i=0;i<5;i++){
			Thread t = new Thread(increment);
			t.start();
			list.add(t);
		}
		for (Thread thread : list) {
			thread.join();	
		}		
		System.out.println(number);
	}
}

2.2. AtomicInteger 是如何实现原子操作的呢

https://blog.csdn.net/reggergdsg/article/details/51835184


2.3. Unsafe类介绍

AtomicInteger类中包含了Unsafe类提供了原子操作(CAS)

Java语言不能直接操作内存地址的(没有指针),通过Unsafe类使Java拥有和C指针一样操作对象的内存空间,同时带来了指针的问题;过度使用会使得出错的几率变大,Unsafe对象只能反射获得

	public class AtomicInteger extends Number implements java.io.Serializable {
		// setup to use Unsafe.compareAndSwapInt for updates
		private static final Unsafe unsafe = Unsafe.getUnsafe();
		//用来找到value的内存地址(AtomicInteger的内存地址+valueOffset偏移量 )
		private static final long valueOffset;
		// value用来保存int
		private volatile int value;
		
		public final int incrementAndGet() {
			//调用Unsafe的方法
        	return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
	}

在这里插入图片描述
流程:
https://www.bilibili.com/video/BV1JJ411J7Ym?p=21



2.4.CAS算法问题

在这里插入图片描述

ABA问题
  如果在线程2修改V之前,有一个线程3对V值进行操作,将V值重新改回0,V值实际上已经发生过改变,但是对于线程2来说,并不能感知V的变化

3. 死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放

package demo;

public class Test_dead {
	public static void main(String[] args) {
		MyThreadD my = new MyThreadD();
		new Thread(my,"小明").start();
		new Thread(my,"小红").start();
	}
}

class MyThreadD implements Runnable{
	private Object o1 = new Object();
	private Object o2 = new Object();
	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		if(name.equals("小明")){
			a();
		}else b();
			
	}
	public void a(){
		synchronized (o1) {
			System.out.println(Thread.currentThread().getName()+"已经获取到了01锁,准备获取o2锁;先睡眠一会");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"睡醒了,等待获取02锁");
			synchronized (o2) {
				System.out.println("获取到了o2锁");
			}
		}
	}
	public void b(){
		synchronized (o2) {
			System.out.println(Thread.currentThread().getName()+"已经获取到了02锁,准备获取o1锁;先睡眠一会");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"睡醒了,等待获取01锁");
			synchronized (o1) {
				System.out.println("获取到了o1锁");
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值