1.每一个分支就是一个线程,main()方法是主分支也叫做主线程.
2进程:进程其实是一个静态的概念,一个.exe文件,一个.class文件
都叫做进程,把它们放入代码区,等待执行,执行正在准备期间叫做
进程.
那么进程的执行是这么回事呢?
进程的执行指的是进程里面的主方法也就是main()方法开始执行
了,进程是一个静态的概念,在我们机器上运行的都是线程.
windows,linux,unix都是支持多进程.多线程的,但是dos是只支持
单进程的.
3创建新的线程有两种办法
->要new出来一个新的继承了Thread类的对象才可以的.
->要new出来一个新的实现了runnable接口的对象也行.
其中runnable接口只有一个方法就是run();
实际上Thread类本身也是实现了runnable接口,否则它里
面就不会有run()这个方法了.
在run()方法里面些什么代码就执行什么代码.
一个的对象调用run()方法不叫做"开启线程,而叫做方法调用"
而对象调用Start()方法才叫做开启线程.为什么你呢?
因为当一个对象调用run()方法的时候,它并没有通知cpu说我这边
已经有了一个新的线程出来了,它只是按照main()方法的顺序,在轮
到他执行的时候去执行就是了,跟普通的类实现了接口之后被main()
方法调用时一样一样的,但是呢如果一个对象它有Thread类来创建
的话那结果就不一样了,当这个对象调用了.Start()方法之后它就会
通知cpu告诉它我已经有了一个新的线程出来了,什么时候有时间
就分配给我点来执行我的程序,所以这个线程就会在某些时候被执行
当然这也就是cpu工作的原理,在,某些事件调用一些线程.
public class TestRunnableAndThread {
public static void main(String[] args) {
f t1=new f();
Thread t2=new Thread(t1);
//t1.run();
t2.start();
for(int i=0;i<=100;i++){
System.out.println("main:\t"+i);
}
/*
* 如果单独的运行t1.run();而不去运行
* t2.start()的话,结果就是先把run()里面
* 的全部打印完了再去打印后面main()方法
* 里面的,而如果说是把t1.run()给注释掉了
* 的话,那结果就是什么呢?就是这样的:
* main: 34
* main: 35
* main: 36
* Runnable: 0
* Runnable: 1
* Runnable: 2
* Runnable: 3
* Runnable: 4
* Runnable: 5
* Runnable: 6
* Runnable: 7
* Runnable: 8
*这个结果就说明在调用了一个继承了Thread类的
*start()方法之后,它就是一个新的线程,而且这个
*线程会在cpu有空的时候就会被执行.
*/
}
}
class f implements Runnable {
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("Runnable:\t"+i);
}
}
}
public class TestRunnableAndThread {
public static void main(String[] args) {
// f1 t=new f1();
// t.start();
// t.display();
/*
* 通过这个程序可以发现,其实对,象的.start()
* 方法也是调用的对象的重写过的run()方法,
* 当然jdk里面也已经写明了是调用的run()方法
* 就是说不管你调用的是对象的run()方法,还是
* 对象的.start()方法,结果都是调用的对象的
* run()方法,当然区别在于,调用run方法只需要
*实现了runnable方法即可,但是要调用start()
*方法就要继承Thread类才行,知道了吧,还有一点
*比较有趣的就是,看下面的实例:就是尽管调用了
*tt1.start(),但是它调用的方法最终跟
*tt1.run()的结果是一样的,这时因为在
* Thread tt1=new Thread(tt);一步上实际上
* tt1它是作为一个已经继承了Thread类的实例来
* 出现的,而它所调用的run()方法要么是在继承
* Thread类的时候重写的run()方法,要么是在实行
* runnable接口时重写的方法,两个有一个就好了
* 当然这个地方在继承Thread类的时候没有重写它
* 的run()方法.
* 如果class f extends Thread implements Runnable
* 这个样子来写的话,那么结果就是什么呢?
* 就是你就不用去重写run()方法了,为什么呢?
* 因为你本身已经继承了Thread,而Thread已经实行了runnable
* 接口,重写run()方法里面什么都没有罢了.
* 你不可能把继承Thread和实现runnable分开写,并且分别重写
* run()方法,那样的话就相当于出现了两个一样的类了,类就重名了.
*
*当然如果这个类本身就是继承了Thread类的类的话,那就不要去new
*一个Thread的了,因为你本身就是Thread,只需要实例化对象就可以
*了.
*/
f tt=new f();
Thread tt1=new Thread(tt);
tt1.start();
tt1.run();
}
}
class f implements Runnable {
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("Runnable:\t"+i);
}
}
}
class f1 extends Thread {
public void display(){
System.out.println("Extend Thread");
}
}
那么在新建线程的时候既可以用extends Thread
也可以implements Runnable,到底用哪个呢?
记住这个原则,能够用implements Runnable来实
现的就不要用extends Thread,因为你继承了Thread
就继承不了其他的类了,而你实现了Runnable之后还
可以去实现其他的接口,比较灵活.
cpu就像是一个大厕所,你的线程虽然调用了,但是我里面
的坑已经满了,你就是start了也不会对你进行执行的.
老师讲什么都拿厕所举例子.start()方法的调用只能说明
这个线程已经准备好了,但是下一步要做的不一定就是让你
进厕所了,你很可能要去排队的.
start()准备去厕所,就绪排队调度就是
你要去厕所:
start()(准备好了)-> 就绪(排队)->调度(进厕所)->运行(开始
办事)->导致阻塞的事(没手纸了)->阻塞状态(等待手纸)->
阻塞解除(手纸来了)->进行下一个循环.
当然排队的时候还是有优先级的,比如你的县长跟你排在一个队
里面,他比你来的晚,里面现在正好有一个位置可以去,但是县长
优先级比你高,所以县长先进你后进
sleep会抛异常,就相当于一个人正在睡着,你给他身上泼了一盆
冷水,它自然会抛异常了.
在那个线程里面调用的sleep()放过就让哪个线程睡眠.
stop()的方法现在已经废弃了,它比interrupt还粗暴,interrupt相
当于是给人泼盆冷水,而stop()就相当于是一棍子打死了,永远都
不会执行了.
run()方法结束,线程就结束了.看例子:
public class TestSleep {
public static void main(String[] args) {
T t=new T();
t.start();
}
}
class T extends Thread {
boolean flag=true;
@Override
public void run(){
while(flag){
System.out.println(new Date());
try {
sleep(1000);
/*
* 注意这个地方,对于重写的这个
* run()方法,对于异常的捕捉只能
* 够使用try{}cattch(){}
* 为什么呢?
* 因为作为run()方法是被重写的方法
* 所以作为重写的方法,不能够抛出比
* 自己父类更大的错误,也不允许抛出
* 不同的异常.
*/
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
join()方法,就是把某个线程跟当前的线程给合并在一起
看例子:
public class TestJoin {
public static void main(String[] args) {
j jj=new j();
jj.start();
try {
jj.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/*
* 如果把上面的join()方法给注释掉的话,结果就是
* 两个线程main()线程和jj线程交替的打印,如果是
* 把注释拿掉的话,结果就是先打印jj线程的东西,之
* 后才会去打印main()线程里面的东西,因为它们已经
* 合并成为了一个线程,它必须要按照顺序来执行了.
*/
for(int i=0;i<100;i++){
System.out.println("main Thread");
}
}
}
class j extends Thread {
@Override
public void run(){
for(int i=0;i<100;i++){
System.out.println("join Thread"+i);
try {
sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
要注意的都是:只有调用了.start()方法的才叫做开启了新线程,
只是调用了.run()方法是不算作新线程的,因为它不用通知cpu,
它只会按照自己位置等待被执行就可以了.
yield(),方法就相当于于是让给别人来办事,但是就只是让了一下
而已,并不是我不再去执行了,我还没完,我还要执行,只是我先让一
下,sleep()和yield()方法都是对于当前的线程来说的.所以不需要
有对象来调用它,对象就是默认的当前对象.
看例子:
public class TestYield {
public static void main(String[] args) {
display d1=new display("线程1");
display d2=new display("线程2");
d1.start();
d2.start();
}
}
class display extends Thread {
public display(String s){
super(s);
}
public void run(){
for(int i=0;i<100;i++){
System.out.println(getName()+":"+i);
if(i%10==0){
yield();
}
}
}
}
一般情况下yield()方法用到的不多,但是也是有的.
优先级的意思是:同样两个处于就绪的线程,优先级高的那个
线程在执行的过程中所占用的cpu时间相对的比优先级低的
那个要长,并不是说优先级高的就一定要先执行,执行完了之后
优先级低的才会去执行,而是两个都会执行,只是优先级越高它
的执行时间就越长而已.
线程同步:对于访问同一个资源的多个线程进行协调,使它们按照
正常的顺序去执行就叫做线程同步.它就相当于你在上厕所的时候
进去了之后先把门给关上,这段期间归你独占这个坑.其他的人进不来
就相当于是其他的线程在此期间不能访问这块资源一样.这个时候就
加上一把锁就行了.
public class TestSyncronize implements Runnable {
timer t=new timer();
@Override
public void run() {
t.add(Thread.currentThread().getName());
}
public static void main(String[] args) {
TestSyncronize test=new TestSyncronize();
Thread t1=new Thread(test);
Thread t2=new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
/*
* 为什么打印的结果是两个都是第二个用户呢?
* 不应该是t1是第一个,而t2是第二个用户吗?
* 不是的,这是因为在t1执行的过程中被interrupt
* 了,所以呢就在他要去执行下面打印的话的时候
* 它sleep()了,而这个时候num已经由0变成了1了
* 然后再执行一个新的test对象的时候,它又由1变成
* 了2了,这个时候t1醒过来了,它继续执行下面的打印
* 的话结果就是第二个用户,而同样t2也是第二个用户
* 这个地方是用的sleep()方法来使得第一个线程sleep()
* 或者说用sleep()方法打断了线程1的但是,实际上
* 就是不用这种方法,它t1仍然可能被打断的,虽然说
* 不能说一定会被打断,但是完全存在这种可能,而这种
* 情况的出现将会是很危险的,比如夫妻两个人去取款,
* 一个用的是邮政卡,一个用的是存折,同时不同地的去
* 取钱,那么这个时候就会有两个线程同时的访问这个账户
* 里面的资源,如果有个线程被另外一个线程打断的话,那么
* 银行就亏死了,存折中工4000,一个人取了3000.结果里面
* 还剩下1000,可笑,所以呢就要注意了是不能这样做的,要
* 在一个线程访问一块资源的时候先把这块资源给锁住,只有
* 这一个线程可以用,等这个线程搞完了之后再把锁给解开
* 就行了.
*/
}
}
class timer {
private static int num=0;
public void add(String name){
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("你是第"+num+"个使用timer的用户");
}
}
怎样解决呢就是加上锁,在方法add()上加上锁就好了,它的意思就是在
执行当前方法的时候锁定当前的对象,而不是说只是锁定这个方法,只
有等这个方法执行完了之后其他的人才可以对这个对象进行访问。
死锁的问题:就是一个进程要完成一件事情的时候,它要在锁住一个
对象的同时要锁住另外一个对象,而另一进程则正好跟他一样,也是
要锁住同样的这两个对象,只是它们锁的顺序正好是相反的,比如进
程a要锁住对象o1和o2,即在锁住o1的内部还要锁住o2才能完成这个
任务,而对象b则相反,它要先锁住o2,之后再锁住o2的内部再锁住
o1才能完成任务,而这两个线程又正好是同时执行的,那么两个进程
都会被对方所锁住不能向下执行,这个就是死锁。
要注意这个死锁是在锁住一个对象的内部,再锁住另外的一个对象,而
不是并排的先锁住一个o1,执行完后把o1给放开,然后在执行o2.
而且这两个对象都必须是static类型的,否则也是锁不住的。为什么呢?
因为,static是静态的全局的相当于,它是属于类所拥有的东西,因此
不管是new几个对象,在使用它们的时候必须是有顺序的,不可能一个
静态变量被两个对象同时的去访问,否则它就没有意义了。
而如果不是静态的,那么这两个对象就会成为对象的变量,它们分布在
堆空间中,这个时候每个new出来的对象所拥有的变量都是它们自己的
而不是全局的,所以可以任意的改变。
对于死锁有一个故事:哲学家吃饭的问题,五个哲学家围坐在一个圆桌
边上,桌上面摆放着大鱼大肉的,但是每个哲学家都是只有右手有一支
筷子,所以大家都等着左边的那个人能把那支筷子给他,让他先吃,可
是没有一个人愿意把自己的筷子给其他人,结果,五个人就看着饭被饿
死了。它的这个死锁相当于是多线程死锁,而上面的例子是两个线程之
间的死锁。
解决死锁的问题办法:加粗粒度,就是不要只是锁住一个类里面的一两
属性,而是锁住整个类。
对于数据库里面的数据来说有该和读的两个方法,一个是该,可以允许
两个方法同时的来读数据,但是不允许两个线程同时的来该数据,所以
对于修改数据的方法要加锁,对于读取数据的方法来说不应该加锁。
如果两个方法同时都修改了一个参数的值的话,那么这两个方法应该都
加锁。
Wait()方法跟sleep()方法之间的区别?
Wait()方法跟sleep()方法区别太大了,Wait()方法是Object的方法,而sleep()
是Thread类的方法.
在调用Wait()方法的时候要必须锁定该对象,因为Wait()一发生就会解锁,
所以要在调用的时候锁定对象
而sleep()则不一样,在调用它的时候线程还是锁定着的.
notify(),notifyAll()都是Object类的方法.
java线程
最新推荐文章于 2021-08-02 16:35:04 发布