多线程为多个代码执行单元获取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的锁。双方都在等待对方释放锁资源。