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

黑马程序员——多线程

2013-06-03 23:15 225 查看
 ------- android培训java培训、期待与您交流! ----------

———————————————————————————————————————————————

21、多线程

|——概念:

线程就是进程中一个负责程序执行的控制单元(执行路径)。

进程中可以同时有多个执行路径

|——多核处理器可以真正意义上的多线程

|——JVM启动时,就启动了多个线程

有一个垃圾回收机制,显式 public static void gc()运行垃圾回收器。

protected void finalize()

当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写 finalize 方法,以配置系统资源或执行其他清除。

java中垃圾回收器由JVM直接调用。显式的调用System.gc();方法也不确定垃圾回收机制会被执行

class Demo {

protected void finalize() throws Throwable {

System.out.println("finalize called.");

}

}

class ThreadDemo{

public static void main(String []args){

new Demo();

new Demo();

System.gc();//显式的调用java的垃圾回收器

new Demo();

System.out.println("Main Thread end.");

}

}

//运行结果可能会出现以下多种情况:

————————————————————————————————————————————————

Main Thread end.

finalize called.//main线程执行完执行,这里主线程结束,垃圾回收器的线程还没有结束

————————————————————————————————————————————————

finalize called.

finalize called.

Main Thread end.//调用后直接执行

————————————————————————————————————————————————

Main Thread end.//程序结束也没有执行

|——线程的创建(示例)

注-->注-->线程可以由操作系统默认创建。JVM创建的线程都定义在了主函数中

|--创建新执行线程有两种方法。

|---一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。

|---另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。

然后可以分配该类的实例,在创建 Thread
时作为一个参数来传递并启动。

|---自定义线程

class MyThread extends Thread

class MyThread implements Runnable

|---开启线程的方法(对应上边的两个自定义线程)

new MyThread().start();

new Thread(new MyThread()).start();//这里只能这样开启,不能像Thread的子类那样

|--开启自定义线程要继承Thread类,并复写run方法。

注-->注-->
线程对象调用start()方法, 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

|--线程对象调用run方法,没有开启线程

|--线程创建的时候就已经确定了线程的编号。要获得线程开启的线程,使用Thread.currentThread.getName();

这里可以通过直接调用run方法看出,没有开启线程也有编号。

|——线程的状态:

被创建 运行消亡
冻结(sleep())

start run结束wait(),唤醒用notify();

|--CPU的执行资格:可以被CPU的处理,在处理队列中排队

CPU的执行权:正在被CPU的处理

|——Runnable接口的出现就是将任务封装成一个对象。Thread 类也是Runnable接口的实现类

|——继承Thread类和实现Runnable接口的区别:

|--将线程的任务从线程的子类中分离出来,进行了单独的封装

按照面向对象的思想将任务封装成对象;

|--避免了java中类的单继承的局限性

|--所以一般情况下多用实现Runnable接口的方法,将任务封装成对象。符合了面向接口(面向对象)编程的思想。

|——示例1

class Demo extends Thread{

private String name;

Demo(String name){

this.name = name;

}

public void run(){

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

System.out.println(name+"..."+x);

}

}

class ThreadDemo{

public static void main(String []args){

new Demo("zhangsan").start();

new Demo("lisi").start();

}

}

//运行结果可能会出现以下多种情况:

————————————————————————————————————————————————

zhangsan...0
zhangsan...0

zhangsan...1
zhangsan...1

zhangsan...2
lisi...0

zhangsan...3
lisi...1

zhangsan...4
lisi...2

zhangsan...5
zhangsan...3

lisi...0
zhangsan...4

lisi...1
lisi...3

.. ..

|——示例2
//实现Runnable接口的类。注意开启线程的方法

// new Thread(new MyThread("xxxx")).start();

|--步骤:

定义类实现Runnable接口。

实现run方法,将线程的任务代码封装到run方法中。

通过Thread类创建线程对象,并将Runnable接口的实现类作为构造函数的参数进行传递

class MyThread implements Runnable{

private String name;

MyThread(String name){

this.name = name;

}

public void run(){

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

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

}

}

注-->注-->new MyThread().start();是错误的,编译就出现错误。这样不是开启线程

(向上转型,不能调用接口中没有的方法,也就是实现类的特有方法)

|——示例3
//Thread先找自己的run方法,如果自己需要开启的线程代码在

// 一个实现了Runnable接口的类中,

new Thread(new Runnable(){

实现Runnable接口的run方法;

})

{

重写Thread父类的run方法 //先找这里,如果找不到,就会找实现Runnable接口的run方法

}.start();

new Thread(new Runnable(){

public void run(){

System.out.println("runnable Thread called");

}

}){

public void run(){

System.out.println("My Thread called.");// 匿名线程类中有自己的run方法,

}//这个时候线程会先去找Thread(父类)run方法,如果找不到,会找Runnable子类的run方法

}.start();

|——示例4
卖票实例

|--以下两种都没有实现线程间的同步问题

1继承Thread类

——————————————————————————————————————————————————————————————

class MyThread extends Thread{//继承Thread类

private static int tickets = 100;//为了使多个线程共享着100张票,

// 将tickets设置为静态的(共享数据)

public void run(){

while(tickets>=0){

try{

Thread.sleep(1);

}catch(Exception e){}

System.out.println("tickets left "+(tickets--)+" ,Thread name : "+Thread.currentThread().getName());

}

}

}

class ThreadDemo{

public static void main(String []args)throws Exception{

MyThread mt1 = new MyThread();

MyThread mt2 = new MyThread();

mt1.start();

mt2.start();

}

2实现了Runnable接口 //这样的实现类称为线程任务类

——————————————————————————————————————————————————————————————

class MyThread implements Runnable{ //这里实现了Runnable接口

private int tickets = 100;//这里可以共享也可以不共享。

public void run(){

while(tickets>=0){//假设有两个线程都进入到了这里,条件也判断过了,

// Thread-0还没有迹象执行,CPU切换到了Thread-1上。下次

// 切换过来的时候就不在进行判断

System.out.println("tickets left "+(tickets--)+

" ,Thread name : "+Thread.currentThread().getName());

}

}

}

//在主方法中这样开启线程

MyThread mt = new MyThread();

new Thread(mt).start();

new Thread(mt).start();

|——线程安全问题产生的原因:

多个线程在操作共享数据

操作共享数据线程代码有多条

|——解决线程安全问题:(同步代码块,用关键字 synchronized)

将需要同步的代码封装起来。只有当当前线程将这些代码执行完毕后,其他线程才能执行

|--同步的前提:

有多个 线程并使用同一个锁。

|--同步函数:在函数名前加 synchronized同步函数的锁是 this

public synchronized void show(){

if(tickets>0)

// System.out.println("saled tickets:"+(100-tickets));

System.out.println("tickets left "+(tickets--)+

" ,Thread name : "+Thread.currentThread().getName());

}

public void run(){

while(true){

show();

}

}

注-->注-->|--一个普通方法和一个静态方法同步就需要用到字节码

静态方法就没有 this
需要用到类的字节码

|---获得字节码的两种方式:

对象.getClass();

类.class;

|--单例设计模式的线程安全问题:

|---饿汉式没有问题

|---懒汉式有线程安全问题

解决单例设计的第一种模式

同步函数:降低了效率

______________________________________________________

class Single{

private static final Single s = null;

public synchronized Single getInstance(){//

if(s == null)

s = new Single();

return s;

}

}

解决单例设计的第二种模式提高效率

同步代码块

______________________________________________________

class Single{

private static final Single s = null;

public synchronized Single getInstance(){//

if(s == null)

synchronized(Single.class){

if(s == null)

s = new Single();

}

return s;

}

}

|---死锁示例:

解析:

示例中有为了解决问题,又开启了两个线程,

class Demo implements Runnable{

boolean flag = true;

private int num = 100;

String s = "";

public synchronized void show(){

synchronized(s){//Thread-0要进入这里的时候发现Thread-1拿着这个锁,等待

if(num>0){

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

}

}

}

public void run(){

if(flag){
//Thread-0满足条件,拿到了s这个锁。这个时候可能Thread-1已经拿到了this这个锁。

while(num>0){
//加入两个线程各自拿到一个锁后,状态改变。两个线程还没有释放掉锁拿到的锁

synchronized(s){

show();

}

}

}

else{

while(num>0){

show();
//要运行到这里的时候,一看Thread-1拿着这个this锁。等待

}

}

}

}

public class ThreadDemo{

public static void main(String []args){

Demo d = new Demo();

new Thread(d).start();

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

d.flag = false;

new Thread(d).start();

}

}

死锁清晰实例

————————————————————————————————————————————————————————————————————

class MyLock{

public static final MyLock locka = new MyLock();

public static final MyLock lockb = new MyLock();

}

class Demo implements Runnable{

boolean flag ;

Demo(boolean flag){

this.flag = flag;

}

public void run(){

if(flag){

synchronized(MyLock.locka){

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

synchronized(MyLock.lockb){

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

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

}

}

}

else{

synchronized(MyLock.lockb){

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

synchronized(MyLock.locka){

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

}

}

}

}

}

public class ThreadDemo{

public static void main(String []args){

Demo d1 = new Demo(true);

Demo d2 = new Demo(false);

new Thread(d1).start();

new Thread(d2).start();

}

}

|——线程间通信
(等待唤醒机制)

|--wait();
让线程处于冻结状态,被wait的线程会被存储到线程池中

notify(); 唤醒线程池中的一个线程。(任意)

notifyAll();唤醒线程池中的所有线程

|--这些方法都 必须定义在同步中,

因为这些方法是用于操作线程状态的方法,

必须要明确到底操作的是哪个锁。

class Resource{

String name;

String sex;

boolean flag = true;

public synchronized void put(String name,String sex){

if(!flag){

try {

wait();//当标志位为false,线程wait这里其实是r.wait();

} catch (InterruptedException e1){}

}

this.name = name;

this .sex = sex;

flag = false;
//执行完后,将标志位状态改变。

notify();
//并且唤醒一个线程这里其实是r.notify();

}

public synchronized void get(){

if(flag){

try {

wait(); //当标志位为true,线程wait。这里其实是r.wait();

} catch (InterruptedException e1){}

}

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

flag = true;//执行完后,将标志位状态改变。

notify();//并且唤醒一个线程这里其实是r.notify();

}

}

class Input implements Runnable{

Resource r ;

private int x=0;

Input(Resource r){

this.r = r;

}

public void run(){ //这里有一个线程任务

while(true){

if(x == 0){

r.put("zhangsan","nan");//这里可以将同步代码封装

}else{

r.put("lisi","nv");

}

x = (x+1)%2;

}

}

}

class Output implements Runnable{

Resource r ;

Output(Resource r){

this.r = r;

}

public void run(){ //这里有一个线程任务

while(true){

r.get();
//这里可以将同步代码封装

}

}

}

经典 多生产者多消费者问题

——————————————————————————————————————————————————————————————————

语言描述:

两个知识点:

用while不用if判断标记 ,解决了可能出现的一个重复生产,或者一个重复消费的情况

用notifyAll()不用notify();//避免生产线程唤醒生产线程(这样在while下一判断,又跑线程池里了),

// 避免消费线程唤醒消费线程。这是产生死锁的情况

这里有四个线程:

Thread-0和Thread-1作为生产者:

Thread-2和Thread-3作为消费者:

flag一开始是false,当Thread-0来到线程任务后,判断flag,flag为false。Thread-0生产了一只烤鸭。

此时notify()或是notifyAll()是没有唤醒任何线程池中的线程。

将flag设置为true。如果用notify(),任意唤醒一个线程。可能是Thread-1;

假设这个时候:Thread-1也来到线程生产任务。进行等待,Thread-1进入线程池。

这个时候flag依然还是true,Thread-0进入生产线程。判断后也进入等待,Thread-0也会进入线程池。

此时生产线程都没有了执行权

同理,消费者也是一样的。

if和while

if(flag)

try {wait();} catch (InterruptedException e){}

Thread-0 到这里以判断,flag为false。直接往下运行。生产烤鸭1

flag = true;

Thread-2 判断一次,flag为true,向下运行,消费烤鸭1

flag = false;

Thread-3判断一次,flag为false,Thread-3被冻结

Thread-1到这里判断,flag为false。

flag = true;

Thread-2 判断一次,flag为true。唤醒了Thread-3,这个时候就不在进行判断。直接消费一次。

这样就出现了同一只烤鸭消费了两次

/* // Thread-1到这里判断,flag为true。Thread-1冻结。

// 这个时候线程池中只有Thread-1 ,唤醒了Thread-1。

// Thread-1 直接往下运行(if不在进行判断),生产烤鸭2

flag = true;

假设这个时候又来了Thread-0 ,判断后,wait()Thead-0进入线程池。

Thread-1来了后,flag为true,向下运行,消费烤鸭2 */

class Resource{

private String name ;

private int count =1;

private boolean flag = false;

public synchronized void put(String name){

while(flag)
//这里用while不用if,目的是在线程唤醒后再判断一次

try {wait();} catch (InterruptedException e){}//Thread-

this.name = name+count;

count++;

System.out.println(Thread.currentThread().getName()+"...Producer..........s"+this.name);

flag = true;

notifyAll();

}

public synchronized void get(){

while(!flag)

try {wait();} catch (InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"...xiaofeile...."+name);

flag = false;

notifyAll();

}

}

// 生产者线程任务类

class Producer implements Runnable{

Resource r ;

Producer(Resource r){

this.r = r;

}

public void run(){

while(true)

r.put("kaoya");

}

}

//消费者线程任务类

class Constumer implements Runnable{

Resource r ;

Constumer(Resource r){

this.r = r;

}

public void run(){

while(true)

r.get();

}

}

public class ProducerDemo{

public static void main(String []args){

Resource r = new Resource();

Producer p1 = new Producer(r);

Producer p2 = new Producer(r);

Constumer c1 = new Constumer(r);

Constumer c2 = new Constumer(r);

new Thread(p1).start();

new Thread(p2).start();

new Thread(c1).start();

new Thread(c2).start();

}

}

|——java 1.5的新特性

Lock 实现提供了比使用 synchronized

方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock

实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了

Object 监视器方法的使用

对比以上示例:

使用了一个锁,两个监视器。目的是,实现线程间的同步(互斥)。唤醒机制,只唤醒需要监视器中的线程对象

public void put(String name){

lock.lock(); //直接在这里加了锁,相当于家里synshronized将函数同步

try{

while(flag)
//这里使用的是监视器con1的await()方法

try {con1.await();} catch (InterruptedException e){ e.printStackTrace();}

this.name = name+count;

count++;

System.out.println(Thread.currentThread().getName()+"...Producer..........s"+this.name);

flag = true;

// notifyAll();

con2.signal();//这里使用的是监视器的signal,唤醒消费者线程(任意一个)。

}

finally{

lock.unlock();//锁用完了就要释放掉

}

}

public void get(){

lock.lock();

try{

while(!flag)
//这里使用的是监视器con2的await()方法

try {con2.await();} catch (InterruptedException e){e.printStackTrace();}

System.out.println(Thread.currentThread().getName()+"...xiaofeile...."+name);

flag = false;

// notifyAll();

con1.signalAll();//这里使用的是监视器的signal,唤醒生产者线程(任意一个)。

}

finally{

lock.unlock();

}

}

}

上边示例中出现了Lock接口:替代了同步代码块或者同步函数。将同步的隐式操作变成显示锁,

并且更加灵活。(可以使用多组监视器)

|--守护线程(幽灵线程)

后台线程和前台线程同样都开启。前台线程必须手动结束,后台线程在前台线程结束的情况下,后台线程也会自动消失

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

———————————————————————————————————————————————
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: