Java笔记系列(基于马士兵的课堂)(6)-线程

时间:2022-09-20 08:43:11

          线程

本章概述:线程的基本概念,线程的创建和启动,线程的调度优先级,线程的状态控制,线程的同步(重点)

线程:是一个程序里面不同的执行路径。

对于一个main方法而言,只有一条线程,若main方法中调用方法,或某个方法调用方法,只有当这个方法调用完毕时才会继续执行之前的方法。其中main方法叫做主方法。

注意线程与进程的区别:进程是静态的概念,某一个class文件,或者某一个exe文件叫做一个进程,当进程产生时,只是一些代码位于内存中,当进程中线程开始执行时,进程(同时被执行),也就是说,真正被执行的只是线程。

同时执行:其实cpu的运算在某一个时刻 是只能执行某一个线程的,但是,由于cpu的执行速度非常的快,他可以先执行某一个线程一会儿,在执行另外一个线程一会儿。

Main方法所指的是某一个程序的不同执行路径。真正的双线程:双核cpu,多核cpu。

Java中的多线程通过java.lang.Thread来实现,启动线程,创建线程需要new一个新线程:这个线程类要继承Thread并重写run方法(Thread这个类本身就已经实现了runable接口),或者某一个线程类实现一个接口runnable(同样重写run方法)。

Thread里的一个特殊的方法,叫做run,向其中写什么语句,就运行什么语句,通过start方法来启动一个新的线程。

这时main方法就可以忽略新的线程,直接自动运行。这是一种并行的运行。

在继承与实现中的选择时:建议尽量选择实现接口,这样可以继承更多的类,实现更多的方法,尽管继承调用起来更简单,但后者更具可扩展性。

Run与start:直接run和,new一个Thread之后再用启动线程这个方法是完全不同的。启动线程只能通过Thread里的这个start方法(其会启动cpu的另一种运行方式,并行运行),而run方法只是适用于某一个线程中的方法调用。

线程转换的转换:

注意线程创建过后不能直接的运行,需要先进入到准备状态,然后等cpu有时间了(将线程调度),就可以让其进入到运行状态,但可能运行到中间,又进入到就绪状态,接着排队运行,直到运行终止,也有可能运行到中间发生阻塞,进入阻塞状态,直到阻塞处理完毕。

线程的处理方法:

Java笔记系列(基于马士兵的课堂)(6)-线程

注意线程优先级,优先级高的线程并非全部运行结束才能让次高级线程运行,而是高级线程的运行时间相对长。

Sleep方法:

注意:sleep是thread中的静态方法,而且会抛出某些异常(注意这个方法在哪里调用,那个线程就会停止,可以让主线程睡眠)。相应的有Interrupt方法可以打扰睡眠状态。

通过Test11,test2,可以终止子线程使其睡眠或者打断(通过捕获异常),但这种方法比较粗暴,相对有一个更粗暴的stop方法(已弃用),这两种都可能关闭不了线程文件关闭,但我们可以让子线程不用死循环,定义一个Boolean的成员变量,这样可以在main方法里改变这个变量的类型,一旦改变run方法结束,子线程关闭,简单却不粗暴。还可以在子线程类里加入方法,这个方法可以关闭run方法也可以。

join方法,yield方法:

线程的构造方法中可以给他起名字(不要忘了向父类传构造方法值)。Join叫做合并,本来在不合并时,主线程和子线程是可以一起执行的,但合并之后就相当于方法调用,一定是子线程调用完了(main线程)在这个点上等待子线程运行完毕,自己才能继续往下运行(就算子线程sleep)。注意join也会抛出interruptedException。

Yeild是让方法,让别的线程也运行一下,调控cpu。可以用同一个线程类,new出两个对象,此时这两个对象的执行语句是一样的。让之后并不代表完全结束,但这个方法调用后,下依次执行一定不是调用者。

线程优先级:由于Thread里定义了很多常量,线程的优先级默认值是5,最大10,最小1。

可以通过thread.setPriority(int)可以让优先级升高(相当于路上公交车优先级高)。

并不是完全执行完最高优先级再执行低优先级。

IsAlive()是否在执行

CurrentTread()<拿到当前线程,与this拿到当前方法类似>

线程同步的意义:

