线程那点事

1. 在多任务系统中,每个独立执行的程序被称为进程,也就是“正在进行的程序”。我们现在使用的操作系统一般都是多任务的,即能够同时处理多个应用程序,实际情况是,操作系统负责对CPU等设备的资源进行分配和管理,虽然这些设备某一时刻只能做一件事,但以非常小的时间间隔交替执行多个程序,就可以给人以同时执行多个程序的感觉。

2. 一个进程中又可以包含一个或多个线程,一个线程就是一个程序内部的一条执行线索,如果要一程序中实现多段代码同时交替运行,就需产生多个线程,并指定每个线程上所要运行的程序代码段,这就是多线程。

                                                            

用Thread类创建线程:

        1. 要将一段代码在一个新的线程上运行,该代码应该在一个类的run函数中,并且run函数所在的类是Thread类的子类,倒过来看,我们要实现多线程,必须编写一个继成了Thread类的子类,子类要覆盖Thread类中的run函数,在子类的run函数中调用想在新线程上运行的程序代码。

        2. 启动一个新线程,我们不是直接调用Thread类的子类对象的run方法,而是调用Thread子类对象的start (从Thread类继承下来的) 方法,Thread类对象的start方法将产生一个新的线程,并在该线程上运行该Thread对象中的run方法,根据面向对象的运行时的多态性,在该线程上实际运行的是Thread子类(也就是我们写的那个类)对象中的run方法。

       3. 由于线程的代码段在run方法中,那么该方法执行完成以后线程也就相应的结束了,因而我们可以通过控制run方法中循环的条件来控制线程的结束。

 

后台线程与联合线程

       1. 如果我们对某个线程对象在启动(调用start方法)之前调用了setDaemon(true)方法,这个线程就变成了后台线程。

       2. 对java程序来说,只要还有一个前台线程在运行,这个线程就不会结束,如果一个进程中只有后台线程在运行,这个进程就会结束。

       3. xx.join()的作用是把xx所对应的线程合并到调用xx.join();语句的线程中(合并即将其他线程合并到当前线程中,使的多线程运行变成单线程运行,即当前线程让出CPU给被合并进来的线程,当被合并进来的线程执行完之后才会再次执行当前线程),join方法可以添加一个时间参数,代表合并多少毫秒之后当前线程有与被合并进来的线程进行分离,两个线程各自运行各自的。

   注意:

          程序中的主线程结束掉了并不代表程序已经退出结束,只要还有一个前台子线程在运行,进程都不会退出,即程序运行还没有结束。

 

用Runnable接口来创建线程:

         1. 当使用实现了Runnable接口的类来构造线程时,线程执行时就不再使用Thread类自带的run方法,而是调用Runnable接口中的run方法,作为线程运行的代码。

 

区别:

       1. Runnable接口适合多个相同程序代码的线程去处理同一资源的情况把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想。

       2. 可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能继承Thread类的方式,那么,这个类就只能采用实现Runnable。

       3. 当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。

       4. 事实上,几乎所有多线程应用都可用Runnable接口方式。

       注意:Runnable接口中的run方法是非线程安全的。

 

synchronized块:

       synchronized块后面必须要有一个括号,并且括号中也需要一个对象,然而这个对象可以使任意对象。

synchronized作用和原理:

       任何一个对象都有一个标识位,这个标志位有0和1两种状态值,然而开始状态都为1,当一个线程到达synchronized块时会检查这个标志位的值,如果为1则进入代码块执行并将此状态值由1改为0。若这个标志位的值为0则当前线程阻塞并让出CPU直到这个对象的标志位由0变为1时才开始继续往下执行,当当前线程离开synchronized块时会将对象的状态值由0变为1,此时其他线程就可以进入同步块。

       方法同步的做法只需要在方法修饰符之后加上synchronized关键字即可,该关键字默认使用的同步对象是当前对象,即this。如果在静态方法上加synchronized则此时的同步对象则是类的字节码对象,因为字节码在内存中也是一个对象,即XXXClass.class

       注意:线程同步必须使用同一个监视器(对象锁),否则无法实现线程同步,当有多个线程访问同一个对象或者共享资源时才需要考虑线程安全问题。

 

wait:

       告诉当前线程放弃监视器并进入睡眠状态直到其他线程进入同一监视器并调用notify为止。

notify:

       唤醒同一对象监视器中调用wait的第一个线程,类似银行柜台目前有一个空闲窗口后通知所有排队等待存钱的用户中的第一位可以前来办理业务。

notify All:

       唤醒同一对象监视器中调用wait的所有线程,具有最高优先级的线程首先被唤醒并执行,用于类似某个不定期的活动计划人数报满后,通知所有参加活动的人来参加活动。

 

线程的等待和唤醒过程:

 

总结:查看Thread类的run()方法的源代码,可以看到其实无论是使用Thread还是Runnable接口来实现线程其实都是在调用Thread对象的run方法,如果Thread类的run方法没有被覆盖,并且该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法。源代码如下:

 

Lock&Condition实现线程同步通信

         1. Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。锁是加在代表要操作的资源的类的内部方法中,而不是线程代码中。

         2. 读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是jvm自己控制的,你只要加好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但是不能同时写,那就加读锁,如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就加写锁,总之,读的时候上读锁,写的时候上写锁。

         3. 在等待condition时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为condition应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等。

 

ThreadLocal(线程范围内共享数据):

         1. ThreadLocal的作用和目的是用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时需要共享一份数据,而在另外线程中运行时又共享另外一份数据。

         2. 每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值