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

黑马程序员——Java基础---多线程

2015-06-07 21:40 309 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

创建线程方式一

继承Thread类

1.子类覆盖父类中的run方法,将线程运行的代码存放在run中。

2.建立子类对象的同时线程也被创建。

3.通过调用start方法开启线程。

代码示例:

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

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

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

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

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

如何在自定义的代码中,自定义一个线程呢?
通过查找api,Java已经提供了对线程这类事物的描述,就是Thread类。

创建线程的第一种方式:继承Thread类。
步骤:
1,定义类继承Thread。
2,复写Thread类中的run方法。
目的:将自定义代码存储在run方法中,让线程运行。
3,调用线程的start方法,
该方法有两个作用:启动线程,调用run方法。

发现运行结果每一次都不同。
因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。
明确一点,在某一个时刻,只能有一个程序在运行(多核除外)
cpu在做着快速的切换,以达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。

这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说了算。

为什么要覆盖run方法呢?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
也就是说Thread类中的run方法,用于存储线程要运行的代码。
*/
class Demo extends Thread
{
public void run()
{
for(int x=0;x<50;x++)
System.out.println("demo run---"+x);
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();//创建一个线程
d.start();//开启线程并执行该线程的run方法。
//d.run();//仅仅是对象调用方法,而线程创建了并没有运行。
for(int x=0;x<50;x++)
System.out.println("Hello World!"+x);
}
}

线程的四种状态

(1) 产生(New):线程对象已经产生,但尚未被启动,所以无法执行。如通过new产生了一个线程对象后没对它调用start()函数之前。

(2) 可执行(Runnable):每个支持多线程的系统都有一个排程器,排程器会从线程池中选择一个线程并启动它。当一个线程处于可执行状态时,表示它可能正处于线程池中等待排排程器启动它;也可能它已正在执行。如执行了一个线程对象的start()方法后,线程就处于可执行状态,但显而易见的是此时线程不一定正在执行中。

(3) 死亡(Dead):当一个线程正常结束,它便处于死亡状态。如一个线程的run()函数执行完毕后线程就进入死亡状态。

(4) 停滞(Blocked):当一个线程处于停滞状态时,系统排程器就会忽略它,不对它进行排程。当处于停滞状态的线程重新回到可执行状态时,它有可能重新执行。如通过对一个线程调用wait()函数后,线程就进入停滞状态,只有当两次对该线程调用notify或notifyAll后它才能两次回到可执行状态。

创建线程方式二

实现Runnable接口

1.子类覆盖接口中的run方法。

2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。

3.Thread类对象调用start方法开启线程。

代码示例:

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

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

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

实现方式和继承方式有什么区别呢?
实现方式好处:避免了单继承的局限性。
在定义线程时,建议使用实现方式。

两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable,线程代码存在接口的子类run方法中。
*/
class Ticket implements Runnable//extends Thread
{
private static int tick =100;
public void run()
{
while(true)
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);
}
}
}
}
class TicketDemo
{
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.star
4000
t();
// Ticket t1 = new Ticket();
// Ticket t2 = new Ticket();
// Ticket t3 = new Ticket();
// Ticket t4 = new Ticket();
// t1.start();
// t2.start();
// t3.start();
// t4.start();
}
}

线程安全问题

导致安全问题的出现的原因:

多个线程访问出现延迟。

线程随机性 。

注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

代码示例:

/*
通过分析,发现打印出0,-1,-2等错票。
多线程的运行出现了安全问题。
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行一部分,
还没有执行完,另一个线程参与执行,导致共享数据的错误。
解决方法:
对多条操作共享数据的语句,只能让一个线程都执行完,再执行过程中,其他线程不可以参与执行。

Java对于多线程的安全问题提供了专业的解决方式。
就是同步代码块
synchronized(对象)
{
需要被同步的代码
}
对象如同锁。只有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

火车上的卫生间--经典。
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
3,必须保证同步中只能有一个线程在运行

好处:解决了多个线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
*/
class Ticket implements Runnable//extends Thread
{
private static int tick =100;
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);
}
}
}
}
}
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();
}
}

同步(synchronized)

格式:

synchronized(对象)

{

需要同步的代码;

}

同步可以解决安全问题的根本原因就在那个对象上。

该对象如同锁的功能。

同步的特点

同步的前提:

1,同步需要两个或者两个以上的线程。

2,多个线程使用的是同一个锁。

未满足这两个条件,不能称其为同步。

同步的弊端:

当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形 中会降低程序的运行效率。

同步函数

格式:

在函数上加上synchronized修饰符即可。

代码示例:

/*
需求:银行有一个金库。有两个储户分别存300元,每次存100,存3次。
目的:该程序是否有安全问题,如果有,如何解决?

如何找问题:
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(Exception 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);
}
}
}
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();
}
}

死锁

死锁的概念

死锁是进程死锁的简称,是由Dijkstra于1965年研究银行家算法时首先提出来的。它是计算机操作系统乃至并发程序设计中最难处理的问题之一。实际上,死锁问题不仅在计算机系统中存在,在我们日常生活中它也广泛存在。

产生死锁的必要条件

1,互斥条件。即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。如独木桥就是一种独占资源,两方的人不能同时过桥。

2,不可抢占条件。进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。如过独木桥的人不能强迫对方后退,也不能非法地将对方推下桥,必须是桥上的人自己过桥后空出桥面(即主动释放占有资源),对方的人才能过桥。

3,占有且申请条件。进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。还以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况。

4,循环等待条件。存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。就像前面的过独木桥问题,甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。

代码示例1:
/*
死锁
同步中嵌套同步。
*/
class Ticket implements Runnable
{
private int tick =1000;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(obj)
{
show();
}
}
}
else
while(true)
show();
}
public synchronized void show()//this
{
synchronized(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code:"+tick--);
}
}
}
}
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(Exception e){}
t.flag = false;
t2.start();
}
}
死锁代码示例2:

class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
synchronized(MyLock.locka)
{
System.out.println("if locka");
synchronized(MyLock.lockb)
{
System.out.println("if lockb");
}
}
}
else
{
synchronized(MyLock.lockb)
{
System.out.println("else lockb");
synchronized(MyLock.locka)
{
System.out.println("else locka");
}
}
}
}
}
class MyLock
{
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}总结
线程学习内容挺多的,而且有些知识点也挺难的,需要反复的学习才能懂。线程又是很重要的,所以要经常回顾巩固。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: