Tread和Runnable
定义线程
实现Runnable接口,重写run()方法
继承Thread类,重写run()方法
启动线程
Runnable tortoise=new Tortoise();
Thread thread1=new Thread(tortoise);
thread1.start();
Thread thread1=new Tortoise();
thread1.start();
线程生命周期
daemon线程
主线程
会从main()方法开始执行,直到main()方法结束后停止jvm.如果主线程
中启动了额外线程
,则主线程
默认会等到被启动的所有额外线程
都执行完run()方法才会终止jvm.
public class Daemon implements Runnable{
@Override
public void run() {
while(true){
System.out.println("TheViper");
}
}
public static void main(String[] args){
Thread thread=new Thread(new Daemon());
//thread.setDaemon(true);
thread.start();
}
}
TheViper
TheViper
TheViper
...
控制台会不停的打印。如果把注释去掉,thread.setDaemon(true),则在所以非daemon线程都结束时,jvm自动终止。
默认所有从daemon线程产生的线程也是daemon线程。
线程基本状态图
启动线程后,基本状态可分为可执行(runnable),阻塞(blocked),执行中(running).
在一个时间点上,一个cpu只能执行一个线程,只是cpu会不断切换线程。可以用Thread的setPriority()方法设置线程的优先级,设定值1-10,默认为5.优先级越高的线程,越有机会抢占cpu.
线程进入blocked状态可能的情况:
调用Thread.sleep()方法
进入synchronized前,对竞争对象的锁定
调用wait()方法阻断
等待io完成
...
一个处于blocked状态的线程,可由另一个线程调用interrupt()方法
,让它离开blocked状态。
public class Daemon implements Runnable{
@Override
public void run() {
System.out.println("TheViper");
try {
Thread.sleep(3000);//进入blocked状态
} catch (InterruptedException e) {
System.out.println("离开blocked状态");
}
}
public static void main(String[] args){
Thread thread=new Thread(new Daemon());
thread.start();
thread.interrupt();
}
}
TheViper
离开blocked状态
安插线程
假设线程A正在运行,这时线程B想占用cpu运行,等它运行完后,继续运行线程A.这种情况可以使用join()方法
.
未使用join()
public class Daemon implements Runnable{
@Override
public void run() {
System.out.println("线程开始");
try {
Thread.sleep(3000);
System.out.println("线程执行");
} catch (InterruptedException e) {
System.out.println("离开blocked状态");
}
System.out.println("线程结束");
}
public static void main(String[] args){
Thread thread=new Thread(new Daemon());
thread.start();
System.out.println("主线程开始");
}
}
主线程开始
线程开始
线程执行
线程结束
使用join()
public class Daemon implements Runnable{
@Override
public void run() {
System.out.println("线程开始");
try {
Thread.sleep(3000);
System.out.println("线程执行");
} catch (InterruptedException e) {
System.out.println("离开blocked状态");
}
System.out.println("线程结束");
}
public static void main(String[] args) throws InterruptedException{
Thread thread=new Thread(new Daemon());
thread.start();
thread.join();
System.out.println("主线程开始");
}
}
线程开始
线程执行
线程结束
主线程开始
程序启动后就开始运行主线程
(因为新建线程使用了sleep(),让新建线程进入blocked状态,主线程可以占用cpu),对新建的线程使用join()
,将其加入到主线程
后,原本应该一来就运行的主线程
,现在需要等到后面新建的线程执行完后,才能继续执行。
如果加入的线程处理时间太久,可以在join()时指定时间,如join(10000),表示加入的线程最多只能处理10秒。
停止线程
线程完成run()方法后,就进入dead状态。这时不能再次调用start()方法。
Thread类上的stop()方法是过时方法。
如果要停止线程,最好自行操作,让线程跑完应有的流程,而不是调用stop()方法
...
private boolean isContinue=true;
public void stop(){
this.isContinue=false;
}
public void run(){
while(isContinue){
...
}
}
...
synchronized
public synchronized void add(Object o){...}
每个对象都有一个内部锁定。被标示为synchronized的区块会被监控,任何线程要执行synchronized区块都必须先获得指定的对象锁定。
如果线程A已获得对象锁定开始执行sychronized区块,线程B也想执行synchronized区块,线程B会因为无法获得对象锁定而进入等待对象锁定状态,直到线程A释放锁定(如执行完synchronized区块)。
在方法上标示sychronized,则执行方法必须获得该实例的指定。
实际上等待对象锁定时,也会进入线程的blocked状态
synchronized不仅可以声明在方法上,也可以描述句方式使用
public void add(Object o){
synchronized(this){
...
}
}
List<String> list=new ArrayList<String>();
synchronized(list){
...
list.add("TheViper");//让ArrayList线程安全
}
这种方式不用锁定整个方法,只锁定会发生竞争状况的区块。获得锁定的线程执行完这个区块后,会立即释放锁定,其他线程就有机会再竞争这个锁定.
相比于将整个方法声明为synchronized,这种方式更有效率。
可以提供不同的对象作为锁定的来源
private Object lock1=new Object();
private Object lock2=new Object();
public void method1(){
synchronized(lock1){
data1++;
...
}
}
public void method2(){
synchronized(lock2){
data2++;
...
}
}
在两个synchronized区块里,没有公共的数据,方法。当有一个线程执行method1()而另一个线程执行method2()时,并不会引起共享存取问题,而且一个线程不会因为另一个线程获得锁定而阻塞.
java的synchronized提供的是可重入同步
,也就是线程获得某对象锁定后,若执行过程中又要执行synchronized,而这个锁定对象的来源又和前面的是同一个,则可以直接执行。
死锁
public class Resource {
synchronized void doSome(){
}
public synchronized void deal(Resource res){
res.doSome();
}
}
public class TheadDemo extends Thread{
Resource res1;
Resource res2;
TheadDemo(Resource res1,Resource res2){
this.res1=res1;
this.res2=res2;
}
@Override
public void run(){
for(int i=0;i<10;i++)
this.res1.deal(this.res2);
}
public static void main(String[] args){
Resource res1=new Resource();
Resource res2=new Resource();
Thread thread1=new TheadDemo(res1,res2);
Thread thread2=new TheadDemo(res2,res1);
thread1.start();
thread2.start();
}
}
这段代码可能出现死锁,也可能不会。
如果出现的话,原因在于
Thread thread1=new TheadDemo(res1,res2)=>this.res1.deal(this.res2)时,thread1获得res1的锁定
Thread thread2=new TheadDemo(res2,res1)=>this.res2.deal(this.res1),thread2获得res2的锁定
thread1线程res2.doSome(),准备获得res2的锁定,但是res2的锁定被thread2线程占用,于是thread1线程进入blocked状态
thread2线程res1.doSome(),准备获得res1的锁定,但是res1的锁定被thread1线程占用,于是thread2线程进入blocked状态
volatile
synchronized所标志区块的特点:
互斥性:synchronized区块在一个时间点上只能有一个线程执行它
可见性:线程离开synchronized区块后,另一个线程面对的是上一个线程改变后的对象状态
在java中对于可见性的要求,可以使用volatile达到变量范围。
没用volatile,synchronized
public class Variable {
static int i=0,j=0;
static void deal(){
i++;
j++;
}
static void print(){
System.out.println("i="+i+" j="+j);
}
}
public class VariableTest1 extends Thread{
@Override
public void run(){
while(true)
Variable.print();
}
}
public class VariableTest extends Thread{
@Override
public void run(){
while(true)
Variable.deal();
}
public static void main(String[] args){
Thread t1=new VariableTest();
Thread t2=new VariableTest1();
t1.start();
t2.start();
}
}
...
i=638864948 j=638864993
i=638866963 j=638867006
i=638868897 j=638868941
i=638870928 j=638870974
...
可以看到j大于i,甚至可以远大于i.原因在于为了效率,线程可以快取变量的值。
t2调用Variable.print()从共享内存中取到变量i
的值,并存储在自己的内存空间,如果此时cpu切换线程至t1,并不断的执行Variable.deal()多次,再切回t2,取得变量j
的值,然后和i值一起输出。
如果在deal()和print()方法上标志synchronized,则t1每次调用deal()方法时,t2都必须等到t1释放锁定才能调用print()方法.t2每次调用print()方法也一样。
这种情况下,输出的i
一定等于j
.
在变量上声明volatile,表示变量是不稳定的,易变的。这样变量就可以在多线程情况下存取,保证了变量的可见性.
换句话说,如果有线程修改了变量的值,另一个线程一定可以看到被修改的变量。
被标示为volatile的变量,不允许线程快取,变量的存取一定是在共享内存中进行。
下面将上面例子中的i,j声明为volatile.
volatile static int i=0,j=0;
...
i=34456445 j=34456448
i=34456620 j=34456623
i=34456789 j=34456791
i=34456959 j=34456961
...
可以看到没有出现j
远大于i
的情况,都是i
略小于j
.
事实上,i
和j
的关系可以是大于,等于,小于三种之中的任一种。
i
大于j
i
已经改变,但是j
仍然是上一次操作的j
.i
小于j
第一次输出时,保存i
,然后j++
,第二次输出的是没有修改过的i
和修改过的j
.i
等于j
输出的都是共享内存中修改过的值.
由此可见,volatile保证的是单一变量可见性,线程对变量的存取一定是在共享内存中进行,不会在自己的内存空间中快取变量。线程对共享内存中变量的存取,另一个线程一定看得到。
等待与通知
wait()
,notify()
,notifyAll()
是Object类中的方法,可以通过这三个方法控制线程释放对象的锁定,或者通知线程参与锁定的竞争.
线程进入synchronized区块前,要先获得指定对象的锁定。而在执行synchronized区块代码时,若调用锁定对象的wait()方法,线程会释放对象锁定,并进入对象等待集合,其他线程这时可以竞争对象锁定,取得锁定的线程可以执行synchronized区块代码。
处于等待集合中的线程不会参与竞争cpu.wait()方法可以指定等待时间,时间到之后,线程会参与竞争cpu.如果指定时间为0或没指定,则线程会一直等待,直到被中断(interrupt)或通知(notify)可以参与竞争cpu.
锁定的对象调用notify()方法,会对象等待集合中随机通知一个线程参与竞争cpu.
如果调用的是notifyAll()方法,则等待集合中的所有线程都会参与竞争cpu.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。