首页 > 编程语言 > 详细

多线程

时间:2016-02-28 16:24:58      阅读:105      评论:0      收藏:0      [点我收藏+]

   进程  

   是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序就是一个执行路径或者叫一个控制单元。

   线程

   就是进程中的一个独立的控制单元。线程在控制着进程的执行。

   一个进程中至少有一个线程。

   Java VM启动时会有一个进程java.exe,该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在main方法中,该线程称之为主线程

   扩展知识:其实更细节说明JVM,JVM不止启动一个线程,还有负责垃圾回收机制的线程

   如何在自定义的代码中,自定义定义一个线程?

   通过对API的查找,java已经提供了对线程这类事物的描述,即Thread类。

   创建线程的第一种方式:继承Thread类。

   步骤:

  1. 继承Thread类
  2. 重写Thread类中的run()。目的:将自定义的代码存储在run(),让线程运行
  3. 调用线程的start()。该方法有2个作用:启动线程,调用run()

   如下代码:

class Demo extends Thread {
    public void run() {
        for(int x = 0; x < 60; x++)
            System.out.println("demo run----"+x);
    }
}
public class ThreadDemo {

    public static void main(String[] args) {
        Demo d = new Demo();//创建好一个线程
//      d.start();//开启线程,并执行该线程的run()
        d.run();//仅仅是对象的调用方法,而线程创建了,并没有被运行 
        
        for(int x = 0; x < 60; x++)
            System.out.println("hello world!---"+x);
        
//      Thread t = new Thread();
//      t.start();
    }

}

   发现运行结果每一次都不同。

   因为多个线程都在获取CPU的执行权,CPU执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行(多核除外)。CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象地把多线程的运行形容为互相抢夺CPU的执行权,这就是多线程的一个特点:随机性。谁抢到谁执行,至于执行多长,CPU说了算。

   为什么要覆盖run()呢?

   Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run()。也就是说Thread类中的run()用于存储线程要运行的代码。

 

   练习:

   创建两个线程,和主线程交替执行。

   代码示例:

class Test extends Thread {
//  private String name;
    
    Test(String name) {
        super(name);
    }
    public void run() {
        for(int x = 0; x < 60; x++)
            System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);
    }
}
public class ThreadTest {

    public static void main(String[] args) {
        Test t1 = new Test("one----");
        Test t2 = new Test("two++++");
        t1.start();
        t2.start();
        
        for(int x = 0; x < 60; x++)
            System.out.println("main run..."+x);
    }

}

   原来线程都有自己默认的名称:Thread-编号,该编号从0开始。

   static Thread currentThread():获取当前线程对象

   getName():获取线程名称

   设置线程名称:setName()或者构造函数

   

   以此例引申出创建线程的第二种方式:

   需求:简单的卖票程序。多个窗口同时买票。

   代码如下:  

/*
class Ticket extends Thread {
    private int tick = 100;
    public void run() {
        while(true) {
            if(tick > 0)
                System.out.println(Thread.currentThread().getName()+"sale: "+tick--);
        }
    }
}
*/
class Ticket implements Runnable {
    private int tick = 100;
    public void run() {
        while(true) {
            if(tick > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {

                }
                System.out.println(Thread.currentThread().getName()+"sale: "+tick--);
            }
        }
    }
}
public class ThreadTest1 {

    public static void main(String[] args) {
        /*
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        Ticket t4 = new Ticket();
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        */
        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();
    }

}

   创建线程的第二种方式:实现Runnable接口。

   步骤:

  1. 定义类实现Runnable接口
  2. 覆盖Runnable接口中的run()。目的:将线程要运行的代码存放在该run()中
  3. 通过Thread类建立线程对象
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。为什么要将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数?——因为自定义的run()所属的对象是Runnable接口的子类对象,所以要让线程去运行指定对象的run(),就必须明确该run()所属的对象
  5. 调用Thread类的start()开启线程并调用Runnable接口子类的run方法

   实现方式和继承方式有什么区别呢?

  1. 实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式
  2. 继承Thread:线程代码存放Thread子类的run()中
  3. 实现Runnable:线程代码存放在接口的子类的run()中

   

   还是以简单的卖票程序为例:

   通过分析发现打印出0、-1、-2等错票,多线程的运行出现了安全问题。

   问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致了共享数据的错误。

   解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

   java对于多线程的安全问题提供了专业的解决方式——就是同步代码块

   格式:

