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

黑马程序员--多线程

2015-11-02 11:05 260 查看
------<a href="http://www.itheima.com"
target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

一、多线程概述

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

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

注意:一个进程中至少有一个线程,且多线程有随机的特性。

如javaVM启动的时候,就会有一个进程java.exe。该进程中有两个线程参与运行,一个是代码存在于 main方法中的主线程,还有一个是jvm启动时的一个负责垃圾回收机制的线程。

定义线程的方法:

1,继承Thread类并复写run()方法,然后调用线程的start方法。

start方法:启动线程,并调用run方法。

注意:千万别再主函数中调用run方法,那样会导致自始至终都是主线程在执行。

2,定义类实现Runnable接口,覆盖Runnable接口中的run方法。通过Thread类建立线程对象,将Runnable接口的子类对象传递给Thread类的构造函数。调用Thread类的start方法开启线程并调用Runnable接口的子类run方法。

线程有自己的名称,为Thread-编号。

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

获取线程名称:getName()

设置线程名称:setName或者super(name);

实现方式避免了单继承的局限性,所以定义线程时,应用实现方式完成。

package 博客5_多线程;
class Text extends Thread//创建一个类继承Thread
{
private String name;
Text(String name)
{
this.name=name;
}
public void run()//复写Thread类的run方法
{
for(int x = 0 ;x < 60;x++)
{
System.out.println(name+"...run..."+x);
}
}
}
public class 创建线程_继承方式 {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Text t1 = new Text("one");//创建Text对象
Text t2 = new Text("two");
t1.start();//开启线程。
t2.start();
}

}
package 博客5_多线程;

class Text_2 implements Runnable//定义类实现Runnable接口
{
private String name;
Text_2(String name)
{
this.name = name;
}
public void run()
{
for(int x = 0;x<60;x++)
{
System.out.println(Thread.currentThread().getName()+".....run...."+x);
}
}

}

public class 创建线程_实现方式 {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Text_2 t = new Text_2("一号");//创建子类对象
Thread t1 = new Thread(t);//创建线程
Thread t2 = new Thread(t);
t1.start();//启动线程
t2.start();
}

}
二、线程的安全问题

多线程运行时出现安全问题的原因:当多条语句在操作同意线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完另一个线程参与进来执行,导致共享数据错误。
解决方法:对多条操作共享数据的语句,只能让一个线程执行完,且在执行过程中,其他线程不可以参与执行即可。

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

同步代码块格式:synchronized(对象)

{

需要被同步的代码;

}

package 博客5_多线程;

class Ticket implements Runnable
{
private int tick = 100 ;
Object obj = new Object();
public void run()//复写Runnable中的run方法
{
while(true)
{
synchronized(obj)//加一把Object对象的锁
{
if(tick>0)
System.out.println(Thread.currentThread().getName()+"...sale...."+tick--);
}
}
}
}

public class 同步代码块_卖票小程序synchronized {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
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();
}

}
同步的前提:
1,必须要有两个或者两个以上的线程。

2,必须是多个线程使用同一把锁。

同步的目的:保证同步中只有一个线程在运行。

同步的优点:解决了多线程的安全问题。

同步的缺点:效率较低。

获取多线程安全问题的步骤:

1,明确哪些代码是多线程运行的代码。

2,明确共享数据。

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

同步函数:可以将synchronized作为修饰符放在函数上即可。

同步函数的锁是this,因为同步函数都有一个所属对象的引用。

如果同步函数被静态修饰的时候,用的锁是Class对象。

因为静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象,该对象就是类名.Class。该对象的类型是Class。

死锁:同步中嵌套同步,但是用的不是同一把锁。如同步代码块中嵌套同步函数,同步函数中嵌套同步代码块。

package 博客5_多线程;

class MyLock//自己定义一个锁
{
static Object locka = new Object();
static Object lockb = new Object();
}

class DeadLockText implements Runnable
{
private boolean flag;//设置一个标记,让两个线程可以交替运行
DeadLockText(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");
}
}
}
}
}
public class 死锁Text {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1 = new Thread(new DeadLockText(true));
Thread t2 = new Thread(new DeadLockText(false));
t1.start();
t2.start();
}

}三、线程间通信

