1.线程和进程的概念
1.1.进程(Process):拥有独立的内存空间,每个独立执行的程序称为进程
1.2.线程(Thread):线程是一个程序内部的一条执行路径,Java虚拟机允许应用程序并发地运行多个执行线程
1.3.线程和进程的区别
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换开销大
线程: 同一进程内的线程共享代码和数据空间,线程切换的开销小
多进程: 在操作系统中能同时运行多个任务(程序)
多线程: 在同一应用程序中多条执行路径同时执行
2.线程的创建与启动
第一种:继承Thread类,重写run方法
第二种:实现Runnable接口,重写run方法
3.两种创建方式的比较
3.1使用Runnable接口
还可以从其他类继承;
保持程序风格的一致性。
3.2直接继承Thread类
不能再从其他类继承;
编写简单,可以直接操纵线程
使用实现Runnable接口的方式创建线程时可以为相同程序代码的多个线程提供共享的数据。
4.线程小结
4.1.Java的线程是通过java.lang.Thread类来实现的。
4.2.当程序启动运行时,JVM会产生一个线程(主线程),主方法(main方法)就是在这个线程上运行的。
4.3.可以通过创建Thread的实例来创建新的线程。
a.每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。
b.通过调用Thread类的start()方法来启动一个线程。线程进入Runnable(可运行)状态,它将向线程
调度器注册这个线程。
c.调用start()方法并不一定马上会执行这个线程,正如上面所说,它只是进入Runnable 而不是
Running。
4.4.每个线程都有一个优先级,一个标识名,多个线程可以同步。没有指定标识名时,会自动为其生成一个
高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程
中运行的代码创建一个新 Thread
对象时,该新线程的初始优先级被设定为创建线程的优先级,并且
当且仅当创建线程是守护线程时,新线程才是守护程序。
4.5.Java虚拟机退出的情况
- 调用了
Runtime
类的exit
方法,并且安全管理器允许退出操作发生。 - 非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到
run
方法之外的异常
注意,不要直接在程序中调用线程的run()方法。
5.线程的基本使用方法
- static Thread currentThread ();//返回当前正在执行的线程对象的引用
- static void yield ();//暂停当前线程一下下。
- static void sleep (long millis);//线程睡眠(暂停执行)
- 上面三个都是静态方法,其他的一般不是
- void start();//使线程启动,进入runnable状态。
- boolean isAlive();//判断该线程是否处于活动状态
- boolean isDaemon();//判断是否是守护线程
- Thread.State getState();//返回该线程的状态
- void setName(String name);//改变线程名称
- void setDaemon(boolean on);//true则为守护进程,false为用户进程。
6.线程的调度
- 线程睡眠: Thread.sleep(long miao);//使线程进入阻塞状态,睡眠结束后进入就绪(Runnable)状态
- 线程让步: Thread.yield();//暂停当前执行线程,将执行机会让给同等或更高优先级的线程执行,但是这个时间不能控制.是不确定的.只是让当前线程暂停一下下.
- 线程加入: join();//throws InterruptedException 。当前线程调用另一个线程的join方法,则当前线程
转入waiting状态,直到调用join()方法的线程执行完毕,当前线程再由阻塞转为就绪状态。
4.线程等待:Object类中的wait()方法,导致当前线程等待,直到其他线程调用此对象的notify()方法或notifyAll
()方法唤醒该线程。
5.线程唤醒:Object类的notify()方法,唤醒在。此对象监视器上等待的某个单个线程。选择是随意的。、
Object类的notifyAll()方法,唤醒在此对象监视器上等待的所有线程。
注意:这三个方法(wait,notify,notifyAll)只能在被同步化(synchronized)的方法或代码块中使用。
7.线程停止
- 如果run方法中执行的是一个重复执行的循环,可以提供一个标志来控制循环是否执行。
- 如果线程是因为执行了sleep()方法或wait()方法而进入了阻塞状态,此时想停止它的话可以使用interrupt()方法
程序会抛出InterruptException异常。
- 如果程序因为输入/输出的等待而阻塞,基本上是必须等待输入/输出的动作完成才能离开阻塞状态。无法通过interrupt
方法使得线程离开run方法,要想离开只能引发一个异常。
7.1 线程的终止
- 因为run方法的正常退出而自然死亡
- 因为一个没有捕获的异常终止了run方法而意外死亡
8.线程状态的转换
创建(new)---->[阻塞解除]----->就绪(runnable)---->运行(running)--->[阻塞]---->死亡(dead)
新建状态(new):新创建一个线程对象。
就绪状态(runnable):其他线程调用了该线程的start方法后,该状态的线程位于可运行线程池中,变得可运行,等待
获取cpu使用权。获取了cpu,执行程序代码时也属于就绪状态。
运行状态(running):执行run方法。
阻塞状态(blocked):阻塞状态是因为线程由于某个原因而放弃cpu的使用权,而暂时停止运行,直到线程进入就绪状态
才有机会进入运行状态。阻塞情况有三种:
1.同步阻塞:运行的线程在获取对象的同步锁时,如果该同步锁被其他线程占用,则jvm将它放入锁池中
2.等待阻塞:运行的线程执行wait的方法时,jvm会把该线程放入等待池中。
3.其他阻塞:运行的线程执行sleep()方法或join()方法,或发出I/O请求时,将进入阻塞状态
死亡状态(dead):线程执行完或因异常退出run方法,线程结束生命周期。
注意:调用yield()方法并没有进入阻塞(blocked)状态
9.调整线程的优先级
Java提供了一个线程调度器来监控程序中启动后进入就绪状态的所有线程。优先级高的线程会获得较多的运行机会
Java线程的优先级用int表示,取值1~10,Thread类的三个静态常量:MAX_PRIORITY=10,MIN_PRIORITY=1
NORM_PRIORITY=5(默认优先级取值为5).
Thread类的setPriority(),getPriority()方法可以设置和获取线程的优先级
注意:优先级高的只是得到cpu的机会概率多一些
10.线程同步(同步锁)
在Java语言中,引入了对象互斥锁的概念,保证共享数据操作的完整性,每个对象都对应于一个可称为"互斥锁"
的标记,这个标记保证在任何时刻,只能有一个线程访问对象。
关键字synchronized用来与对象互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任何时刻
只能由一个线程访问。
注意:同步锁即synchronized(参数)里的参数必须是引用类型,如成员变量(必须是引用类类型:String)
成员方法(方法的返回值必须是引用类型,不能是int,char),this(即本身对象),this.getClass()//类
11.Synchronized关键字
同步代码块: synchronized(this){需要同步的代码;}
同步方法:public synchronized void method(){...}
12.死锁问题
有两个线程,x和y,x线程拿到了A对象锁后执行代码,y线程拿到了B对象锁后执行代码,如果这时x线程还要取
B对象锁,而y线程也要取A对象锁,则大家拿着各自的锁不放,就会导致死锁的问题。
解决死锁的办法之一就是:加大锁的粒度。
13.多线程安全问题
当run()方法中的代码操作到了成员变量(共享数据)时,就会出现多线程安全问题(线程不同步问题)
编程技巧:在方法中尽量少操作成员变量,多使用局部变量。
14.容器类的线程安全
大多数的容器类默认都没有考虑线程安全问题,程序必须自行实现同步:
a.用synchronized来锁住这个对象
synchronized (list){ list.add(...) ;}
b.用Collections类的synchronizedXXX()方法返回一个同步化的容器对象
List list=Collections.synchronizedList(new ArrayList());
这种方式在迭代时仍要用synchronized修饰。
List list = Collections.synchronizedList(new ArrayList());...synchronized(list) {Iterator i = list.iterator(); while (i.hasNext()) {foo(i.next());}}
JDK 5.0之后,新增了java.util.concurrent这个包,其中包括了一些确保线程安全的容器类,
如ConcurrentHashMap,CopyOnWriteArrayList,CopyOnWriteArraySet,它们在效率
与安全性上取得了较好的平衡。
总结:
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
JDK1.5版本提供了一些新的对象,优化了等待唤醒机制。
1,将synchronized 替换成了Lock接口。
将隐式锁,升级成了显示锁。
Lock
获取锁:lock();
释放锁:unlock();注意:释放的动作一定要执行,所以通常定义在finally中。
获取Condition对象:newCondition();
2,将Object中的wait,notify,notifyAll方法都替换成了Condition的await,signal,signalAll。
和以前不同是:一个同步代码块具备一个锁,该所以具备自己的独立wait和notify方法。
现在是将wait,notify等方法,封装进一个特有的对象Condition,而一个Lock锁上可以有多个Condition对象。
线程中一些常见方法:
setDaemon(boolean):将线程标记为后台线程,后台线程和前台线程一样,开启,一样抢执行权运行,
只有在结束时,有区别,当前台线程都运行结束后,后台线程会自动结束。
join():什么意思?等待该线程结束。当A线程执行到了B的.join方法时,A就会处于冻结状态。
A什么时候运行呢?当B运行结束后,A就会具备运行资格,继续运行。
加入线程,可以完成对某个线程的临时加入执行。
注意:启动一个jvm时,会有一个前台线程:主线程(执行main方法),还有一个后台线程:垃圾回收机制线程。