您的位置:首页 > 职场人生

黑马程序员—7、JAVA基础&多线程

2012-05-04 22:05 567 查看
黑马程序员—7、JAVA基础&多线程

--------------------- android培训java培训、期待与您交流!

进程:正在执行的程序。就是一个应用程序在内存中开辟的空间。

线程:其实就是进程中的一个控制单元。它负责就是程序的执行。

一个进程中至少有一个线程。JVM本身就是多线程的。因为在程序运行过程中会在堆内存产生很多的垃圾。就需要被垃圾回收器进行回收。main函数代码执行时,也在运行着垃圾回收。所以是同时执行的,这就是两个独立线程来进行控制的。执行垃圾回收的线程,称为垃圾回收线程。执行main函数的线程,称为主线程。

创建一个执行路径的目的就是,让单独一个线程去执行指定的代码。和其他代码同时执行。

对于主线程:它的运行的代码都存储在主函数中。

对于垃圾回收线程:它运行就是用于回收对象垃圾的代码。

描述线程的对象是 Thread 类。继承 Thread 类,覆盖其run方法。Thread 本身就是一个类,就是一个线程Thread,直接创建其对象,就是一个线程。run既然是运行方法,那么他里面存储了线程要运行的代码。可是我们想要创建线程运行我们自己制定的代码,那么这时就应该利用继承思想,将 Thread 类进行继承,并覆盖已经有的run方法,定义自己要运行的线程代码。

步骤:

1、继承 Thread 类

2、覆盖 Thread 类中的run方法 在该方法中定义需要运行的代码

3、调用线程对象中的start方法开启线程,并调用线程的run方法

主线程运行的代码在主函数中,自定义线程运行的代码在run方法中。发现程序运行的结果每次都不一样。那是因为多线程的随机性造成的。CPU做着快速的切换,运行着各个线程。随机性原理是由于CPU做着快速的切换造成的。形象的称为多个线程在抢夺cpu的执行权。哪个线程获取到cpu的执行权,哪个线程就执行。

主线程名称为 “main”。其他中定义线程名称 Thread-编号 编号从零开始

创建线程的目的:就为了当前线程和目前正在运行的线程同时执行。



垃圾回收机制:

垃圾回收器在不定时的时候回收堆内存中没有引用的一些对象。有时候垃圾回收器还没有来得急回堆内存中的垃圾,JVM就已经关闭那么此时所有分配的空间全部都被随着JVM的关机而全部释放。每次调用垃圾回收器,打印的结果不一样,原因是垃圾回收线程和其他线程抢夺资源。多线程不容易控制,随机性。

创建线程之后,必须用start开启线程。线程在运行的状态



售票的程序:

class Ticket implements Runnable//extends Thread

{

private int num = 100;

public void run()

{

while(true)

{

if(num>0)

{

System.out.println(Thread.currentThread().getName()+"...sale:"+num--);

}

}

}

}

class TicketDemo

{

public static void main(String[] args)

{

Ticket t = new Ticket();

//用Thread类创建了四个线程。并开启.

Thread t1 = new Thread(t);

Thread t2 = new Thread(t);

Thread t3 = new Thread(t);

Thread t4 = new Thread(t);

t1.start();

t2.start();

t3.start();

t4.start();

}

}





final finally finalize 区别?

final finally 是两个关键字。final 可以修饰 类 方法 变量 finally 异常中一定能被执行到的语句,通常用于关闭资源。finalize 是一个方法,被垃圾回收器调用垃圾回收器的。

实现 Runnable 接口

现实Runnable接口的步骤:

1、定义类实现 Runnable 接口

2、覆盖Runnable接口中的run方法。将线程要运行的代码存储到run方法中

3、通过Thread类创建线程对象。

4、将实现了Runnable接口的子类对象作为实际参数传递给 Thread 类的构造函数。为什么这么做呢?因为线程对象创建后,必须明确要运行的run方法,而该run方法所属的对象是Runnable接口的子类对象,所以将该子类对象传递Thread类的构造函数