线程间通信:多个线程在操作同一个资源,但是操作的动作不同。
线程间通信保证安全问题的方法:

1,加锁,保证线程同步。

2,通过等待唤醒机制。

package 博客5_多线程;
/*
* 用加锁的方式完成线程间通信的安全问题
* */
class Res//创建一个资源对象
{
String name;
String sex;
}
class Input implements Runnable//输入名字和性别的对象
{
private Res r;//保证对象唯一,也可以用单例,但是没有这样简单
Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0 ;
while(true)
{
synchronized(r)//需要加锁,保证线程同步。r是唯一对象,可以保证使用的是同一把锁。
{
if(x==0)
{
r.name="mike";
r.sex="man";
}
else
{
r.name= "丽丽";
r.sex="女";
}
x=(x+1)%2;
}
}
}
}
class Output implements Runnable
{
private Res r;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
synchronized(r)
{
System.out.println(r.name+"......"+r.sex);
}
}
}
}

public class 线程间通信 {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Res r = new Res();
Input in = new Input(r);
Output op = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(op);
t1.start();
t2.start();
}

}
线程池:用于存放等待的线程。唤醒的时候要唤醒先等待的线程。

wait,notify,notifyAll都使用在同步中,因为要对持有监视器(锁)的线程操作,所以只能定义在同步中。
注意:等待和唤醒的必须是使用同一把锁。

上述代码优化后的结果为:package 博客5_多线程;
class Res_1//创建一个资源对象
{
private String name;
private String sex;
private boolean flag=false;//定义标记,初始化为假
public synchronized void set(String name,String sex)//同步函数,锁是this
{
if(flag)//如果为真,则等待,但是由于flag初始化为假,所以可以执行下面的语句
try{this.wait();}catch(Exception e){}
this.name=name;
this.sex = sex;
flag = true;//将标记改为真
this.notify();//唤醒输出
}
public synchronized void out()
{
if(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(name+"....."+sex);
flag = false;
this.notify();
}
}
class Input_1 implements Runnable//输入名字和性别的对象
{
private Res_1 r;//保证对象唯一,也可以用单例,但是没有这样简单
Input_1(Res_1 r)
{
this.r = r;
}
public void run()
{
int x = 0 ;
while(true)
{
if(x==0)
r.set("mike","man");
else
r.set("丽丽","女");
x=(x+1)%2;
}
}
}
class Output_1 implements Runnable
{
private Res_1 r;
Output_1(Res_1 r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}

public class 线程间通信_等待唤醒机制
{
public static void main(String[] args)
{
Res_1
9ecb
r = new Res_1();
new Thread(new Input_1(r)).start();
new Thread(new Output_1(r)).start();
}
}
当多个线程同时进行通信时,上述代码的if应更换成while,同时notify应更改成notifAll。
换成while是为了让被唤醒的线程再一次判断标记,但是如果不改notify,则会造成所有线程都等待的情况。所以应将notify更改成notifyAll。

四、多线程的其他操作

1,停止线程。

原理:想停止线程,就要让run方法结束。因为开启多线程的时候,运行代码通常都是循环结构,所以只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:当线程处于冻结状态时,就无法读取到循环结束标记,那么线程就不会结束。

当没有指定的方式让冻结的线程恢复到运行状态时,就需要对冻结进行清除。强制的让线程恢复到运行状态,这样就可以通过操作标记让线程结束。

用Thread类中提供的方法interrupt。

2,守护线程

Thread类中的setDeamon方法。需要在线程开启前调用。

当线程被标记为守护线程后,当正在运行的线程都是守护线程时,java虚拟机会自动退出,结束线程。

3,Join方法

当主线程执行到线程的join方法时,就会释放自己的执行权给调用join方法的线程,等他先执行完之后才会继续执行。

4,yield方法

线程的优先级是从1到10个等级。默认的优先级都是5。

通过setPriority可以设置优先级。

yield方法,暂停当前正在执行的线程对象,并执行其他线程。

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