java基础之线程(重点)
来源:互联网 发布:福建广播网络电视台 编辑:程序博客网 时间:2024/06/04 01:38
(一)线程与进程
线程本质:线程是一个程序里面不同的执行路径,即一个执行流程
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
注意:进程是一个静态的概念,机器上的一个class文件或者exe文件就是一个进程,比如,程序的执行,首先将class文件加载到代码区,这时进程还没有开始执行,但进程已经产生了。平常说的进程的执行指的是主线程执行即main()方法的执行。
线程:线程是进程中的一个执行流程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
(二)线程的创建与执行
1、线程有三种创建方法
第一种方法:继承java.lang.Thread类,子类必须重写run()方法,该方法作为新线程的入口点,同时,必须调用start()方法才能执行。(start()方法是通知cpu,线程我准备好了)
构造方法:publicThread(Runnable target)
注意:Thread类实际上是实现了Runnable接口的类。
第二种方法:实现java.lang.Runnable接口,实现public void run()方法,该方法用于定义线程的运行体。(推荐使用)
注意:使用Runnable接口可以为多个线程提供共享的数据。
在run()方法中可以使用Thread类的静态方法:currentThread()方法:获取当前线程的引用
第三中方法:
1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i); if(i==20) { new Thread(ft,"有返回值的线程").start(); } } try { System.out.println("子线程的返回值:"+ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception { int i = 0; for(;i<100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } return i; } }
1. 采用实现 Runnable、Callable 接口的方式创见多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
(三)线程的状态
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
- 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
isAlive()方法:判断线程是否还活着,即线程是否终止
getPriority()方法:获得线程的优先级数值。
setPriority()方法:设置线程的优先级数值。
注意:线程的优先级无法保证线程的执行次序,只不过,优先级高的线程获得CPU资源的概率比较大,获得CPU的执行时间越多。优先级低的也有机会执行。优先级用1-10之间的整数表示,默认是5(可省略),数值越大,优先级越高。
Thread.sleep():将当前线程睡眠指定毫秒数
join()方法:调用某线程的该方法,将当前线程和该线程合并,即等待该线程结束,再恢复当前线程的运行
yield()方法:让出CPU,当前线程进入就绪队列等待调度。
wait()方法:当前线程进入阻塞状态,让出CPU控制权,当前线程进入对象的wait pool。
notify()方法、notifyAll()方法:唤醒对象的wait pool 中的一个或者所有等待的线程
(重点)sleep()和wait()的区别
1、每个对象都有一个锁来控制同步访问,Synchronized关键字可以和对象的锁交互,来实现同步方法或同步块。sleep()方法正在执行的线程主动让出CPU(然后CPU就可以去执行其他任务),在sleep指定时间后CPU再回到该线程继续往下执行(注意:sleep方法只让出了CPU,而并不会释放同步资源锁!!!);wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度);
2、sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用;
3、sleep()是线程线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态;
(四)sleep / join / yield 方法
1、sleep(long millis)方法:是Thread类的静态方法,睡眠指定的毫秒数。一秒=1000毫秒
注意:sleep(long millis)方法是静态方法,在那个线程中调用该方法,就让那个线程睡眠。
interrupt()方法:中断线程,会抛出一个异常,进行处理(太粗暴,不推荐使用)
stop()方法:中断线程,更加粗暴,直接杀死线程。(已过时,尽量不要使用)(重点)
注意:中断是一个状态,interrupt()方法只是将这个状态置为true而已。它并不像stop()方法那样会中断一个正在运行的线程,线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。
Thread.interrupted()检查当前线程是否发生中断,返回boolean
synchronized在获锁的过程中是不能被中断的。
因此,正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体
控制线程停止的方法:
在线程类中定义一个Boolean类型的属性,对于while()循环,将其Boolean类型的属性设为false即可。
2、join()方法:优先执行完当前线程
join()方法:表示先执行完该线程(即调用join方法的线程),再执行其他线程。
3、yield()方法:暂停当前正在执行的线程对象,并执行其他线程
(五)线程同步:共享资源一定要设置好同步,防止其他未设置同步的方法操作共享资源,引发冲突,造成数据错误(重点)
JDK1.5之前,采用关键字synchronized和监视器机制来实现线程同步,JDK1.5之后,采用锁的机制实现线程同步。
(1) synchronized 修饰非静态方法,此时在这个方法体内的就有了 ”监视器“,它是所在类的对象.
synchronized void func(){}
(2) synchronized 修饰静态方法,此时在这个方法体内的也有了 ”监视器“,它是所在类的对象.
static synchronized void func(){}
(3) synchronized 修饰语句块,此时需要一个参数,而这个参数就作为“ 监视器 ”。
synchronized(Object o){ }
监视器就是一个对象,不过他是有条件的,线程获取监视器才能执行synchronized修饰的方法或者代码。
java.util.concurrent.locks包提供了锁和等待条件的接口和类, 可用于替代JDK1.5之前的同步(synchronized)和监视器机制(主要是Object类的wait(), notify(), notifyAll()方法).
所谓互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问.
而现在, Lock提供了比synchronized机制更广泛的锁定操作。
Lock和synchronized机制的主要区别:
- synchronized机制提供了对与每个对象相关的隐式监视器锁的访问, 并强制所有锁获取和释放均要出现在一个块结构中, 当获取了多个锁时, 它们必须以相反的顺序释放. synchronized机制对锁的释放是隐式的, 只要线程运行的代码超出了synchronized语句块范围, 锁就会被释放.
- Lock机制必须显式的调用Lock对象的unlock()方法才能释放锁, 这为获取锁和释放锁不出现在同一个块结构中, 以及以更自由的顺序释放锁提供了可能. 以下代码演示了在不同的块结构中获取和释放锁。
2、线程安全:多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问,必须等该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
注意:对全局变量和静态变量操作在多线程模型中会引发线程安全问题。
3、并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。相当于两个人同时做事情。
4、并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。相当于两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
注意:需要将事务和线程同步区分开,二者是不同层面的术语,应用场景不同。
多线程为了提高应用的执行效率而设置的,事务为保证一个操作的原子性而设置的
形象说明:多线程相当于多个人同时工作,在工作的过程中,大家可能都需要上厕所(厕所只有一个),这时就需要进行排队,每个人依次上厕所,第一个上厕所的人需要上锁,防止其他人进入,用完解锁,下一个人再用。这就是多线程和线程同步。
(六)使用synchronized实现线程同步
synchronized关键字的用法(是关键字,内置特性)(重点)
1、synchronized的用法(作用:同一时间只能一个线程执行synchronized修饰的代码或者方法)
synchronized代码块使用格式:synchronized(synObject){ //程序代码 }
注意:synObject可以是this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。
synchronized方法:就是将synchronized放在方法声明中,表示整个方法是同步方法。
例如:public synchronized void add(String str){ //方法体 }
注意:每个类也会有一个锁(类锁),它可以用来控制对static数据成员的并发访问。
2、Lock接口的用法(ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。)
问题:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
注意:Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。- public interface Lock {
- void lock();
- void lockInterruptibly() throws InterruptedException;
- boolean tryLock();
- boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
- void unlock();
- Condition newCondition();
- }
<1>.lock():如果锁已被其他线程获取,则进行等待。采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
tryLock(long time, TimeUnit unit): 和tryLock()方法是类似的,区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用thread B.interrupt()方法能够中断线程B的等待过程。
注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
总结:1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
(七)死锁示例
类似案例:五个科学家吃放,互相占用筷子,最后饿死。
对于多线程,最基本的原则是:线程安全大于性能,一定要保证线程安全。
public class DealThread implements Runnable{public int flag = 1;static static Object o1 = new Object(),o2 = new Object(); //注意这里是静态的对象@Overridepublic void run() {System.out.println("flag="+flag);if (flag == 1) {synchronized (o1) {try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}synchronized (o2) {System.out.println("1");}}}if (flag == 0) {synchronized (o2) {try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}synchronized (o1) {System.out.println("0");}}}}public static void main(String[] args) {DealThread d1 = new DealThread();DealThread d2 = new DealThread();d1.flag = 1;d2.flag = 0;Thread t1 = new Thread(d1);Thread t2 = new Thread(d2);t1.start();t2.start();}}
参考文档:http://blog.csdn.net/g893465244/article/details/52538598
- java基础之线程(重点)
- java学习之路---线程(重点)
- java基础之集合(重点)
- java基础之IO(重点)
- Java基础——Java重点基础之多线程(一)
- Java基础——Java重点基础之多线程(二)
- Java基础——Java重点基础之多线程(三) 注意
- Java入门基础之重点补充
- Java入门基础之重点练习
- Practical Java(重点版)之多线程
- Java基础——Java重点基础之集合框架(一)
- Java基础——Java重点基础之集合框架(二)
- Java基础——Java重点基础之集合框架(三)
- Java基础——Java重点基础之集合框架(四)
- Java基础——Java重点基础之IO流(一)
- Java基础——Java重点基础之IO流(二)
- Java基础——Java重点基础之IO流(三)
- Java基础——Java重点基础之反射
- 安卓扫描车牌识别的功能SDK
- 【软考】-防火墙
- Distances to Zero
- 【ZJOJ 5454】【NOIP2017提高A组冲刺11.5】仔细的检查
- 并发编程(11)-Future模式
- java基础之线程(重点)
- 生成波动图
- java se--2.数组-2.一维数组
- 训练日记
- Java字符串的操作
- 网络七层
- 一个获取鼠标位置和键盘按钮的图形化界面一个 工程文件
- 从体测到健康问题的思考转变
- Go语言实现二叉搜索树