synchronized(对象) {
    需要被同步的代码
}

   对象如同,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有锁。

   火车上的卫生间---经典同步例子。

   同步的前提:

  1. 必须要有两个或者两个以上的线程
  2. 必须是多个线程使用同一个锁 

   必须保证同步中只有一个线程在运行

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

   同步的弊端:多个线程需要判断锁,较为消耗资源。

   示例代码如下:

class Ticket implements Runnable {
    private int tick = 100;
    Object obj = new Object();
    synchronized
    public void run() {
        while(true) {
            synchronized (obj) {
                if(tick > 0) {
                    //模拟多线程的运行出现的安全问题
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        
                    }
                    System.out.println(Thread.currentThread().getName()+"sale: "+tick--);
                }
            }
        }
    }
}

public class ThreadDemo1 {

    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();
    }

} 

   

   同步函数

   以此例引申出同步函数:   

   需求:银行有一个金库,有两个储户,分别存300元,每次存100元,存3次。

   程序代码如下:

class Bank {
    private int sum;
    Object obj = new Object();
    public void add(int n) {
        sum = sum + n;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            
        }
        System.out.println("sum="+sum);
    }
}
class Cus implements Runnable {
    private Bank b = new Bank();
    public void run() {
        for(int x = 0; x < 3; x++) {
            b.add(100);
        }
    }
    
}
public class BankDemo {

    public static void main(String[] args) {
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }

}

   目的:该程序是否有安全问题,如果有,如何解决?

   如何找到问题:

  1. 明确哪些代码是多线程运行代码
  2. 明确共享数据
  3. 明确多线程运行代码中哪些语句是操作共享数据的

   修改后代码如下:

class Bank {
    private int sum;
    Object obj = new Object();
    //同步函数
    public synchronized void add(int n) {
//        synchronized (obj) {
            sum = sum + n;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                
            }
            System.out.println("sum="+sum);
//        }
    }
}
class Cus implements Runnable {
    private Bank b = new Bank();
    public void run() {
        for(int x = 0; x < 3; x++) {
            b.add(100);
        }
    }
    
}
public class BankDemo {

    public static void main(String[] args) {
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }

}

   

   同步函数用的是哪一个锁呢?

   函数需要被对象调用,那么函数都有一个所属对象引用,就是this。所以同步函数使用的锁是this。

   需求:通过该程序进行验证同步函数使用的锁是this。使用两个线程来买票,一个线程在同步代码块中,一个线程在同步函数中,都在执行买票动作。

   代码如下:

class Ticket implements Runnable {
    private int tick = 100;
    Object obj = new Object();
    boolean flag = true;
    public void run() {
        if(flag) 
            while(true) {
                synchronized (this) {
                    if(tick > 0) {
                        //模拟多线程的运行出现的安全问题
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            
                        }
                        System.out.println(Thread.currentThread().getName()+"...code: "+tick--);
                    }
                }
            }
        else
            while(true) 
                show();
    }
    
    public synchronized void show() {//this
        if(tick > 0) {
            //模拟多线程的运行出现的安全问题
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                
            }
            System.out.println(Thread.currentThread().getName()+"...show...: "+tick--);
        }
    }
}

public class ThisLockDemo {

    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.flag = false;
        t2.start();
        
        
//      Thread t3 = new Thread(t);//创建一个线程
//      Thread t4 = new Thread(t);//创建一个线程
//      t3.start();
//      t4.start();
    }

}

 

   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

   

 

多线程

原文:http://www.cnblogs.com/yerenyuan/p/5224874.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!