5、调用Thread类的start方法

实现 Runnable 接口的好处:

1、 Runnable 接口的出现,避免了单继承的局限性

2、 Runnable 直接将线程运行的代码(任务)封装Runnable接口类型的对象中,也就是将线程的任务封装成了对象。这样就实现了线程对象和任务对象的解耦。所以这种方法更为常用。



目前所接触的三个线程:主线程、垃圾回收线程、自定义线程(来自于继承 Thread 或者 实现 Runnable)。

线程安全性问题:造成安全问题的原因:

1、多线程同时操作了共享数据

2、多线程的任务代码中操作共享数据的语句不止一条。

class Ticket implements Runnable

{

private int num = 100;

private Object obj = new Object();

public void run()

{

while(true)

{ obj 就代码一个锁而已

synchronized(obj) 同步代码块

{

if(num>0)

{

try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"...sale:"+num--);

}

}

}

}

}

class TicketDemo2

{

public static void main(String[] args)

{

Ticket t = new Ticket();

Thread t1 = new Thread(t);

Thread t2 = new Thread(t);

Thread t3 = new Thread(t);

Thread t4 = new Thread(t);

t1.start();

t2.start();

t3.start();

t4.start();

}

}



解决思想:让一个线程在执行多条操作共享数据的运算过程中,其他线程不要参与共享数据的操作。

java中的解决线程安全问题方案:加同步。将需要同步的代码封装到了指定同步语句块当中即可。

同步代码块的体现:

synchronized(对象)

{

需要被同步的代码;

}

同步的好处:解决了线程的安全问题。

同步的前提:

1、同步中如果只有一个线程在执行。是没有必要同步的。

2、如果有多个线程需要同步,必须要保证它们使用的一个锁。这个前提的好处:如果在多线程中加入了同步后,还是出现了安全问题的话。这时就可以用这个前提来对程序进行分析。

同步的弊端:对程序性能有一些影响,会降低一些效率。

同步的另一种表现形式:同步函数,就是让函数具备同步性。

同步函数和同步代码块的区别:同步函数使用的锁是: this 。同步代码块使用的锁是任意对象,一般开发建议使用同步代码块。静态同步函数使用的锁是 类名.class 是静态函数所属类的字节码文件对象。

class Bank

{

private int sum;

private Object obj = new Object();

public synchronized void add(int num)//同步函数 这里使用的锁是当前这个对象的引用this

{

sum = sum + num;

try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println("sum="+sum);

}

}

class Customer implements Runnable

{

private Bank b = new Bank();

public void run()

{

for(int x=0; x<3; x++)

{

b.add(100);

}

}

}

class BankDemo

{

public static void main(String[] args)

{

Customer c = new Customer();

Thread t1 = new Thread(c);

Thread t2 = new Thread(c);

t1.start();

t2.start();

}

}

卖票的实例:要求是一个线程执行同步代码块,一个线程执行同步函数;

class Ticket implements Runnable

{

private static int num = 100;

private Object obj = new Object();

private boolean flag = true;

public void run()

{

if(flag)

while(true)

{

synchronized(obj) 将这里的锁改为this就可以了

{

if(num>0)

{

try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"...code....:"+num--);

}

}

}

else

while(true)

this.show();

}

public synchronized void show()

{

if(num>0)

{

try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"......func........:"+num--);

}

}

public void setFlag()

{

flag = false;

}

}

class StaticLockDemo

{

public static void main(String[] args)

{

Ticket t = new Ticket();

Thread t1 = new Thread(t);

Thread t2 = new Thread(t);

t1.start();

try{Thread.sleep(10);}catch(InterruptedException e){}

t.setFlag();

t2.start();

}

}

通过这个例子发现了同步使用了,但是还是出现了零号票,这个时候什么原因造成的呢?这个时候就需要用前面说过的两个原因来分析,这个时候首先判断是不是同一把锁。经过分析后发现同步代码块执行的时候用的锁是obj锁,而同步函数使用的this这个锁。这个时候可以将同步代码块的锁该为this。

