java基础(三):多线程

1.进程、线程

进程:正在运行的程序

线程:进程中负责程序运行的执行单元

即:进程的范围>线程的范围。

且:一个进程可以有多个线程。

2.多线程的意义:多部分代码同时执行,提高CPU使用效率
3.多线程的特点:CPU的随机性

4.创建线程的两种方法

(1).继承Thread

    ^1.继承Thread

    ^2.覆盖run()方法

    ^3.创建线程对象

    ^4.调用start()方法开启线程

(2).实现Runnable接口

    ^1.实现Runnable接口

    ^2.覆盖run()方法

    ^3.新建Thread对象,在该对象构造函数时将上述对象作为参数传入

    ^4.调用start()方法。

比较:第一种因为是继承,java中的继承都是单继承,有局限性。第二种比较灵活,且耦合也比较低,建议。

5.线程的状态变化关系

线程的状态有五种:被创建,运行,冻结,阻塞,消亡

    被创建->运行:start()方法。

    运行->消亡:run()方法结束。

    运行->冻结:sleep(time),wait()两种方法。

    冻结->运行:sleep时间到了,notify()

    运行,阻塞,冻结之间的关系:

        运行:有CPU执行资格,有CPU执行权

        阻塞:有CPU执行资格,无CPU执行权

        冻结:无CPU执行资格,无CPU执行权

6.线程安全:当多个线程同时操作同一个数据时,会导致数据错误,造成线程安全问题。

(1)解决方法:(1).同步代码块    (2)同步函数

    同步代码块:synchronized(对象){代码};(可用try,catch解决错误)

    同步函数:正常的函数上加synchronized。

        常规函数的锁:this

        静态函数的锁:类名.class(字节码对象)

(2)比较:同步函数一个类只有一个锁,所以一个类一个锁的情况用同步函数。一个类多个锁或者多个类用一个锁的用同步代码块。

(3)加锁为什么能保证线程安全?因为加了锁就用了单例模式。

        单例模式分成2种:饿汉式,懒汉式。

        (1)饿汉式(先创建对象,然后通过方法返回该对象)

        (2)懒汉式(先不创建对象,通过方法返回时再创建对象)

class Single{//饿汉式
    private Single single = new Single();
    Single(){}
    public Single getInstance(){
        return single;
    }
}

class Single{//懒汉式,双重if判断保证安全
    private Single single = null;
    Single(){}
    public Single getInstance(){
        if(single == null){
            synchronized(Single.class){
                if(single == null){
                    single = new Single();
                }
            }
        }
        return single;
    }
}

7.死锁:加锁时需要注意导致一些新的问题,即死锁。

死锁的原因:(1).同步嵌套    (2).生产者消费者的同一方(生产者转到另一个生产者,消费者转到另一个消费者)

(1).同步嵌套:A线程拥有CPU执行权,但是A内还嵌套一个B。如果A想运行B需要把执行权给B,但是B拿到执行权又进不了A,就在互相等待,造成死锁。

synchronized(A.class){
    synchronized(B.class){
        xxx;
    }
}

(2).生产者消费者模式的几个规则:

    ^1.生产时不可以消费(多线程且需要同步)

    ^2.生产一个后就不能生产,让消费者把生产出来的东西消费掉。然后转到生产者继续生产。

   所以,如果一个生产者生产好了东西,然后CPU执行权转给另外一个生产者,这个时候是不可以生产的,2个生产者互相等待别人去消费,造成死锁。

生产者消费者模式的其他一些细节:

    ^1.判断是生产者还是消费者时,可以用一个flag来判断,但是不要用if(flag),用while(flag)。因为如果一个线程刚刚判断完if,转到另一个线程,则判断实际是无效的。

    ^2.为了避免死锁,一个生产者结束之后,唤醒其他线程的时候一定要有消费者线程在里面。所以不用notify(),用notifyAll();

    ^3.但是紧接着又有一个问题,一个生产者结束后,只需要消费者,但是notifyAll()把生产者也唤醒了,降低效率,所以为了解决效率低的问题,引入一套锁和监视器机制。

    ^4.每次只能生产一个产品,效率有点低,所以可以设置数组,保证可以生产多个,那么while判断时是数量判断。并且每次生产满后需要将数组的指针归为0重新开始。

class Resource(){
    private int count;
    private boolean flag = false;//一开始默认没有产品
    private String name = "";
    
    //生产
    public synchronized void set(String name){
        while(flag){//如果有产品了,等待
            try{
                wait();
            }catch(Exception e){
                e.printStack();
            }
        }
        count++;
        this.name = name + "..." + count; 
        system.out.println(Thread.currentThread.getName + "生产者" + this.name);
        flag = true;
        notifyAll();
    
    //消费
    public synchronized void get(){
        while(!flag){
            try{
                wait();
            }catch(Exception e){
                e.printStack();
            }
        }
        system.out.println(Thread.currentThread.getName + "消费者" + this.name);
        flag = false;
        notifyAll();
    }
}

class Producer implements Runnable{
    private Resource r;
    Producer(Resource r){
        this.r = r;
    }
    public void run(){
        while(false)
            r.set("面包");
    }
}

class Consumer implements Runnable{
    private Resource r;
    Consumer(Resource r){
        this .r = r;
    }
    public void run(){
        while(true)
            r.get();
    }
}

public class Test{
    public static void main(String[] args){
        Resource r = new Resource();
        Producer p = new Producer(r);
        Consumer c = new Consumer(c);
        
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(c);
        Thread t4 = new Thread(c);
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        
    }
}

锁和监视器的写法:

    private Lock lock = new ReenTrantLock();

将上述synchronized变成lock.lock();

在finally里加上lock.unlock();

    private Condition con = lock.newCondition();

    wait()变成con.await();

    notify()变成con.signal();

    notifyAll()变成con.signalAll();

在唤醒时唤醒需要的那一方即可。

 

数组使可以同时生产多个。

    生产者:while(count == objs.length),    objs[put] = name,    if(++put == objs.length) put = 0;

    消费者:while(count == 0),    name = objs[take],    if(++take == objs.length) take = 0;

    

8.插入一个线程先运行

t.join():让t线程先运行,运行完之后再运行原本的线程

9.转化成守护线程

        t.setDaemon(true):垃圾回收机制是一个很典型的守护线程。前台线程结束,守护线程也结束。

10.中止线程

        如果不是在一个线程,用while判断

        如果是一个线程,用t.interrept();

11.优先级

        t.setPrority(数字):数字1-5,数字越大,CPU倾向分配的资源就越多。

12.wait和sleep异同

        同:都会把线程从运行状态变成冻结状态

        异:(1).sleep时间到了会自动从冻结状态变成运行状态,wait不会

               (2).wait会释放执行资格和锁,sleep只会释放执行资格不会释放锁

猜你喜欢

转载自blog.csdn.net/qq_40594696/article/details/85294402