Java线程基础

线程基本知识

线程的状态

Java的Thread类中,维护了线程状态的枚举类:State。一共有如下6个值:

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

如果把这几个状态画入状态图的话,那会是如下这样:

Java线程基础
线程状态机

线程的基本操作

创建线程

// 创建线程
Thread t1 = new Thread(Runnable实例);

// 启动线程
t1.start();

上面是创建线程以及启动一个线程的方式,需要注意,Runnable接口只有一个方法,那就是run()方法,这个run()可不能直接调,必须使用start()才可以。如果直接调用run()方法,那只会和调用一个普通方法一样,而不会创建新的线程。

中断线程

Thread方法有一个特别明显的终止线程的方法,叫做stop,一眼看过去,Java这明显就是让调这个方法去中断一个线程啊。不过再仔细一看就会发现,这个stop方法已经被打了@Deprecated标识了,也就是Java不推荐使用了。为什么呢?因为这个方法特别的“暴力”。一旦调用了stop()方法,不管线程正在做什么操作,都会立刻退出,这样就会造成很多问题。比如你正在往数据库写数据,本来要改4个表,只改了2个表就因为外部调用stop()而强制退出,就会造成数据库数据混乱,这在生产环境是绝对不应该存在的问题。

现在,Java提供了另外的下面几个方法供Java程序员优雅的结束一个线程。

interrupt(); // 中断线程
isInterrupted(); // 判断是否被中断
static interrupted(); // 判断是否被中断,并且清除当前中断状态

怎么使用呢?例如可以这样:

// Runable的run方法
public void run() {
    // 检测到线程被中止时,做好最后的处理再退出
    if (Thread.currentThread().isInterrupted()) {
        //doSomething
    }
}

另外,Java程序员肯定都知道Thread.sleep()方法,方法要求传入一段时间,调用后线程就会阻塞一段时间然后再继续执行。如果调用过,那肯定还知道这个方法会抛出InterruptedException,这里要说明的是,这个异常是在线程sleep中途调用interrupt()时抛出的。并且,sleep()抛出这个异常后,还会清除线程的中断状态,也就是线程从中断状态变为非中断状态。如果在其他地方有判断线程是否中断而做的一些后续工作,就会因为检测不到中断状态而无法退出线程。所以,要切记,在InterruptedException的处理部分,是需要再调用一次interrupt()方法将线程状态置为中断状态的。

等待和通知

Java的Object对象有两个方法可以控制线程暂停或继续,那就是wait()和notify()方法。调用wait()方法时,线程暂停,等待调用notify(),一旦有其他线程调用刚才对象的notify()方法,线程就可以继续执行了。

调用这两个方法要注意的是,如果你要调用A对象的wait()或notify(),必须首先进入A对象的synchronized块,接下来才能正常调用。

等待和谦让

等待:join()。谦让:yield()。

如果需要等待其他线程结束才能继续执行,那就调用那个线程的join()方法,相当于“加入”那个线程,等它干完活我再继续干。

如果某个线程调用了yield()方法,意思就是说:我干完我的活了,可以给其他线程资源去工作了。但这样做了之后,此线程还是会参与后续CPU的争夺,谁能争夺到,就得看是如何调度的了。

诡异的并发问题

ArrayList

case:多个线程并发的往一个ArrayList内add元素。此时可能会出现3中结果:

  • 一切正常
  • 抛出ArrayIndexOutOfBoundsException
  • 元素总数小于一切正常的时候

原因可以归咎于多线程操作同一个对象,破坏了对象内部的状态。

改进方法:使用线程安全的Vector。

HashMap

多个线程并发的put,在resize时,可能会出现在某个hash值内出现环状链表的情况。在get时就会出现死循环。

原因简单的说是和上面ArrayList一样。

改进方法:使用ConcurrentHashMap。

错误的加锁

Integer num = 0;

// Runnable的run()
public void run() {
    for (int i = 0;i < 10000;i++) {
        synochronized(num) {
            num++;
        }
    }
}

各位看看上面的代码,如果多个线程一块做num++操作,是否会有问题呢?

答案是会有问题。num++这个操作,每次num++都会产生一个新的对象,因为num是Integer,所以多个线程的synochronized(num)很可能是加在不同对象上的,造成这个加锁是无效的。

改进方法:使多个线程的synochronized加锁加在一个固定对象上即可。

结语

上面列举的都是一些Java线程基本的使用方式以及注意点,希望对你有帮助。

原创文章,作者:geekgao,如若转载,请注明出处:https://www.geekgao.cn/archives/1345

发表评论

电子邮件地址不会被公开。 必填项已用*标注

GitLab GitHub
分享本页
返回顶部