单例设计模式中懒汉式中存在的安全问题

因为懒汉式存在多线程并发访问安全问题,所以需要同步解决现存安全问题,但是同步会降低效率,所以可以通过双重if判断的形式,解决效率问题,降低对锁的判断次数。但是在代码体现上较多,所以开发的时候采用饿汉式,而面试中,常用懒汉式

//饿汉式

class Single

{

private static 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()

{

if( s==null )

s = new Single();

return s;

}

}

改良的懒汉式

class Single

{

private static Single s = null;

private Single(){}

public static Single getInstance() //可以在函数上加同步,但是效率低,因此采用下面形式

{

if( s==null )

{

synchronized(Single.class)

{

if( s==null )

s = new Single();

}

return s;

}

}

}



死锁:面试:容易引发死锁的情况之一:同步的嵌套

为什么是object 中的方法呢?因为这些方法都是必须要标识出来所属的锁。而锁是任意的对象,能被任意对象所访问到的对象,应该是在使用等待唤醒时都需要标记。flag等待唤醒机制中,最常见的体现就是生产者和消费者问题。当多生产多消费者同时出现时,用while做判断,并且用notifyAll来唤醒,此时唤醒中肯定有对方。但是这个只适合单生产者和单消费者,当面对多生产者和多消费者的时候有无法保证了。

等待唤醒机制中,最常见的体现就是生产者消费者问题,发现两个问题:

1,出现了错误的数据。是因为多生产和多消费的时候,被唤醒的线程没有再次判断标记就执行了。解决是将if判断变成while判断。

2,发现有了while判断后,死锁了。因为本方线程唤醒的有可能还是本方线程。所以导致了死锁。解决:本方必须唤醒对方才有效。notify只能唤醒一个,还不确定。所以干脆唤醒全部,肯定包含对方,至于被唤醒的本方,会判断标记是否继续等待。

死锁实例:面试题

class Test implements Runnable

{

private boolean flag = true;

Test(boolean flag)

{

this.flag = flag;

}

public void run()

{

while(true)

{

if(flag)

{

synchronized(MyLock.lock_a)

{

System.out.println(Thread.currentThread().getName()+"..if lock_a");

synchronized(MyLock.lock_b)

{

System.out.println(Thread.currentThread().getName()+"..if lock_b");

}

}

}

else

{

synchronized(MyLock.lock_b)

{

System.out.println(Thread.currentThread().getName()+"..else lock_b");

synchronized(MyLock.lock_a)

{

System.out.println(Thread.currentThread().getName()+"..else lock_a");

}

}

}

}

}

}

class MyLock

{

public static Object lock_a = new Object();

public static Object lock_b = new Object();

}

class DeadLockTest

{

public static void main(String[] args)

{

Test t1 = new Test(true);

Test t2 = new Test(false);

new Thread(t1).start();

new Thread(t2).start();

}

}

/*

演示死锁。

容易引发死锁的情况之一:同步的嵌套。

*/

class Ticket implements Runnable

{

private int num = 100;

private Object obj = new Object();

private boolean flag = true;

public void run()

{

if(flag)

while(true)

{

synchronized(obj)

{

show();

}

}

else

while(true)

this.show();

}

public synchronized void show() 这个锁是this

{

synchronized(obj)

{

if(num>0)

{

try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"......sale........:"+num--);

}

}

}

public void setFlag()

{

flag = false;

}

}

class DeadLockDemo

{

public static void main(String[] args)

{

Ticket t = new Ticket();

Thread t1 = new Thread(t);

Thread t2 = new Thread(t);

t1.start();

try{Thread.sleep(10);}catch(InterruptedException e){}

t.setFlag();

t2.start();

}

}

线程间通信:多个线程在处理同一个资源,但处理的动作却不一样,动作不同意味着线程的任务是不一样的,就需要对任务进行单的描述和封装。

