Java多线程随笔(一)

1.多线程之cas分析

1.1什么是cas

CAS英文翻译为:Compare and Swap ,就是比较并且交换,cas有三个值,第一个是变量地址,第二个是变量旧值,第三个变量要变成的值

1.2cas在什么地方用到了

CAS无锁实现方式,在Java中的java.util.concurrent.atomic包中用到了,以AtomicInteger为例子:

//给int值加一并且得到加一后的值
  public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

这里unsafe.getAndSetInt()的方法就是使用的cas机制,就是一个do{}while()循环,具体实现原理是:拿内存地址找到这个变量,然后旧值和内存中的值比较,如果相等了就会替换成新值。在concurrent包中大量使用了cas机制,包括重入锁,也是使用cas机制来保证锁的唯一性,至于多线程同时访问一个变量的控制,这个是CPU底层指令控制的。

1.3cas的优势和劣势

CAS的优势就是可避免死锁,根本不存在锁,只是比较内存值,所以不会出现死锁。
CAS的弊端就是可能会出现aba的情况,如果加上版本号那么这种问题就完美解决了,这就是乐观锁,Mysql的乐观锁机制就是一个版本号,还有redis的锁,都是基于版本号的乐观锁。

2.线程状态

线程的五大状态分别是:
1. 新建状态 : 刚new出线程并未执行start()方法。
2. 就绪状态 :执行了start()方法,但是还没有分配cpu给它
3. 执行状态 : 执行run()里面的方法
4. 堵塞状态 : 当线程执行sleep或者获取别人正在占有的锁的时候
5. 死亡状态 : 线程执行完毕,进入死亡状态,当时当前对象未必死亡
这里写图片描述
在实际的项目中,往往会因为某种错误操作导致线程堵塞,会造成服务器雪崩,如果有特定的访问的话,找到代码BUG是不太好找的,就可以使用线程Dump来诊断:

public void monitor(){
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfo = threadMXBean.dumpAllThreads(false, false);
        for(int i=0;i<threadInfo.length;i++){
            ThreadInfo threadInfo2 = threadInfo[i];
            String threadName = threadInfo2.getThreadName();
            State threanState = threadInfo2.getThreadState();
            String blockedMsg = "";
            for(int z=0;z<threadInfo2.getStackTrace().length;z++){
                if(threadInfo2.getStackTrace()[z].getClassName().contains("yizhengqiyuan") && !threadInfo2.getStackTrace()[z].getClassName().contains("LogConsole")){
                    blockedMsg+=",阻塞类为:"+threadInfo2.getStackTrace()[z].getClassName()+"第"+threadInfo2.getStackTrace()[z].getLineNumber()+"行进行了阻塞";                 
                }
            }

            //  thread state NEW RUNNABLE  BLOCKED WAITING  TIMED_WAITING  TERMINATED
            if(threadName.startsWith("/") && (threanState == State.BLOCKED ||   threanState == State.TIMED_WAITING) ){
                logger.info("线程的名字为:"+threadInfo2.getThreadName()+",线程的id为"+threadInfo2.getThreadId()+",线程的状态为:"+threadInfo2.getThreadState()+",改线程等待的时间为:"+threadInfo2.getWaitedTime()+"ms,改线程阻塞的时间为:"+threadInfo2.getBlockedTime()+"ms"+",线程阻塞信息为:"+blockedMsg);              

            }
        }

    }

3.开启线程和结束线程

要开启线程,首先要实现多线程,实现多线程有三种方法:
1. 实现Runnable接口,重写run方法
2. 实现Callable接口,重写call方法
3. 继承Thread类,重写run方法
需要注意的是,第三种方式也是实现了Runnable接口,区别就是如果有一个继承类了,你就得实现Runnable接口,因为,一个类只可以有一个父类,可以实现多个接口。callable可以做到有返回值,经典的future模式。
所谓启动多线程,在实际项目中线程一般是用线程池来控制,单纯的线程启动的代码:

Thread thread = new Thread(new Test1());
thread.start();

实现Callable接口的简单demo如下:

package com.study.www.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadNewTest extends Thread  {
    public static void main(String[] args) throws Exception {
        ExecutorService executors = Executors.newFixedThreadPool(5);
        for(int i=0;i<10;i++){
            Future<String> str =  executors.submit(new Test()); 
            System.out.println(str.get());
        }
        executors.shutdown();//关闭线程池
        while(true){
            if(executors.isTerminated()){//判断线程是否执行逻辑完毕
                System.out.println("线程执行逻辑完毕");
                return ;
            }
        }
    }
}

class Test implements Callable<String>{
    public String call() throws Exception {
        return "String1";
    } 
}

终止线程有三种办法:
1. 如果线程是while循环,当道某一个条件需要退出的时候可以使用标志位,当需要结束的时候只要设置标志位就可以
2. 使用stop()方法,此方法不建议用
3. 中断,thread.interrupt(); ,while循环的时候判断thread.isInterrupted()来结束线程

4.多线程之线程之间通信

4.1wait()和notify()/notyfyAll()

wait()方法和notify()方法是Object对象中的方法,也就是说所有的java类都会有这两个方法。
wait():等待对象的同步锁,必须先获取到锁,然后才可wait(),否则编译可通过,运行的时候是会抛出异常的,改方法会释放锁。
notify()/notifyAll():唤醒该对象的一个(notify())执行线程(notifyAll()所有执行线程),该方法调用必须等到获取该对象的锁之后才可执行。

package com.study.www.thread;

public class ThreadStatueTest {
    public static void main(String[] args) {
        ThreadAddInt b = new ThreadAddInt();
        b.start();
        synchronized (b)
        {
            try {
                b.wait();
                System.out.println("唤醒之后执行的逻辑");
            } catch (InterruptedException e) {
            }
        }
        System.out.println("执行完毕totalInt = " + b.totalInt);
    }
}
class ThreadAddInt extends Thread {
    public Integer totalInt =0;

    public void run() {
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                totalInt = totalInt+1;
                System.out.println("线程正在执行,还没有被唤醒,totalInt的值为"+totalInt);
            }
            System.out.println("线程即将被唤醒");
            notify();
        }
    }
}

只是做了notify的demo,notify和notifyAll的区别是notify只唤醒一个线程,notifyAll唤醒改对象的所有线程,至于唤醒顺序,需要JVM去决定。

4.2挂起和唤醒

线程挂起是suspend()方法,唤醒是resume()方法,suspend和resume必须成对出现,若只执行了suspend()方法,那么该线程就会一直持有该对象的锁。和wait/notify不同的是wait执行完synchronized括号里的内容后悔释放锁。
线程挂起和唤醒在jdk1.6中已经不推荐使用了,当某一线程A挂起了,并不会释放对象锁,线程B想恢复线程A,并且想在resume()方法之前获取锁资源的时候就会发生死锁。

4.3信号量和发令枪

发令枪(countdownlatch),通常用在线程之间交互,比如有六个线程,当第六个线程是在前五个线程执行完之后才执行的,就可以在前五个线程里对countDownlatch进行减一,在第六个线程那进行await();
countDownlatch的demo:

public class CountDownLatchTest {
    static CountDownLatch countDownLatch = new CountDownLatch(5);
    public static void main(String[] args) {
        RunableTest runableTest = new RunableTest();
        for(int i =0;i<5;i++){
            Thread thread = new Thread(runableTest);
            thread.start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发令枪被唤醒后执行的逻辑");
    }
}
class RunableTest implements Runnable {
    static int addInt = 0;
    public synchronized void run() {
        System.out.println("线程"+addInt+"正在执行");
        addInt = addInt+1;  
        CountDownLatchTest.countDownLatch.countDown();
    }
}

信号量(Semaphore),一个信号计数器,就像一个饭店,固定有50张桌子,也就只能坐50个人,其他人只能在某个人吃完饭了,才能进来吃饭。可以控制线程对资源的访问

package com.study.www.thread;

import java.util.concurrent.Semaphore;

public class SemaphoreTest {
    static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {
        RunableTest1 runableTest1 = new RunableTest1();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runableTest1);
            thread.setName(i+"");
            thread.start();
        }

    }
}
class RunableTest1 implements Runnable {
    public  void run() {
        try {
            SemaphoreTest.semaphore.acquire();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "进来了");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "出去了");
        SemaphoreTest.semaphore.release();
    }

}

这里写图片描述
可以从执行结果上看到,每次最多有两个线程在执行

4.4CyclicBarrier回环栅栏

CyclicBarrier字面意思回环栅栏,通过他可以让一组线程等待直某个状态的时全部同时执行,回环是可重复使用。可用于多线程计算数据,最后合并计算结果。比如计算成绩,数学,语文,英语这张卷分别为三个线程,统计所有题得分加起来的分数,计算总成绩的时候就需要前面三门课程成绩计算完毕,然后根据前面三门课程各自的成绩加起来即可。
用法如下:

package com.study.www.thread;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 回环栅栏
 * @author liuguoqiang
 *
 */
public class CyclicBarrierTest {
    static CyclicBarrier barrier = new  CyclicBarrier(5);
    public static void main(String[] args) {
        CyclicBarrierRunable cyclicBarrierRunable = new CyclicBarrierRunable();
        for(int i=0;i<10;i++){
            Thread thread = new Thread(cyclicBarrierRunable);
            thread.start();
        }

    }
}
class CyclicBarrierRunable implements Runnable {
    public void run() {
        try {
            Thread.sleep(new  Random().nextInt(1000));
            System.out.println("线程执行到这里,我在处理线程里的业务1完毕,等待其他线程处理");
            CyclicBarrierTest.barrier.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("其他线程也执行完了业务1,继续处理其他业务");
    }

}

countDownlatch 和 CyclicBarrier的区别:
1. countDownLatch只能使用一次,CycliBarrier可以多次重复使用,直接重置调用reset()即可;
2. countDownLatch是减数器,countDown是减一,barrier.await();是加一;
3. countDownLatch是减到0的时候执行,CycliBarrier是加到某一个数的时候执行;
4. 功能基本相同,用法上除了可复用这个特点其他基本可以实现相同的功能;

上面分析了线程基本知识,下篇文章分析锁和线程安全的集合/map/队列.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值