最近在读《Java核心技术》这本书,学习到了《多线程》这一章,因为线程在Java里是非常重要的内容,所以特意在这里结合自己的理解记录一下,以供自己查阅之用。
多线程与多进程有哪些区别呢?本质的区别在于每个进程拥有自己的一整套变量,而线程则共享数据。
首先来说一下Java创建线程的两种方式。
1.从Java.lang.Thread类派生一个新的线程类,重载它的run()方法。
2.实现Runnalbe接口,重载Runnalbe接口中的run()方法。
下面是详细的代码:
1.从Java.lang.Thread类派生一个新的线程类,重载它的run()方法:
public class TestThread extends Thread{
TestThread(){
super();
}
public void run(){
//do something
}
}
public class Test {
public static void main(String[] args) {
TestThread t= new TestThread();
t.start();
}
}
2.实现Runnalbe接口,重载Runnalbe接口中的run()方法:
class TestThread implements Runnable{
TestThread(){
}
public void run(){
//do something
}
}
public class Test {
public static void main(String[] args) {
TestThread t= new TestThread();
Thread t1 = new Thread(t);
t1.start();
}
}
当线程的run方法执行完方法体中的最后一条语句时,线程将终止。在Java的早期版本中,还有一个stop方法,其他线程可以调用它终止线程。但是,在目前主流的Java版本中,这个方法已经被废弃了。
那中断线程的方法是什么呢?下面我来结合自己的理解谈一谈。
首先,有一个方法,interrupt()可以用来请求终止线程。当对一个线程调用这个方法时,线程的中断状态将被置位。每个线程都会不时地检查这个标志,以判断线程是否被中断。
但是,如果当线程被阻塞,就无法检查中断状态。这是产生InterruptedException异常的地方。当一个被阻塞的线程(调用sleep或wait)上调用interrupt方法时,阻塞调用将会被这个异常中断。我们可以在捕获异常的地方进行相应的处理。
单纯的文字还是有一些苍白,我在这里贴一下自己的测试代码:
public class Test {
public static void main(String[] args) {
TestThread t= new TestThread();
Thread t1 = new Thread(t);
t1.start();
}
}
class TestThread implements Runnable{
TestThread(){
}
public void run(){
while(true){
System.err.println("ddddddddddddddddddddddd");
}
}
}
首先一开始运行上面的代码,我们的console在疯狂地打印字符串,这个时候我们来看,怎么关掉当前的线程。
首先,常用的在run方法的循环体添加一些全局变量,然后在外部通过操纵全局变量让线程停止,这种是很常见的,就不再贴代码了,下面我试一下interrupt()方法。
首先,代码如下:
public class Test {
public static void main(String[] args) throws InterruptedException {
TestThread t= new TestThread();
Thread t1 = new Thread(t);
t1.start();
Thread.currentThread().sleep(2000);
t1.interrupt();
}
}
class TestThread implements Runnable{
TestThread(){
}
public void run(){
// while(!Thread.currentThread().isInterrupted()){
while(true){
System.err.println("ddddddddddddddddddddddd");
}
}
}
我们发现,即使调用了interrupt()方法,console还在不停地打印。如果如下:
public class Test {
public static void main(String[] args) throws InterruptedException {
TestThread t= new TestThread();
Thread t1 = new Thread(t);
t1.start();
Thread.currentThread().sleep(2000);
t1.interrupt();
}
}
class TestThread implements Runnable{
TestThread(){
}
public void run(){
while(!Thread.currentThread().isInterrupted()){
// while(true){
System.err.println("ddddddddddddddddddddddd");
}
}
}
我们发现在打印了一段时间之后,停止了打印。也就是说,interrupt()方法本身改变了一个状态位,然后我们通过状态位去控制相应的逻辑,这其实和之前我提到的全局变量的方法差不多。我们再看下面一段代码:
public class Test {
public static void main(String[] args) throws InterruptedException {
TestThread t= new TestThread();
Thread t1 = new Thread(t);
t1.start();
t1.interrupt();
}
}
class TestThread implements Runnable{
TestThread(){
}
public void run(){
try {
Thread.currentThread().sleep(100000);
} catch (InterruptedException e) {
System.err.println("end !!!");
e.printStackTrace();
}
}
}
这段代码的结果是(在控制台的打印):
end !!!
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method) at TestThread.run(TestThread.java:8) at java.lang.Thread.run(Unknown Source)
这也就验证了我们上面的话:如果当线程被阻塞,就无法检查中断状态。这是产生InterruptedException异常的地方。当一个被阻塞的线程(调用sleep或wait)上调用interrupt方法时,阻塞调用将会被这个异常中断。我们可以在捕获异常的地方进行相应的处理。
目前来看,Java中是没有直观的中断一个线程的方法的,它的中断都是建立在我们对自己代码逻辑的控制上的。
我们写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock等如果用的不好,不仅不能提高性能,还可能带来灾难。 所以在这里我就先不记录ReentrantLock的相关知识了。
下面就来描述一下synchronized关键字:(这里参考了这篇文章java synchronized详解,谢谢作者~)
当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。
(一) synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
public synchronized void a();
synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。 在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
(二) synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:
synchronized(syncObject) {
//允许访问控制的代码
}
synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。
一、当两个线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
volatile关键字在修饰变量之后,就意味着这个变量是随时会被其他线程修改的。也就是说,线程在每次使用这个变量的时候,都是从一个地方(主存)去取,也就是说保证线程得到当前变量的最新值。
什么是线程池呢?先准备好若干个线程等待执行任务,只要任务来了,存放线程的容器就是线程池,然后从池子拿出一个线程来让这个线程进行服务。
使用线程池的好处:
1.减少在创建和销毁线程上所花的时间以及系统资源的开销
2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存
如果一次性会创建多个线程,我们可以考虑使用线程池。
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。如果是做计时器的话,可以考虑使用它。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
这里只是一些Java知识的简单描述,参考了书上和一些优秀的文章,其实对于我来说就是一个笔记,以供查阅。