索引
线程基本知识
线程的状态
在Java的Thread类中,维护了线程状态的枚举类:State。一共有如下6个值:
- NEW
- RUNNABLE
- BLOCKED
- WAITING
- TIMED_WAITING
- TERMINATED
如果把这几个状态画入状态图的话,那会是如下这样:
线程的基本操作
创建线程
// 创建线程 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