用我们最直观的windows使用来谈谈进程线程吧
例如我们常常使用的qq和360都是单独的进程(当然他们貌似会有几个进程,我们理想化一点吧,就认为它们是一个进程)
还记得腾讯和360的那场桌面大战吗?
那其实就是进程通讯,通过一个进程去杀死另一个进程,用的是信号,发送一个kill信号,嘿嘿。
跑题了,我们要说的是线程,下面我们看看java的多线程技术:
进程和线程关系:一个进程里面可以有多个线程(至少一个)
例如:使用QQ聊天的时候,你可以同时和很多好友聊天,每个都是一个线程,也可以一边发送,一边接收,甚至视频语音聊天,都是用多线程来实现的。
如何创建线程:当创建一个应用程序其实就已经创建了一个线程。
当然,我们要说的并不是仅仅使用一个线程,java在程序中另外创建线程的方法有两种
1:继承Thread类
2:使用runnable接口
这两种方法的区别:
继承Thread的类只能起一个线程而使用runnable的接口却可以起多个线程。
详细分析:a:当我们使用继承Thread的类,创建一个对象,然后start多次,其实只是start了一次,而我们创建多个对象对每个对象都进行start的话,每个线程都是独立的,它们单独处理自己的对象。
b:当我们使用runnable接口的时候,我们可以实现多个线程对一个资源的处理。
所以总结下runnable相对于Thread的优点
1:适用多个线程处理相同资源。
2:避免由java的单继承带来的局限性。
3:有利于代码的健壮性,代码能被多个线程共享,代码与数据时独立的。
(见Java就业培训教程 ---张孝详)
后台线程
我们可以回想一下:我们用前面说的方法启动一个线程,在没有达到线程停止或者退出的条件,我们的线程会一直执行下去,对吧。
这里引入一个后台线程的例子
public class TestExCeption
{
public static void main(String args[])
{
TestThread t = new TestThread();
Thread tt = new Thread(t);//
tt.setDaemon(true);//使线程变为后台线程。
tt.start();//
}
}
class TestThread implements Runnable
{
int i=1000000;
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName()+"the value of i is "+i--);
}
}
}
运行线程,结果为:
Thread-0the value of i is 1000000
Thread-0the value of i is 999999
Thread-0the value of i is 999998
Thread-0the value of i is 999997
Thread-0the value of i is 999996
Thread-0the value of i is 999995
Thread-0the value of i is 999994
Thread-0the value of i is 999993
到这里程序就已经结束退出了
我们的后台线程启动了一个 while(true)的死循环,但是并没有一直执行,而是结束了。
可想而知,导致这样的结果是由于我们把线程变成了后台线程。然而我们的后台线程还没有执行完程序就停止结束了,所以得出结论只有后台线程运行时,进程结束。
联合线程
联合线程其实就是join()方法的调用,在我们调用join()方法后,线程会进入等待,知道那个线程结束后,调用jion()的线程才继续开始。
join()有三种格式join(),join(long millis),和join(long millis,int nanos)三种方法,无参的是一直等待,第二个指定时间精确到毫秒,第三个是指定时间精确到纳秒
基础概念说了这么多,我们看看怎么用吧,亲:
多线程技术能够同时(我们认为是同时)进行多个操作,大大的提升了cpu的工作效率。
我们还是看张孝祥老师书上的多线程卖票的例子(因为我努力想使线程出错,结果却始终都是对的,很尴尬)
class TestThread implements Runnable
{
private int tickets = 100;
String str = new String("");
public void run()
{
while(true)
{
if(tickets > 0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
System.out.print(e.getMessage());
}
System.out.println(Thread.currentThread().getName()+"the value oftickets is "+tickets--);
}
}
}
}
运行结果
Thread-0the value of i is 2
Thread-1the value of i is 1
Thread-2the value of i is 0
Thread-3the value of i is -1
可见,我们只需要 >0 的数 ,可是却打印出了 0 和 -1;
我们想一下为啥?
原因分析,当我们执行到 tickets = 1 的时候,有三个线程同时去访问了tickets这个变量,那时候tickets = 1;
满足 >0 的条件,然后那三个线程分别对tickets进行了 --操作,导致出现了0和-1.到现实生活中这就是一个致命的错误了。
想想,当你春节时候拼死拼活的抢到一张票,结果却是一张不存在的票,那就只能唱算你狠了,哈哈,开个玩笑。
当然,发现错误我们就必须想法子去纠正它
1:同步代码块
加synchronized()至于该如何加我就不贴了,好像在抄书似的,呵呵。
加完运行:
Thread-2the value of i is 5
Thread-2the value of i is 4
Thread-2the value of i is 3
Thread-2the value of i is 2
Thread-2the value of i is 1
结果正确了,哈哈
2同步函数
在函数前加上synchronized关键字
当然结果也是正确的,我都运行过的,可以打包票的哦。
写的有点头大了,休息一下
3 代码块和函数同步
这个是让代码块和函数监视同一个对象(这个没太整明白,还在进一步分析中)
最后留下来的是死锁问题
多线程程序中最要命的一部分。
死锁形成条件:
张老师书中举例子:
A有一只筷子一把刀,B有一只筷子一把叉子。A要求B先给自己筷子,才给B刀子。B要求A先给刀子,才给筷子,于是一直僵持,大家最后都吃不上饭。
这样就是线程死锁了。抛开问题不说,如果是我,我就直接用手,哈哈。
言归正传,下面我们分析下死锁形成的条件吧:
1:资源互斥(只有一双筷子和一副刀叉,如果有很多,完全可以叫服务员再拿,肯定能吃上饭)
2:每个线程都占有一定资源(A有筷子和刀子,B有筷子和叉子,但是没有完全占有)
3:每个线程都试图抢占资源(A希望先得到筷子,B希望先得到刀子)
4:循环等待(大家僵持住,一直等待,不让步)
这样两个线程就死锁了。
死锁的条件总共没有几个字,相信智商再低的同志也能背下来,但是学以致用就非常困难了。
非常遗憾的一点是我至今没有见过死锁问题,还是拿张老师的例子吧
class A
{
synchronized void foo(B b)
{
String name = Thread.currentThread().getName();
System.out.println(name+" entered A.foo ");
try
{
Thread.sleep(1000);
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
System.out.println(name+" Trying to call B.last ");
b.last();
}
synchronized void last()
{
System.out.println(" inside A.last ");
}
}
class B
{
synchronized void bar(A a)
{
String name = Thread.currentThread().getName();
System.out.println(name+" entered B.bar ");
try
{
Thread.sleep(1000);
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
System.out.println(name+" Trying to call A.last ");
a.last();
}
synchronized void last()
{
System.out.println(" inside A.last ");
}
}
class Deadlock implements Runnable
{
A a = new A();
B b = new B();
Deadlock()
{
Thread.currentThread().setName("MainThread");
new Thread(this).start();
a.foo(b);
System.out.println("back in main thread");
}
@Override
public void run() {
Thread.currentThread().setName("RacingThread");
b.bar(a);
System.out.println(" back in other thread ");
}
public static void main(String[] args)
{
new Deadlock();
}
}
太长了,不过为了见识一下死锁,也豁出去了,敲了半天,运行真的死锁了,先不看张老师的解答,自己尝试定位下呢。
运行结果如下
MainThread entered A.foo
RacingThread entered B.bar
MainThread Trying to call B.last
RacingThread Trying to call A.last
我们可以看到是在调用A.last后出现的问题。
那为什么会出现问题呢,我们进一步看下,当我们第一次执行A.foo(),函数进入sleep状态,然后调用B.bar(),也进入了sleep状态,sleep是占着茅坑不拉屎的一个玩意儿
当它们想继续下去的时候,出现了上述的死锁。
下面我们把问题解决一下啊
解决死锁就把形成死锁的条件破坏掉就OK了
例如强制A先把刀子给B;
强制B把筷子先给A;(在这里表现为直接把对应的sleep注释掉);还有其他方法有兴趣的朋友可以自己想想办法。
当然这里的死锁是我们故意强制造出来的,没有多大意义,相信现实中的死锁,会让人抓耳挠腮的。
补充一句 java的多线程貌似对线程进行了比较好的封装,回想C++的线程,貌似比java复杂很多,这个暂时就先放一放了...
例如我们常常使用的qq和360都是单独的进程(当然他们貌似会有几个进程,我们理想化一点吧,就认为它们是一个进程)
还记得腾讯和360的那场桌面大战吗?
那其实就是进程通讯,通过一个进程去杀死另一个进程,用的是信号,发送一个kill信号,嘿嘿。
跑题了,我们要说的是线程,下面我们看看java的多线程技术:
进程和线程关系:一个进程里面可以有多个线程(至少一个)
例如:使用QQ聊天的时候,你可以同时和很多好友聊天,每个都是一个线程,也可以一边发送,一边接收,甚至视频语音聊天,都是用多线程来实现的。
如何创建线程:当创建一个应用程序其实就已经创建了一个线程。
当然,我们要说的并不是仅仅使用一个线程,java在程序中另外创建线程的方法有两种
1:继承Thread类
2:使用runnable接口
这两种方法的区别:
继承Thread的类只能起一个线程而使用runnable的接口却可以起多个线程。
详细分析:a:当我们使用继承Thread的类,创建一个对象,然后start多次,其实只是start了一次,而我们创建多个对象对每个对象都进行start的话,每个线程都是独立的,它们单独处理自己的对象。
b:当我们使用runnable接口的时候,我们可以实现多个线程对一个资源的处理。
所以总结下runnable相对于Thread的优点
1:适用多个线程处理相同资源。
2:避免由java的单继承带来的局限性。
3:有利于代码的健壮性,代码能被多个线程共享,代码与数据时独立的。
(见Java就业培训教程 ---张孝详)
后台线程
我们可以回想一下:我们用前面说的方法启动一个线程,在没有达到线程停止或者退出的条件,我们的线程会一直执行下去,对吧。
这里引入一个后台线程的例子
public class TestExCeption
{
public static void main(String args[])
{
TestThread t = new TestThread();
Thread tt = new Thread(t);//
tt.setDaemon(true);//使线程变为后台线程。
tt.start();//
}
}
class TestThread implements Runnable
{
int i=1000000;
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName()+"the value of i is "+i--);
}
}
}
运行线程,结果为:
Thread-0the value of i is 1000000
Thread-0the value of i is 999999
Thread-0the value of i is 999998
Thread-0the value of i is 999997
Thread-0the value of i is 999996
Thread-0the value of i is 999995
Thread-0the value of i is 999994
Thread-0the value of i is 999993
到这里程序就已经结束退出了
我们的后台线程启动了一个 while(true)的死循环,但是并没有一直执行,而是结束了。
可想而知,导致这样的结果是由于我们把线程变成了后台线程。然而我们的后台线程还没有执行完程序就停止结束了,所以得出结论只有后台线程运行时,进程结束。
联合线程
联合线程其实就是join()方法的调用,在我们调用join()方法后,线程会进入等待,知道那个线程结束后,调用jion()的线程才继续开始。
join()有三种格式join(),join(long millis),和join(long millis,int nanos)三种方法,无参的是一直等待,第二个指定时间精确到毫秒,第三个是指定时间精确到纳秒
基础概念说了这么多,我们看看怎么用吧,亲:
多线程技术能够同时(我们认为是同时)进行多个操作,大大的提升了cpu的工作效率。
我们还是看张孝祥老师书上的多线程卖票的例子(因为我努力想使线程出错,结果却始终都是对的,很尴尬)
class TestThread implements Runnable
{
private int tickets = 100;
String str = new String("");
public void run()
{
while(true)
{
if(tickets > 0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
System.out.print(e.getMessage());
}
System.out.println(Thread.currentThread().getName()+"the value oftickets is "+tickets--);
}
}
}
}
运行结果
Thread-0the value of i is 2
Thread-1the value of i is 1
Thread-2the value of i is 0
Thread-3the value of i is -1
可见,我们只需要 >0 的数 ,可是却打印出了 0 和 -1;
我们想一下为啥?
原因分析,当我们执行到 tickets = 1 的时候,有三个线程同时去访问了tickets这个变量,那时候tickets = 1;
满足 >0 的条件,然后那三个线程分别对tickets进行了 --操作,导致出现了0和-1.到现实生活中这就是一个致命的错误了。
想想,当你春节时候拼死拼活的抢到一张票,结果却是一张不存在的票,那就只能唱算你狠了,哈哈,开个玩笑。
当然,发现错误我们就必须想法子去纠正它
1:同步代码块
加synchronized()至于该如何加我就不贴了,好像在抄书似的,呵呵。
加完运行:
Thread-2the value of i is 5
Thread-2the value of i is 4
Thread-2the value of i is 3
Thread-2the value of i is 2
Thread-2the value of i is 1
结果正确了,哈哈
2同步函数
在函数前加上synchronized关键字
当然结果也是正确的,我都运行过的,可以打包票的哦。
写的有点头大了,休息一下
3 代码块和函数同步
这个是让代码块和函数监视同一个对象(这个没太整明白,还在进一步分析中)
最后留下来的是死锁问题
多线程程序中最要命的一部分。
死锁形成条件:
张老师书中举例子:
A有一只筷子一把刀,B有一只筷子一把叉子。A要求B先给自己筷子,才给B刀子。B要求A先给刀子,才给筷子,于是一直僵持,大家最后都吃不上饭。
这样就是线程死锁了。抛开问题不说,如果是我,我就直接用手,哈哈。
言归正传,下面我们分析下死锁形成的条件吧:
1:资源互斥(只有一双筷子和一副刀叉,如果有很多,完全可以叫服务员再拿,肯定能吃上饭)
2:每个线程都占有一定资源(A有筷子和刀子,B有筷子和叉子,但是没有完全占有)
3:每个线程都试图抢占资源(A希望先得到筷子,B希望先得到刀子)
4:循环等待(大家僵持住,一直等待,不让步)
这样两个线程就死锁了。
死锁的条件总共没有几个字,相信智商再低的同志也能背下来,但是学以致用就非常困难了。
非常遗憾的一点是我至今没有见过死锁问题,还是拿张老师的例子吧
class A
{
synchronized void foo(B b)
{
String name = Thread.currentThread().getName();
System.out.println(name+" entered A.foo ");
try
{
Thread.sleep(1000);
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
System.out.println(name+" Trying to call B.last ");
b.last();
}
synchronized void last()
{
System.out.println(" inside A.last ");
}
}
class B
{
synchronized void bar(A a)
{
String name = Thread.currentThread().getName();
System.out.println(name+" entered B.bar ");
try
{
Thread.sleep(1000);
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
System.out.println(name+" Trying to call A.last ");
a.last();
}
synchronized void last()
{
System.out.println(" inside A.last ");
}
}
class Deadlock implements Runnable
{
A a = new A();
B b = new B();
Deadlock()
{
Thread.currentThread().setName("MainThread");
new Thread(this).start();
a.foo(b);
System.out.println("back in main thread");
}
@Override
public void run() {
Thread.currentThread().setName("RacingThread");
b.bar(a);
System.out.println(" back in other thread ");
}
public static void main(String[] args)
{
new Deadlock();
}
}
太长了,不过为了见识一下死锁,也豁出去了,敲了半天,运行真的死锁了,先不看张老师的解答,自己尝试定位下呢。
运行结果如下
MainThread entered A.foo
RacingThread entered B.bar
MainThread Trying to call B.last
RacingThread Trying to call A.last
我们可以看到是在调用A.last后出现的问题。
那为什么会出现问题呢,我们进一步看下,当我们第一次执行A.foo(),函数进入sleep状态,然后调用B.bar(),也进入了sleep状态,sleep是占着茅坑不拉屎的一个玩意儿
当它们想继续下去的时候,出现了上述的死锁。
下面我们把问题解决一下啊
解决死锁就把形成死锁的条件破坏掉就OK了
例如强制A先把刀子给B;
强制B把筷子先给A;(在这里表现为直接把对应的sleep注释掉);还有其他方法有兴趣的朋友可以自己想想办法。
当然这里的死锁是我们故意强制造出来的,没有多大意义,相信现实中的死锁,会让人抓耳挠腮的。
补充一句 java的多线程貌似对线程进行了比较好的封装,回想C++的线程,貌似比java复杂很多,这个暂时就先放一放了...