黑马程序员——Java多线程与并发编程

时间:2023-02-20 13:01:32

--------------- Android培训Java培训、期待与你交流! ----------------



         多任务系统中,每个独立执行的程序称为进程,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。线程就是进程中的一个执行路径,单线程中,程序按照代码调用的顺序依次向下执行,这种情况下,当主调函数调用了被调函数,主调函数必须要等待被调函数返回后才能继续执行,两段代码不能交替执行,可能会损失一部分性能。如果一个程序中要实现多段代码同时交替运行,就需要多线程,Java语言内置了对多线程的支持,使我们可以很容易开发多线程程序。多线程能有效降低程序的开发和维护成本,同时能有效利用现代多处理器的强大处理能力,但是Java对线程的支持是一把双刃剑,使用不当可能会出现安全问题。

   一、线程的创建

    Java中创建线程有两种方式,其一是继承Thread类,二是实行Runnable接口

    1 用Thread类创建线程

   Java中的线程是通过lang包中的Thread类来控制的,Thread类的一个对象就是一个线程。通过继承Thread类创建线程的步骤:先定义一个类继承Thread;然后覆写Thread类中的run()方法,其目的是将自定义代码存储在run方法中,这样当该线程运行时,自定义的代码就能运行了;最后通过调用Thread对象的start()方法启动线程。

   Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,也就是说Thread类中的run方法,用于存储线程要运行的代码。下面的代码演示了主线程和自定义线程的交替执行效果:

class Demo extends Thread//定义一个线程类
{
public void run()
{
for(int x=0; x<60; x++)
System.out.println("demo run----"+x);
}
}

class ThreadDemo
{
public static void main(String[] args)
{

Demo d = new Demo();//创建好一个线程。
d.start();//开启线程并执行该线程的run方法。
for(int x=0; x<60; x++)
System.out.println("Hello World!--"+x);

}
}

   2 使用Runnable接口创建多线程

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

  下面的代码和上一段代码的运行效果一致

class Demo1 implements Runnable
{
public void run()
{
for(int x=0; x<60; x++)
System.out.println("demo1 run----"+x);
}
}


class ThreadDemo
{
public static void main(String[] args)
{
Demo1 d = new Demo1();
Thread t1 = new Thread(d);
t1.start();

for(int x=0; x<60; x++)
System.out.println("Hello World!--"+x);

}
}

  3 两种实现方式的对比

    一个Thread类对象只能启动一个线程,无论你调用了多少遍start()方法,都只有一个线程。一个线程对象只能管理一份资源,如果要让多个线程管理同一份资源,我们只能去实现Runnable接口来创建对象。

   实现Runnable接口有以下显著好处:

  1)适合多个相同程序代码去处理同一资源的情况,把虚拟CPU同程序代码、数据有效分离,较好地体现了面向对象的设计思想

  2)避免java单继承带来的局限性。

  3)有利于程序的健壮性,代码能被多个线程共享,代码与数据时独立的。

二、线程安全与线程同步

    一个对象是否是线程安全的,取决于它是否被多个线程同时访问。要使得对象时是线程安全的,需要采用同步机制来协调对对象的可变状态的访问,如果不能实现协调,则会出现数据的破坏和其他不该出现的结果。Java中的主要同步机制是synchronized关键字,它提供了一种独占的加锁机制来实现线程安全。

   1 同步代码块

  同步代码块是用synchronizde括起来的一块代码,放在该块中的代码能确保被独占访问。在同一时刻只有一个线程可以进入同步代码块内运行,只有当该线程离开同步代码块后,其他线程才能进入同步代码块。synchronized语句的格式为:

synchronized(任意一个对象){代码}

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

  同步的前提:
  1)必须要有两个或者两个以上的线程。
  2)必须是多个线程使用同一个锁。

  必须保证同步中只能有一个线程在运行。好处:解决了多线程的安全问题;弊端:多个线程需要判断锁,较为消耗资源。

class Ticket implements Runnable
{
private int tick = 1000;
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();


}
}

 2 同步函数

   除了可以对代码块进行同步外也可以对函数实现同步,只要在同步的函数定义前加上synchronized关键字即可。

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

  3 死锁问题

  死锁是一种少见的、难以调试的问题,在两个线程对两个同步对象具有循环依赖时,就可能会出现死锁。例如,一个线程进入对象A的监视器,而另一个线程进入对象B的监视器,这时进入A对象监视器的线程如果还试图进入B对象的监视器就会被阻止,接着进入B对象监视器的线程如果试图进入X对象的监视器也好被阻止,这样两个线程都处于被挂起状态。

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


}
}
  解决死锁的一种简单方法是,让两个同步代码块持有相同的锁,这就破坏了死锁产生的条件了。