/*

线程间通信:

多个线程在处理同一个资源,但是处理的动作却不一样。

动作不同意味着线程的任务是不一样的,就需要对任务对象进行单独的描述和封装。

*/

class Resource // 创建资源

{

String name;

/*

线程间通信:

多个线程在处理同一个资源,但是处理的动作却不一样。

动作不同意味着线程的任务是不一样的,就需要对任务对象进行单独的描述和封装。

*/

class Resource // 创建资源

{

String name;

String sex;

}

class Input implements Runnable //定义线程任务 输入数据

{

Resource r;

Input(Resource r)

{

this.r = r;

}

public void run()

{

int x = 0;

while (true)

{

synchronized(r) // 同一把锁就是这个资源对象

{

if(x==0)

{

r.name = "mike";

r.sex = "nan";

}

else

{

r.name = "丽丽";

r.sex = "女女女女女";

}

}

x = (x+1)%2;

}

}

}

class Output implements Runnable //定义线程任务 取出数据

{

Resource r;

Output(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

synchronized(r) // 同一把锁就是这个资源对象

{

System.out.println(r.name+"....."+r.sex);

}

}

}

}

class ResourceDemo

{

public static void main(String[] args)

{

Resource r = new Resource(); // 创建资源

Input in = new Input(r); // 建立输入线程对象

Output out = new Output(r); // 建立输出线程对象

Thread t1 = new Thread(in);

Thread t2 = new Thread(out);

t1.start();

t2.start();

}

}

线程间通信最重要的机制,等待唤醒机制.

wait() 等待 让当前线程处于冻结状态,当前线程就被存储到了线程池中。

notify(); 唤醒 唤醒线程池中的任意一个线程,让该线程恢复到运行状态,会具备CPU的执行资格

notifyall(); 唤醒全部 唤醒线程池中的所有等待线程。具备CPU的执行资格

wait() notify() 必须使用在同步当中。要标识操作的线程中所属的同一步锁。

举例就是:抓人的游戏。

notify必须要用到同步中,要标识操作的线程所属的同步的锁。换句话说:wait,到底是让哪个锁上的线程等待了。notify,到底是唤醒了哪个锁上被等待的线程。

而这些方法在Object类中。为什么是Object中的方法呢?因为这些方法都是必须要标识出所属的锁,而锁是任意的对象。能被任意对象调用的方法一定定义在Object类中。

在这个例子中,输入数据和输出数据都只有一个线程。即就是单输入,单输出。此题如果在多输入和多输出就又会出问题

class Resource

{

String name;

String sex;

boolean flag = false;

}

class Input implements Runnable

{

Resource r;

Input(Resource r)

{

this.r = r;

}

public void run()

{

int x = 0;

while (true)

{

synchronized(r) // 当一个输入线程获取锁后,开始执行

{

if(r.flag) // 判断标记,

try{r.wait();}catch(Exception e){}

if(x==0)

{

r.name = "mike";

r.sex = "nan";

}

else

{

r.name = "丽丽";

r.sex = "女女女女女";

}

r.flag = true; // 当这个输入线程执行结束后,将标记该为真

r.notify(); // 并且唤醒其他线程,但这里还有问题,

//因为notify 只能唤醒一个线程,这时我们应该标明唤醒那个线程中的锁

}

x = (x+1)%2;

}

}

}

class Output implements Runnable

{

Resource r;

Output(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

synchronized(r) // 当一个输出线程获取锁后,开始执行

{

if(!r.flag) // 判断标记,

try{r.wait();}catch(Exception e){}

System.out.println(r.name+"....."+r.sex);

r.flag = false; // 当这个输出线程执行结束后,将标记该为真

r.notify(); // 并且唤醒其他线程,但这里还有问题

} //因为notify 只能唤醒一个线程,这时我们应该标明唤醒那个线程中的锁

}

}

}

class ResourceDemo2

{

public static void main(String[] args)

{

Resource r = new Resource();

Input in = new Input(r);

Output out = new Output(r);

Thread t1 = new Thread(in);

Thread t2 = new Thread(out);

t1.start();

t2.start();

}

}

