多线程三:Thread API,ThreadLocal,synchronized,volatile和Condition

Posted 2019lgg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程三:Thread API,ThreadLocal,synchronized,volatile和Condition相关的知识,希望对你有一定的参考价值。

一.Thread API:

setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 

  首先要了解什么是Thread.UncaughtExceptionHandler,默认来说当线程出现未捕获的异常时,会中断并抛出异常,抛出后的动作只有简单的堆栈输出。如:

技术图片
public class ThreadTest
    public static void main(String[] args) throws Exception
        Thread t1=new Thread(new Runnable()
            public void run()
                int a=1/0;
            
        );
        t1.start();
    
技术图片

  那么代码运行到int a=1/0;就会报错:

Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
    at yiwangzhibujian.ThreadTest$1.run(ThreadTest.java:11)
    at java.lang.Thread.run(Thread.java:662)

  这时候如果设置了Thread.UncaughtExceptionHandler,那么处理器会将异常进行捕获,捕获后就可以对其进行处理:

技术图片
public class ThreadTest
    public static void main(String[] args) throws Exception
        Thread t1=new Thread(new Runnable()
            public void run()
                int a=1/0;
            
        );
        t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler()
            @Override
            public void uncaughtException(Thread t,Throwable e)
                System.out.println("线程:"+t.getName()+"出现异常,异常信息:"+e);
            
        );
        t1.start();
    
技术图片

  那么当线程抛出异常后就可以对其抓取并进行处理,最终结果如下:

线程:Thread-0出现异常,异常信息:java.lang.ArithmeticException: / by zero

  如果自己写线程,那么完全可以在run方法内,将所有代码进行try catch,在catch里做相同的操作。UncaughtExceptionHandler的意义在于不对(或者不能对)原有线程进行修改的情况下,为其增加一个错误处理器。

2.interrupt() 、interrupted() 、isInterrupted()作用:

  • 当线程正常运行时,中断属性设置为true,调用其isInterrupted()方法会返回true。
  • 当线程阻塞时(wait,join,sleep方法),会立即抛出InterruptedException异常,并将中断属性设置为false。此时再调用isInterrupted()会返回false。

  这样就由程序来决定当检测到中断属性为true时,怎么对线程中断进行处理。因此,代码可以改成:

技术图片
@Override
public void run()
    while(!Thread.currentThread().isInterrupted())
        //执行某些任务
    

---------------------------------------------------------
@Override
public void run()
    //耗时较长步骤1
    if(Thread.currentThread().isInterrupted()) return;
    //耗时较长步骤2
    if(Thread.currentThread().isInterrupted()) return;
    //耗时较长步骤3

3.stop()、suspend()、resume()为什么不建议使用:

  stop方法会立即中断线程,虽然会释放持有的锁,但是线程的运行到哪是未知的,假如在具有上下文语义的位置中断了,那么将会导致信息出现错误,比如:

技术图片
@Override
public void run()
    try
        //处理资源并插入数据库
    catch(Exception e)
        //出现异常回滚
    
技术图片

  如果在调用stop时,代码运行到捕获异常需要回滚的地方,那么将会因为没有回滚,保存了错误的信息。

  而suspend会将当前线程挂起,但是并不会释放所持有的资源,如果恢复线程在调用resume也需要那个资源,那么就会形成死锁。当然可以通过你精湛的编程来避免死锁,但是这个方法具有固有的死锁倾向。所以不建议使用。其他暂停方法为什么可用:

  • wait方法会释放锁,所以不会有死锁问题
  • sleep方法虽然不释放锁,但是它不需要唤醒,在使用的时候已经指定想要的睡眠时间了。

二、线程安全基本知识:

  首先应该记住以下基本点,先背下来也无妨:

  • 同一时间一个锁只能被一个线程持有
  • 调用对象的wait()和notify()前必须持有它
  • wait()和notify()必须和synchronized一起使用

 

  首先应该记住以下基本点,先背下来也无妨:

  • 同一时间一个锁只能被一个线程持有
  • 调用对象的wait()和notify()前必须持有它
  • wait()和notify()必须和synchronized一起使用

 

三.ThreadLocal详解

ThreadLocal这个对象就是为多线程而生的,没有了多线程ThreadLocal就没有存在的必要了。可以将任何你想在每个线程独享的对象放置其中,并在任何时候取出来。

使用ThreadLocal不会导致内存泄漏。

public class ThreadLocalUse
    //创建一个静态的ThreadLocal对象,当做仓库,存储每个线程自己的资源
    //此处用Object来代替,可以使连接Connection
    private static ThreadLocal<Object> store=new ThreadLocal<Object>();
    
    //当每个线程要获取一个线程资源的时候,调用get方法
    public Object get()
        //首先去仓库中去寻找
        Object obj=store.get();
        //如果不存在那就创建一个给线程,并把创建的放到仓库中
        if(obj==null)
            obj=new Object();
            set(obj);
        
        return obj;
    
    
    //想将线程资源放到仓库调用set方法,
    public void set(Object obj)
        store.set(obj);
    

 

对比ThreadLocal和synchronized同步机制

  很多人都会对这两个对象进行比对,我也谈一下我自己的想法。

  使用synchronized是为了将多条语句进行原子化操作,比说对于递增操作i++,任意一个线程在执行代码时都要保证别的线程不能执行这个代码,否则就会产生脏数据,使用synchronized可以避免这一点。

  而使用ThreadLocal就是给每个线程存储对象用的。既然每个线程使用了自己的对象,没有了竞争就不会出现多线程相关的问题。

四:线程锁synchronized和Lock和volatile和Condition :

多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制。这是Java并发编程中必须要理解的一个知识点。其实使用起来还是比较简单,但是一定要理解。

  有几个概念一定要牢记:

  • 加锁必须要有锁
  • 执行完后必须要释放锁
  • 同一时间、同一个锁,只能有一个线程执行

synchronized : 的特点是自动释放锁作用在方法时自动获取锁,任意对象都可做为锁,它是最常用的加锁机制,锁定几行代码,如下:

//--------同步方法1
public synchronized void test()
    //一段代码

//--------同步方法2,锁某个对象
private Object lock=new Object();
public void test2()
    synchronized(lock)
        
    

  • 作用于普通方法获得当前对象锁,等价于synchronized(this)
  • 作用于静态方法获得类锁,等价于synchronized(类.class)
 

Lock : 的特点是,必须自己创建锁(锁类型已经指定为Lock的实现类,不能使用其它对象),必须自己释放锁。代码结构如下:

Lock lc = new Lock(); 
lc.lock();
try 
    // 执行代码
 finally 
    lc.unlock();

//注意一定要在finally中释放锁,保证即便抛出异常也可以释放。

 

以上是关于多线程三:Thread API,ThreadLocal,synchronized,volatile和Condition的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal模式的核心元素

多线程三:显示主线程执行的效果-第一种继承方式 继承Thread类

尚硅谷_Java零基础教程(多线程)-- 学习笔记

多线程(三):线程的状态

多线程的实现三种方式

Java并发基础