线程常用方法
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的区别?
- 对于sleep()方法,它属于Thread类;sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),该线程不会丢失锁;
- 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()的区别?
-
start()来启动线程真正实现了多线程运行,无需等待run方法体代码执行完毕,就可以继续执行下面的代码,通过调用Thread类的start()方法来启动一个线程,此时线程处于就绪状态,并没有运行
-
如果直接调用线程对象的run()方法,系统把线程对象当成普通对象,而run()方法也是一个普通方法,而不是线程执行体
Java锁
1. 悲观锁与乐观锁 :是一种广义概念,体现的是看待线程同步的不同角度
悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,在获取数据的时候会先加锁,确保数据不会被别的数据修改
锁实现 : 关键字synchronized、接口Lock的实现类
适用场景 : 写操作较多,先加锁可以保证写操作的数据正确
乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据
锁实现 : CAS算法,列如AtomicInteger类的原子自增是通过CAS自旋实现
适用场景 : 读操作较多,不加锁的特点能够使其读操作的性能大幅提升
2. CAS 全名:Compare And Swap(比较与替换)
无锁算法: 基于硬件原语实现,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步
计算机进程的控制通常由原语完成。所谓原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断
CAS可以将比较与交换转换为原子操作,这个原子操作直接由处理器保证
算法涉及到了三个操作数:
需要读写的内存值V
进行比较的值(预估值)A
要写入的新值(更新值)B
CAS这种机制我们也可以称之为乐观锁,综合性能较好
CAS获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰,结合CAS和volatile可以实现无锁并发,适用与竞争不激烈、多和cpu的场景下
- 因为没有使用synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
- 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
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锁");
}
}
}
}