黑马程序员_java_多线程1

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

进程和线程:

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

多进程是指操作系统能同时运行多个任务(程序)。

多线程是指在同一程序中有多个顺序流在执行。

在java中创建一个线程有两种方法:

第一种方法继承thread覆盖run方法;

步奏:

1.     定义类覆盖Thread类;

2.     覆盖Thread类中的run方法;

3.     创建Thread类的子类对象创建线程对象;

4.     调用线程对象的start( )方法,开启线程。

class ticket extends Thread{  
    //100张票  
    private   int tickets = 10;  
    private  String name ;  
    //覆盖run方法
    public  void run(){  
        while(true){  
            if(tickets > 0)  
            System.out.println(Thread.currentThread().getName() + tickets--);  
        }  
    }  
}  

class SaleTicket {  
	  
    public static void main(String[] args) {  
          
        //创建线程Thread子类对象  
        ticket t1 = new ticket();  
        ticket t2 = new ticket();  
        ticket t3 = new ticket();  
        ticket t4 = new ticket();  
        //开启线程  
        t1.start();  
        t2.start();  
        t3.start();  
        t4.start();  
    }  
}  


结果是这10张票 被卖出去 了 4次,与要求不太符合呀,我们可以将这10张票 标记为 static 完成数据共享,这样就能保证10张票是被4 个窗口卖完,并且每

张票只被卖出去了一次。

然而,Java还未我们提供了第二种创建线程的方式,他可以有效的解决这个问题:

第二种方法实现Runnable覆盖Runnable方法

1.    定义一个类实现Runnable接口;

2.    覆盖Runnable接口中的run方法。

       将线程要运行的代码存储到run()方法中。

3.   创建该接口的子类对象。

4.   通过Thread 类进行线程的创建,并将Runnable接口的子类作为Thread类的构造函数                        的实参进行传递。

5.   调用Thread类中的start()开启线程。

public class Ticket1 implements Runnable{  
    //定义10张票 
    private int tickets = 10;  
    //线程任务  
    public void run(){  
        while(true){              
                if(tickets>0){  
                  System.out.println(Thread.currentThread().getName() + "    " + tickets--);  
                }             
            }  
        }  
public class SaleTicket2 {  
  
    public static void main(String[] args) {  
        //任务对象  
        Ticket1 t1 = new Ticket1();  
        //创建4个线程,并吧对象传入
        Thread td1 = new Thread(t1);  
        Thread td2 = new Thread(t1);  
        Thread td3 = new Thread(t1);  
        Thread td4 = new Thread(t1);  
          
        //开启线程  
        td1.start();  //线程1开启
        td2.start();  //线程2开启
        td3.start();  //线程3开启
        td4.start();  //线程4开启
    }  
}  

那么这两种创建方式,买的票数不一致呢

第一种方法中

Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
t1.start();
t2.start();
线程对象被创建了两次,并且每次创建出来,就创建了100张票,当我们 开启线程后,实质上每个线程卖自己的10张票。

第二种方法中:

<span style="font-size:12px;font-weight: normal;">Ticket1 t = new Ticket1();
Thread t1 = new Thread(t);
Thread  t2 = new Thread(t);     
t1.start();
t2.start();</span>

线程对象同样被创建两次 , 但是,任务对象只被创建 1次,当开启线程后,实质上是 由两个线程来共同卖 这100张票。

第一种方式:继承自Thread类,这就会受到 Java 单继承这种局限性的影响,当我们如果再想扩展这个线程子类的功能时就会受到很大的限制。

第二种方式:实现 Runnable接口,则可以避免第一中创建方式的局限性。这样极大的降低了任务对象和 Thread对象的耦合性。

调用run方法和调用start方法的区别

1.  调用run ( )方法,仅仅是一般对象在调用对象中的方法,并没有开启线程,还是由主线程来完成run( )方法的运行。

2.  调用start( )方法,开启了一个线程(一个新的执行路径)这个线程去执行了run方法。

线程安全问题

class ticketrun 
{
	public static void main(String[] args) 
	{
		Ticket1 t = new Ticket1();//开启4个线程
		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();
	}
}
class Ticket1 implements Runnable{  
    private int tickets = 100;  
      
    public void run(){  
        while(true){  
              
                if(tickets>0){  
                    try {  
                        Thread.sleep(10);  //这里睡了10毫秒
                    } catch (InterruptedException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    }  
                System.out.println(Thread.currentThread().getName() + "    " + tickets--);  
                }             
        }  
    }  
}  
运行结果:会出现负数票



 原因如图解释:


分析原因:

当tickets 剩余值为 1 时,线程0 判断 tickets > 0 , 进入 if 中,但 进入之后 被sleep  了 10毫秒,

这时候,线程1 进来判断 tickets> 0 , 满足条件,进入 if 中,但在进入 sleep  10 毫秒,

线程2 , 3 ,也会走同样的流程。当 线程 0 sleep 时间到了之后,就会tickets-- ,这时 tickets 的值就是 0 

而线程 1 , 2 , 3 睡醒之后 不会判断条件,而是继续执行ticket--;于是就打印出了如图所示的情况

以上就是多线程出现安全问题的示例

总结出现线程安全问题的原因:多个线程在多条语句中操作共享数据!

线程安全问题的解决方式

由出现线程安全问题的原因分析解决办法

只要保证一个线程在执行多条操作共享数据的语句时,其他线程不能参与运算,当该线程执行完这些语句后,其他线程才可以执行这些语句。

具体的解决方案——同步代码块。

synchronized(对象){ // 对象可以是 任意的对象

//需要被同步的代码;

}

class ticketrun 
{
	public static void main(String[] args) 
	{
		Ticket1 t = new Ticket1();
		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();
	}
}
class Ticket1 implements Runnable{  
    private int tickets = 100;  
    Object obj = new Object();
    public void run(){  
        while(true){  
              synchronized(obj)//锁,这里放入的可以是任意对象
		if(tickets>0){  
                    try
                  Thread.sleep(10);  
                    } 
			catch (InterruptedException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    }  
                    System.out.println(Thread.currentThread().getName() + "    " + tickets--);  
			}
            }             
        }    
同步的原理其实就是:对需要同步的代码进行封装,并在该代码上加了一个锁。

好处:解决线程安全问题;弊端:当有线程在同步中执行该段代码的时候其他线程不可以使用该段代码中的变量和方法。

同步的前提:必须保证多个线程在使用同一个锁,必须保证同步中有多个线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值