在两个线程执行方法的过程中,两个线程访问同一个资源,如果两个线程同时访问的那一份资源在某个方法中被改变,另一个线程中应有所体现,如果两个线程不能同步,则资源的利用可能会出错。(根本:两个线程执行时,一个线程的执行被另一个线程打断),如果在某一线程中的成员变量里,new一个新的对象,这样,用这个类new出来的新线程对象,满足多个线程指向同一个对象。如果更不幸的,这个资源对象中还有一个静态的方法,这个方法,本应该在多个线程中被调用出的结果具有静态变化性(因为静态方法调用不会立即清除静态数据,下一次调用时,若用到其中的数据仍然是上次调用过后的,静态变量),尽管你可能让第一个线程睡眠或者让,但这仍然无法使调用结果正常,而且这反而给了第二个线程打断第一个线程的机会,就算不睡眠,或是让,这种打断情况仍然可能出现。(前后不一致

解决办法:在某一个线程执行的过程中,这一份资源归某一个线程所独占,其他线程无法访问。锁住当前对象,可以保护资源的改变不当,关键字:Synchronized(this){}在某一个线程执行{}内的内容时,只有一个线程执行完才可以有另一个线程执行。

还有一种将Synochronized(锁)写在方法域修饰符里。(public synchronized void m(){})这样某一个线程在调用这个方法时就不可以被打断(不可以访问这个对象)。这样一来只有在某一个线程调用方法完毕后这个对象才可以被下一个线程访问。

死锁:当某俩个线程在执行过程中必须要访问两个对象,且必须访问两个对象这个线程才能结束,一个先访问了某个对象,并锁定该对象,再访问另外的对象发现另外的线程正在访问这个对象,并且锁定,所以这样一来,两个线程都无法执行完毕,形成了死锁。这个程序将停止在某个节点。(著名的哲学家吃饭问题,每个人只拥有右手的筷子,做成圆桌,无法向别人要筷子)

解决办法:(之一)将锁的粒度变粗,不要只锁其中几个对象,锁住整个主体,类,数据库设计实现中常碰见各种各样的死锁。

互斥:在某一个时间段中,保证只有一个线程进入到方法体里。

关于锁的注意事项:

1,在某一个对象内部,成员变量的改变总是绝对的(也就是静态全局的),当其中某一个方法被锁定,不代表这个方法改变的静态变量,或者成员变量的值没有传递出来,这是已经在内存中有所改变的(相当于银行账户),只是这段方法的执行(data区方法,不能被调用)。

2,两个加锁的方法在两个线程中执行的时候就不会产生(1)中的情况,此时后一种方法改变静态的值,由于这个方法的锁定,上一个方法的锁定,这个静态的值在两个同步的方法,两条同步的线程中是分别被执行的,只有两个非同步的方法才可以被同时调用,而且这种调用可以影响同步方法的相关变量,值,以及内容。

3,同步使效率变低,非同步可能使前后不一致。

4,一般改和读两种方法,一般只要加改锁。

5,注意锁定以后,整个对象被锁定,并非只有方法,过程被锁定。

生产者消费者问题:

一个程序实现生产馒头和不断吃馒头的过程,两个过程同时进行。有一种数据结构叫做栈(Stack),先进后出,区别于io流file的先进先出。装和拿对应的有一个栈顶指针,指向下一个放内存的位置。但如果在两个线程同时执行时,馒头有可能被覆盖,或者馒头被拿错位置,此时多个步骤中不能被打断的几个指令或者语句间应该要锁定方法对象。

Wait方法:(Object里)

当某一个方法被锁定,某一个线程可以对锁定的对象调用wait()方法,与Sleep不同,wait不会自己醒过来,注意wait方法会交出锁定对象,这里用notify()方法,叫醒等待的线程。

这个方法是另外一个线程叫醒的,而并非自己叫醒的自己。如果没有notify,也属于一种死锁,例如生产太快,消费者也不叫醒生产者程序会停住(生产过剩,在两者仍没有超过容量时这个问题不会出现,尽管wait,但一旦另一个结束,还是会执行这个线程)。NotifyAll(叫醒多个其他线程)是不能叫醒自己的。对于if与while(if里如果出现问题,如抛出异常,if会被直接跳过判断,而while不会,他会再次判断是否满足其条件,在执行。)这样实现了两个或多个线程之间的同步,实现了两个或多个线程的通气。

Wait与Sleep的区别:1,所在类不同。2,开锁与不开锁。

总结:理解概念。

关于小生产消费者问题:

附相关代码以更好理解

package ProducerConsumer;


public class WoTou {
int id;
WoTou(int id){
this.id=id;
}
public String toString(){
return "WoTou"+id;
}
}

package ProducerConsumer;


public class Producer implements Runnable {
Backet bk=null;
public void run(){
for (int i=0;i<20;i++){
WoTou wt=new WoTou(i);
bk.push(wt);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}System.out.println("生产了:"+wt);
}
}
Producer(Backet bk){
this.bk=bk;




}

package ProducerConsumer;


public class Consumer implements Runnable{
Backet bk=null;
public void run(){
for (int i=0;i<20;i++){
WoTou wt=bk.pop();

try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}System.out.println("消费了:"+wt);
}
}
Consumer(Backet bk){
this.bk=bk;




}

package ProducerConsumer;


public class Backet {
WoTou []arrayWoTou=new WoTou[6];
int index=0;
public  synchronized void push (WoTou wt){
while (index==arrayWoTou.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}this.notify();
arrayWoTou[index]=wt;
index++;


}
public  synchronized WoTou pop(){
while (index==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
index--;
return arrayWoTou[index];


}}

package ProducerConsumer;


public class ProducerConsumer {
public static void main(String args[]){
Backet bk=new Backet();
Producer p=new Producer(bk);
Consumer c=new Consumer(bk);
Thread t1=new Thread(p);
Thread t2=new Thread(c);
t2.start();
t1.start();




}
}