上面代码需要要优化。因为输入数据和输出数据都应该是资源的自身的属性。这个时候可以用get和set对外提供方法。

class Resource

{

private String name;

private String sex;

private boolean flag = false;

public synchronized void set(String name,String sex)

{

if(flag)

try{this.wait();}catch(Exception e){}

this.name = name;

this.sex = sex;

flag = true;

this.notify();

}

public synchronized void out()

{

if(!flag)

try{this.wait();}catch(Exception e){}

System.out.println(name+"----"+sex);

flag = false;

this.notify();

}

}

class Input implements Runnable

{

Resource r;

Input(Resource r)

{

this.r = r;

}

public void run()

{

int x = 0;

while (true)

{

if(x==0)

r.set("mike","nan");

else

r.set("丽丽","女女女女女");

x = (x+1)%2;

}

}

}

class Output implements Runnable

{

Resource r;

Output(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

r.out();

}

}

}

等待唤醒机制中,最常见的体现就是生产者消费者问题

发现两个问题:

1,出现了错误的数据。是因为多生产和多消费的时候,被唤醒的线程没有再次判断标记就执行了。解决是讲if判断变成while判断。

2,发现有了while判断后,死锁了。因为本方线程唤醒的有可能还是本方线程。所以导致了死锁。

解决:本方必须唤醒对方才有效。notify只能唤醒一个,还不确定。所以干脆唤醒全部,肯定包含对方,至于被唤醒的本方,会判断标记是否继续等待。

class Resource

{

private String name;

private int count;

private boolean flag;

public synchronized void set(String name)

{

while(flag)

try{this.wait();}catch(Exception e){}// (活) t1 (活)t0

this.name = name+count;

count++;

System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name);//生产者 商品0 生产者 商品1 商品2

flag = true;

notifyAll(); //唤醒所有

}

public synchronized void out()//

{

while(!flag)

try{this.wait();}catch(Exception e){}//t2 t3

System.out.println(Thread.currentThread().getName()+"......消费者......."+this.name);//消费 商品0

flag = false;

notifyAll(); //唤醒所有

}

}

class Producer implements Runnable

{

private Resource r;

Producer(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

r.set("商品");

}

}

}

class Consumer implements Runnable

{

private Resource r;

Consumer(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

r.out();

}

}

}

class ProConDemo

{ public static void main(String[] args)

{ Resource r = new Resource();

Producer pro = new Producer(r);

Consumer con = new Consumer(r);

Thread t0 = new Thread(pro);

Thread t1 = new Thread(pro);

Thread t2 = new Thread(con);

Thread t3 = new Thread(con);

t0.start();

t1.start();

t2.start();

t3.start();

}

}

JDK1.5版本后,对多线程中的内部细节进行了升级改良。在java.util.concurrent.locks包中提供了一个Lock接口。

Lock接口中提供了 lock()获取锁 unlock释放锁的操作。Lock接口更符合面向对象的思想,将锁这种事物封装成了对象。

public void run()

{

synchronized(obj)

{//获取锁。

code...

//释放锁。

}

}

但是对于释放和获取锁的操作,都是隐式的。JDK1.5后,就有了新的方法,将锁封装成了对象。因为释放锁和获取锁动作,锁自己最清楚。 锁对象的类型就是Lock接口。并提供了,显示的对锁的获取和释放的操作方法。

Lock lock;

public void run()

{

try

{

lock.lock();//获取锁。

code...throw ...

}

finally

{

lock.unlock();//释放锁.

}

}

Lock接口替代了synchronized 。Condition替代了Object类中监视器方法 wait notify notifyAll。将监视器方法单独封装成了Condition对象。而且一个锁上可以组合多组监视器对象。实现了多生产者多消费者时,本方只唤醒对方中一个的操作,提高效率。

import java.util.concurrent.locks.*;

class Resource

