一、多线程
/*
线程由两种实现方式:
第一种方式:
class MyThread extends Thread{
public void run(){
需要进行执行的代码,如循环。
}
}
public class TestThread{
public static void main(String[] args){
Thread t1=new Mythread();
T1.start();
}
}
只有等到所有的线程全部结束之后,进程才退出。
第二种方式:
Class MyThread implements Runnable{
Public void run(){
Runnable target=new MyThread();
Thread t3=new Thread(target);
Thread.start();//启动线程
}
}
线程中的7种状态:开始,可运行,运行,阻塞(锁死,等待队列),结束,
(有的书上也只有认为前五种状态:而将“锁池”和“等待队列”都看成是“阻塞”状态的特殊情况:
这种认识也是正确的,但是将“锁池”和“等待队列”单独分离出来有利于对程序的理解)
注意:图中标记依次为
①输入完毕; ②wake up; ③t1退出
⑴如等待输入(输入设备进行处理,而CUP不处理),则放入阻塞,直到输入完毕。
⑵线程休眠sleep()
⑶t1.join()指停止main(),然后在某段时间内将t1加入运行队列,直到t1退出,main()才结束。
特别注意:①②③与⑴⑵⑶是一一对应的。
线程间通信:
其实就是多个线程在操作同一个资源,但是操作的动作不同。
等待唤醒机制:
wait();notify();notifyAll();都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才有锁。
为什么这些操作线程的方法要定义在object类中呢?
因为这些方法在操作同步中线程时,都必须要标记他们所操作线程只有的锁
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒彼此是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在object中。
生产者消费者模式:
对于多个生产者和消费者,为什么要用while判断标记:
原因:让被唤醒的线程再一次判断标记。
为什么定义notifyAll?
因为需要唤醒对方线程。如果只用notify,容易出现只唤醒本方线程,导致
程序中的所有线程都在等待。
JDK1.5中提供了多线程升级解决方案:
将同步synchronized替换成现实lock操作。
将object中的wait,notify,notifyAll替换成condition对象。
该对象可以lock锁,进行获取。
在该示例中,实现了本方只唤醒对方的操作。
释放锁的动作一定要执行,放在finally里。
停止线程:
1、定义循环结束标记:
因为线程运行代码一般都是在循环,只要控制了循环即可。
2、使用interrupt(中断)方法
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时,不再使用。
stop方法已经过时,如何停止线程?
只有一种,让run方法结束。开启多线程运行,运行代码通常是循环结构,
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:当线程处于冻结状态,就不会读取标记,那么线程不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时就需要对冻结进行清除。
强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
Thread类中提供了interrupt();方法,可以让线程恢复到运行状态。
守护线程:(setDemo();)
将线程标记为守护线程时(在开启线程前开启),
当正在运行的线程都是守护线程时(被守护线程已经结束),
JVM自动退出(守护线程没有存在的价值,自动关闭线程)。
join();方法:
当A线程执行到B线程的.join()方法时,A就会等待,等B线程都执行完后,A才会执行。
join可以用来临时加入线程执行。
锁池:
线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池。
每个对象都有自己的一个锁池的空间,用于放置等待运行的线程。这些线程中哪个线程拿到锁标记由系统决定。
死锁:
锁标记如果过多,就会出现线程等待其他线程释放锁标记,而又都不释放自己的锁标记供其他线程运行的状况。就是死锁。
死锁的问题通过线程间的通信的方式进行解决。线程间通信机制实际上也就是协调机制。
线程间通信使用的空间称之为对象的等待队列,则个队列也是属于对象的空间的。
线程的实现方式(implements Runnable)和继承方式(extends Thread)的区别?
1、实现方式的好处:避免单继承的局限性,在定义线程时,建议使用实现方式。
2、区别:继承Thread线程代码存放在Thread子类的run方法中,
而实现Runnable线程代码存放在接口子类的run方法中。
同步的前提:
1、必须要有两个或两个以上的线程。
2、必须是多个线程使用同一个锁。
好处:
解决多线程的安全问题。
弊端:
多线程需要判断锁,较为消耗资源。
如果同步函数被静态修饰后,使用是锁是什么?
通过验证,发现不少this,因为静态没有this,静态进内存中,内存没有本类对象,但是一定有该类对应的字节码文件对象。
即类名.class。该对象的类型是class。静态的同步方法,使用的锁就是类名.class。
*/
二、单例设计模式:
package sonyi;
/*
//单例设计模式:
*/
public class SingleDemo {
public static void main(String[] args) {
Single s = Single.getInstance();
}
}
//饿汉式:实例同时加载
/*
class Single {
private static final Single s = new Single();
private Single(){//构造函数私有,外部不可以建立对象
}
public static Single getInstance(){
return s;
}
}
*/
//懒汉式:实例的延迟加载
class Single{
private static Single s = null;
private Single(){//构造函数私有,外部不可以建立对象
}
public static Single getInstance(){
//双重判断,在多线程中,只要有创建一个对象之后,之后进来的线程直接判断条件(s == null),
//而减少了判断同步锁,提高了效率。同步锁保证了对象的唯一性。
if(s == null){
synchronized (Single.class) {//静态中不可以用this作为锁,而是用该类的字节码对象作为锁
if(s == null)
s = new Single();
}
}
return s;
}
}
三、死锁
package sonyi;
/*
死锁:一般是同步中嵌套同步
锁标记如果过多,就会出现线程等待其他线程释放锁标记,而又都不释放自己的锁标记供其他线程运行的状况。就是死锁。
死锁的问题通过线程间的通信的方式进行解决。线程间通信机制实际上也就是协调机制。
线程间通信使用的空间称之为对象的等待队列,则个队列也是属于对象的空间的。
*/
public class DeadLockDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
class Test implements Runnable{
private boolean flag;
public Test(boolean flag) {
this.flag = flag;
}
public void run(){
if(flag){
synchronized (MyLock.locka) {
System.out.println("if locka");
synchronized (MyLock.lockb) {//拿着a锁要b锁。
System.out.println("if lockb");
}
}
}
else {
synchronized (MyLock.lockb) {
System.out.println("else lockb");
synchronized (MyLock.locka) {//拿着b锁要a锁。
System.out.println("else locka");
}
}
}
}
}
//创建两个锁
class MyLock{
static Object locka = new Object();
static Object lockb = new Object();
}