笔记-java线程基础

多线程为多个代码执行单元获取cpu执行自己专属的代码。

关于线程使用:

方式一:

extends java.lang.Thread 

实现Thread的子类,这种方式的线程,new是创建多个实例。


方式二:

implement java.lang.Runnable

实现Runnable接口。


1.继承Runnable接口必须要实现run方法,而继承Thread类则不需要。

2.Thread其实也是实现了Runnable接口。

3.继承Runnable的子类表示这是一个可被多个线程执行的类,而实现类本身并不是一个线程,所以即使你实现了这个类,你最后还是需要通过new Thread(Runnable),然后把该类的实现类作为参数传进去,举例来说。

FirstThread first = new FirstThread();

Thread one = new Thread(first);

Thread two = new Thread(first);

one.start();

two.start();

其中FirstThread实现了Runnable接口。

而one和two两个线程最后都执行的first里面的run方法;

4.同上面,可以想到的是,继承Thread子类,当创建多个线程的时候run方法中的this是不同的。run方法中的this就是指向了自身线程。

5.继承Runnable接口,多个线程最后执行的是Runnable子类的run方法,所以多个线程的run方法中的this都是指向了Runnable的实现类。而线程对象依旧是多个。

6.看源码,可知,Thread类里面有个Runnable类型的私有变量target,Thread最后会执行taget.run()。所以才会造成两种方法this的指向不同。

 /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

7.这很重要,关系到后面代码同步和锁的问题。不懂上面再看一遍。


关于线程状态:

严格来说,java有六种线程状态。

在Thread类中有个State枚举记录了线程的六种状态。


关于线程的锁:

1.多线程编程不加锁会造成数据的不一致,线程锁可以保证数据完全性。

2.java中用

synchronized(obj)

{

//被锁上的带码

}

关键字来锁代码,其中obj可以是任意类型对象或者直接是Object对象。进入该代码块的线程隐式的获取到当前sync的锁,其他线程(此时为blocked)在sync(obj)上等待握有锁的线程释放锁(不一定非要退出sync代码块。)。直到握有锁的线程出了sync代码块,线程释放锁,在sync(obj)上等待的其他线程才拿到锁再次进入代码块,其他线程(blocked)继续等待锁。反复循环。

3.sync可以修饰方法时,因为没有(obj),所以线程锁是隐式指向的。而方法分为静态和非静态。

静态方法的锁隐式指向了当前类的类类对象(类名.class),而非静态方法锁隐式指向的当前this。

4.如果一个数据,你在A和B两个地方同时操作,而你只在A上了锁,那么数据依旧是不安全的,也就是说,上锁的是数据的安全操作,而非数据本身。

关于锁方法和代码块:

1.锁静态方法时,锁为当前类的类对象。对象.class。

2.锁非静态方法时,锁为当前对象也就是this。

3.锁代码块时,可以是任意对象。


关于线程等待:

1.线程有三种等待方式,Thread.join,Thread.sleep,Object.wait其中前两种属于Thread类方法,最后属于Object对象的方法。

2.A线程运行,遇到了B.join那么A线程就会一直等待B线程结束也就是状态为terminated时,A线程才可以继续执行,如果抢到cpu执行的话。

3.sleep默认等待一定时间(timed_waiting),过了指定时间之后线程才会重新成为runnable态。

4.调用wait方法的线程必须拥有当前锁(监视器),用锁来wait,即使说告知锁,当前线程需要等待。

5.上面说过非静态方法的锁是当前this,因此直接wait()。静态方法中,当前锁是类类对象因此需要类名.wait(),调用该方法后,当前线程不仅会让出cpu执行权,还会临时释放手中的锁给等待的线程,并且进入到这个锁的等待集合中,而后面握有这个锁的其他线程调用notify()时,会从等待集合中随机唤醒一个线程。当线程调用notifyAll()时,则会唤醒所有所有线程。

该机制有一定缺陷,只有一个等待队列,因此唤醒的线程是不定的。并且唤醒的线程是随机性的。



4.wait等待唤醒有一定缺陷,它只有一个等待集合,每次唤醒的线程都是不定的。jdk1.5开始有提供了解决办法。

在java.util.concurrent.locks包下的类提供了显示获取锁,上锁,解锁以及多条件等待唤醒机制。

即使说同一把锁不同的线程可以按照不同的条件进行等待和唤醒。



关于线程死锁:

双方互相握着对方等待的锁,双方都在等待对方释放手中的锁资源,这时就会发生锁。

嵌套同步代码最容易发生这种情况:

代码块一:

sync(lockA){

线程A

sync(lockB){

}

}

代码块二:

sync(lockB){

线程B

sync(lockA){

}

}

上面两个代码块就发生了死锁,线程A握有lockA的锁,等待lockB的锁。而线程B握有lockB的锁,等待lockA的锁。双方都在等待对方释放锁资源。





猜你喜欢

转载自blog.csdn.net/helianus/article/details/77583102
今日推荐