{

private String name;

private int count;

private boolean flag;

private Lock lock = new ReentrantLock();

private Condition con1 = lock.newCondition();//一组监视器监视生产者

private Condition con2 = lock.newCondition();//一组监视器监视生产者

public void set(String name)//

{

lock.lock();//获取锁 .

try

{

while(flag)

try{con1.await();}catch(Exception e){} // (活) t1 (活)t0

this.name = name+count;

count++;

System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name);//生产者 商品0 生产者 商品1 商品2

flag = true;

con2.signal();

}

finally

{

lock.unlock();//释放锁。

}

}

public void out()//

{

lock.lock();

try

{

while(!flag)

try{con2.await();}catch(Exception e){}//t2 t3

System.out.println(Thread.currentThread().getName()+"......消费者......."+this.name);//消费 商品0

flag = false;

con1.signal();

}

finally

{

lock.unlock();

}

}

}

class Producer implements Runnable

{

private Resource r;

Producer(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

r.set("商品");

}

}

}

class Consumer implements Runnable

{

private Resource r;

Consumer(Resource r)

{

this.r = r;

}

public void run()

{

while(true)

{

r.out();

}

}

}

class ProConDemo2

{

public static void main(String[] args)

{

Resource r = new Resource();

Producer pro = new Producer(r);

Consumer con = new Consumer(r);

Thread t0 = new Thread(pro);

Thread t1 = new Thread(pro);

Thread t2 = new Thread(con);

Thread t3 = new Thread(con);

t0.start();

t1.start();

t2.start();

t3.start();

}

}

多线程中的等待和唤醒机制:

wait()和sleep()区别 面试题

1、sleep 必须指定时间,wait可以指定时间,也可以不指定时间

sleep 和 wait 都可以让线程临时冻结,释放执行权,cpu不在处理当前线程

2、wait方法必须定义在同步中,sleep不一定

3、在同步中的wait和sleep的对于执行权和锁的处理不同。

3.1 sleep释放CPU的执行权,但在同步中线程不释放锁

3.2 wait释放CPU的执行权,同时线程也释放锁。

停止线程:

stop方法已经过时。

所以只剩了一种办法,线程执行的代码结束,线程会自动终止。

1,run方法中通常都有循环语句,所以只要让循环结束即可,所以只要控制住循环的条件。最简单的方式就是定义标记。

2、如果run方法中有同步可以让线程处于冻结状态方法,比如wait,那么线程就不会取读取标记。那么线程的循环也就无法结束,run方法也不会结束。这时必须让线程恢复到运行状态才可以有机会读取到标记。所以可以通过正常的恢复方法比如notify , sleep 时间到,但是如果没有办法正常恢复,就必须使用强制手段,interrupt方法,强制将线程的冻结状态清除,让其恢复到运行状态,因为只要线程运行就可以读取标记,结束。强制动作会发生异常,需要处理。

前后台线程:main线程 通过start 开启的线程都是属于前台线程。当一个线程被setDeamon标记后,就变成了后台线程。前后台线程没有太大区别,他们都在抢夺cpu的执行权,但当前台线程结束后,后台线程也会随着JVM的退出而结束。

Thread 类中的 toString yield setPriority方法 线程的优先级的数据范围1到10。

setPriority 设置线程的优先级

join() 方法使用的时候会抛出异常:等待该线程终止。一般用于在执行当中临时加入这个线程。

class ThreadDemo2

{

public static void main(String[] args)

{

new Thread()

{

public void run()

{

for(int x=0; x<90; x++)

{

System.out.println(Thread.currentThread().getName()+"....x======"+x);

}

}

}.start();

Runnable r = new Runnable()

{

public void run()

{

for(int y=0; y<90; y++)

{

System.out.println(Thread.currentThread().getName()+"....y======"+y);

}

}

};

Thread t = new Thread(r);

t.start();

for(int z=0; z<90; z++)

{

System.out.println(Thread.currentThread().getName()+"....z======"+z);

}

}

--------------------- android培训java培训、期待与您交流!

----------------------详细请查看:http://edu.csdn.net/heima
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: