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

黑马程序员-Java编程知识总结多线程

2015-01-15 19:33 274 查看
------- android培训java培训、期待与您交流! ----------

-Java编程知识点总结。

一.多线程。

1. 什么是进程:进程是正在运行的程序。2.运行中的电脑看上去可以同时执行多个程序其实CPU同一时刻只能运行一个程序,之所以看上去是在同时运行,是因为CPU在不停的切换执行。每一个进程执行都一个执行顺序,该顺序是一个执行路径或者叫一个执行单元。

2. 什么是线程:线程就是进程中的一个独立的单元,线程在控制进程的执行。

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

二.自定义线程。

Java已经提供了对线程这类食物的描述。Thread类。

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

1,定义类继承Thread。

2,复写Thread类中的run方法。目的:将自定义代码存储在run 方法。让线程运行。

3,调用线程的start方法,该方法两个作用:启动线程,调用run方法。

class Demoextends Thread

{

public void run ()

{

for(int i = 0;i<=60;i++)

{

System.out.println(i);

}

}

}

classThreadDmo

{

public static void main(String[] args)

{

Demo d = new Demo();//创建继承自Thread类的子类对象。

d.start();//start作用是开启线程,并调用run()方法。

for(int x=0; x<60; x++)

System.out.println("HelloWorld!--"+x);

}}


练习一:

创建两个线程,和主线程交替运行。线程都有自己默认的名称,Thread-编号的形式,编号是从0开始。Thread-0;

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

设置线程的名称:setName();

class Testextends Thread

{

//private String name;

Test(String name)

{

//this.name = name;

super(name);//调用父构造函数,将对象名称传给父。

}

public void run()

{

for(int x=0; x<60; x++)

//获取当前线程的对象获取当前线程对象的名称。

//该方法写于run方法中,即写于当前线程中使用。

//那个线程运行当前的run,就获得谁的对象t1,或t2。

{System.out.println((Thread.currentThread()==this)+"..."+this.getName()+"run..."+x);

}

}

}


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....."+x);

}

}

}

2.多线程状态图:

3.(必须掌握)练习二:售票。

class Tickets extends Thread

{privateint ticket = 100;

Tickets(String name)

{

super(name);//必须会使用,1.使用构造函数2.调用父构造3.传对象的名称。

}

publicvoid run()

{

while (ticket>0)

{

ticket-=1;

System.out.println(Thread.currentThread()+"sales"+ticket+this.getName());

}

}

}

class TicketDemo

{

publicstatic void main(String[] args)

{

Ticketst1 = new Tickets("线程1");

Ticketst2 = new Tickets("线程2");

Ticketst3 = new Tickets("线程3");

Ticketst4 = new Tickets("线程4");

t1.start();

t2.start();

t3.start();

t4.start();

}

}

2.创建线程的第二种方式。

1.实现Runnable接口。

2.重写run()方法。

3.(*)通过Thread类创建线程对象。

4.使用Thread类创建线程对象。将实现自Runnable接口的子类对象作为参数传递给Thread类的构造方法。因为自定义对象的run方法是Runnable接口的抽象方法run方法是子类的方法。而真正能使run方法运行的是Thread类所以要让线程运行run就得把Runnable的子类对象传递给Thread。

5.调用Thread类的start方法开启线程并调用Runnable接口。

6.实现方式和继承方式的区别:使用Runnable接口可以实现多继承,即当继承了Thread类,子类就不能再继承其他类。当实现自Runnable接口,子类即创建了线程,又可以再继承其它的类。

5多线程安全问题

多线程安全问题出现的原因:

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

如下所示:

class Tickets extends Thread

{privateint ticket = 100;//该数据是4个线程共享的数据

Tickets(String name)

{

super(name);

}

//run方法是4个线程共同执行的方法。存在于每个线程中,不是共享的。X定义在函数中属于局部是非共享,ticket是定义在类中的是共享的

publicvoid run() //throws RuntimeException子类只能抛父类的子集,父类没有抛。所以不能抛出。

{intx;

while (ticket>0)

{ try

{

Thread.sleep(50);//throwsInterruptedException该方法在使用时自动抛出异常,所以要处理。

//有可能出现以下情况1.2.3.4线程。当ticket
= 1时,1线程获取执行权,打印1;然后自减为0。

//这时2线程获得了执行权,又打印了一次0,自减为-1.这时3线程执行,又打印了-1.这些线程就没有进行条件判断,在这里输出了错值。

System.out.println(this.getName()+"sales"+ticket--);

}

catch (Exception e)

{

}

}

}

}

class TicketDemo

{

publicstatic void main(String[] args)

{

Ticketst1 = new Tickets("线程1");

Ticketst2 = new Tickets("线程2");

Ticketst3 = new Tickets("线程3");

Ticketst4 = new Tickets("线程4");

t1.start();

t2.start();

t3.start();

t4.start();

}

1.(*)解决办法对多条操作共享数据的语句只让一个线程执行完,其他线程都不可以参与。使用同步代码块。

synchronized(对象)

{

需要被同步的代码

}对象如同锁。持有锁的线程可以在同步中执行。

没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

把上个例子的代码修改为安全的代码:

Object obj = new Object();

public void run()

{

//锁就是对象。当线程t1.t2执行到这时,

// 1.假如t1获得了锁,锁标0->1;

4.t2获得了执行权,t2进入,执行输出语句。

Synchronized(obj)
锁标(0)禁止(1)进入

{//2.t1线程进入后锁标1->0;t2进不来。

while (ticket>0)

{

try

{

Thread.sleep(50);//线程会在这儿冻结,

当时间到后,转入到阻塞状态等cpu执行权。

System.out.println(this.getName()+"sales"+ticket--);

}

catch (Exception e)

{

}

}

//3.当t1执行完后退出0->1;t1跳出锁。

}

}

在锁标处的执行情况:

1.假如t1持有了锁,锁标0->1;

2.t1线程进入后锁标1->0;t2进不来。

3.当t1执行完后退出0->1;t1跳出锁。

4.此时可进入,t2获得了执行权,t2进入,执行输出语句。

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

同步的前提:

1,必须要有两个或者两个以上的线程。

2,必须是多个线程使用同一个锁。(同一个对象)

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

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

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

1. (*)同步函数:使函数具有同步性。在函数的返回类型前加上synchronized.所以到这里同步方法有两种:

1.同步函数:使用synchronized作为修饰放在返回类型的前边。 2.同步代码块,使用synchroized(对象){}.的方式实现同步。

如何找问题:

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

2,明确共享数据。

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

class Bank

{

privateint sum;//sum是共享数据所以要把操作共享数据的语句封装为同步语句。

//Objectobj = new Object();

publicsynchronized void add(int n)

{

//synchronized(obj)

//{

sum= sum + n;

try{Thread.sleep(10);}catch(Exceptione){}

System.out.println("sum="+sum);

//}

}

}

class Cus implements Runnable

{

privateBank b = new Bank();

publicvoid run()

{

for(intx=0; x<3; x++)

{

b.add(100);//调用了包含共享数据的方法。

}

}

}

class BankDemo

{

publicstatic void main(String[] args)

{

Cusc = new Cus();

Threadt1 = new Thread(c);

Threadt2 = new Thread(c);

t1.start();

t2.start();

}

}

练习:使用同步函数的方式重写售票的例子。

2. 同步函数的锁。

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

下面的实例来验证上面的结论:

通过该程序进行验证。使用两个线程来买票。一个线程在同步代码块中。一个线程在同步函数中。

都在执行买票动作。

class Ticket implements Runnable

{

private int tick = 100;

Objectobj = new Object();

booleanflag = true;

public void run()

{

if(flag)

{

while(true)

{//当锁使用obj时,线程会出现打印错误的票数,表明线程出现了不同步的情况,当时用this时,打印正确。表明this时同步函数的锁。

synchronized(this)

{

if(tick>0)

{

try{Thread.sleep(10);}catch(Exceptione){}

System.out.println(Thread.currentThread().getName()+"....code: "+ tick--);

}

}

}

}

else

while(true)

show();

}

publicsynchronized void show()//this

{

if(tick>0)

{

try{Thread.sleep(10);}catch(Exceptione){}

System.out.println(Thread.currentThread().getName()+"....show....: "+ tick--);

}

}

}

class ThisLockDemo

{

publicstatic void main(String[] args)

{

Tickett = new Ticket();

//两个线程操作的是同一个对象。共享数据是ticket

Threadt1 = new Thread(t);

Threadt2 = new Thread(t);

t1.start();

try{Thread.sleep(10);}catch(Exceptione){}

t.flag= false;

t2.start();

}

}

3. 静态同步函数的锁

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

class Ticket implements Runnable

{

privatestatic int tick = 100;

//Objectobj = new Object();

booleanflag = true;

public void run()

{

if(flag)

{

while(true)

{

synchronized(Ticket.class)

{

if(tick>0)

{

try{Thread.sleep(10);}catch(Exceptione){}

System.out.println(Thread.currentThread().getName()+"....code: "+ tick--);

}

}

}

}

else

while(true)

show();

}

publicstatic synchronized void show()

{

if(tick>0)

{

try{Thread.sleep(10);}catch(Exceptione){}

System.out.println(Thread.currentThread().getName()+"....show....: "+ tick--);

}

}

}

class StaticMethodDemo

{

publicstatic void main(String[] args)

{

Tickett = new Ticket();

Threadt1 = new Thread(t);

Threadt2 = new Thread(t);

t1.start();

try{Thread.sleep(10);}catch(Exceptione){}

t.flag= false;

t2.start();

}

}

静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class

4. 单例设计模式。

class Single

{

privatestatic Single s = null;

privateSingle(){}

publicstatic Single getInstance()

{

if(s==null)

{

synchronized(Single.class)

{

if(s==null)

s= new Single();

}

}

returns;

}

}

class SingleDemo

{

publicstatic void main(String[] args)

{

System.out.println("HelloWorld!");

}

}

2. 多线程死锁。

class Test implements Runnable

{

privateboolean flag;

Test(booleanflag)

{

this.flag= flag;

}

publicvoid run()

{

if(flag)

{

while(true)

{//假如t1有a锁并进入

synchronized(MyLock.locka)

{

System.out.println(Thread.currentThread().getName()+"...iflocka ");//t1有a但b锁被t2拥有,只有t2执行完下面的语句才会释放b锁,所以t1停止这里。

synchronized(MyLock.lockb)

{

System.out.println(Thread.currentThread().getName()+"..iflockb");

}

}

}

}

else

{

while(true)

{//假如t1阻塞,t2执行,t2有b锁进行

synchronized(MyLock.lockb)

{

System.out.println(Thread.currentThread().getName()+"..elselockb"); 同理t2也被挂在这里。因为t2没有a锁。

synchronized(MyLock.locka)

{

System.out.println(Thread.currentThread().getName()+".....elselocka");

}

}

}

}

}

}

class MyLock

{

staticObject locka = new Object();

staticObject lockb = new Object();

}

class DeadLockTest

{

publicstatic void main(String[] args)

{

Threadt1 = new Thread(new Test(true));

Threadt2 = new Thread(new Test(false));

t1.start();

t2.start();

}

}

3. (*)线程间通信。

当前的线程对象持有的锁对象调用:

wait():当前持有锁的线程对象处于了冻结状

notify():唤醒线程池中首个被冻结的线程。

notifyAll();唤醒线程池中所有的被冻结的线程。

它们都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。为什么这些操作线程的方法要定义Object类中呢?因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。

不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。

而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

(因为要被当做锁的对象不确定,即所有对象都要有notify和wait的功能,既然所有类型的对象都要具备这两个功能,那只能定义在Object(上帝)中了,因为所有类都继承自Object)。

1. 示例。



class Res

{

Stringname;

Stringsex;

booleanflag = false;

}

class Input implements Runnable

{

privateRes r ;

Input(Resr)

{

this.r= r;

}

publicvoid run()//input中的run方法

{

intx = 0;

while(true)

{

synchronized(r)

{

if(r.flag)

try{r.wait();}catch(Exceptione){}

if(x==0)

{

r.name="mike";

r.sex="man";

}

else

{

r.name="丽丽";

r.sex= "女女女女女";

}

x = (x+1)%2;

r.flag= true;

r.notify();

}

}

}

}

class Output implements Runnable

{

privateRes r ;

Output(Resr)

{

this.r= r;

}

publicvoid run()

{

while(true)

{

synchronized(r)

{

if(!r.flag)

try{r.wait();}catch(Exceptione){}

System.out.println(r.name+"...."+r.sex);

r.flag= false;

r.notify();

}

}

}

}

class InputOutputDemo

{

publicstatic void main(String[] args)

{

Resr = new Res();

Inputin = new Input(r);

Outputout = new Output(r);

Threadt1 = new Thread(in);

Threadt2 = new Thread(out);

t1.start();

t2.start();

}

}

以上代码的执行过程:

1.创建一个公共数据类Res,创建两个实现Runnable接口的线程对象。

2.创建两个线程对象,将对象传给创建线程的Thread对象用于执行run方法。

3.启动线程t1.t2。t1和t2的run方法不同,但两者操作的都是同一个对象的属性,即共享属性,但是操作的动作不同。这就被称为线程之间的通信。

4.t1线程先被启动,执行in中的run方法。一,第一次执行。二,第二次执行。

publicvoid run()//input中的run方法

{

intx = 0;

while(true)

{//同步前提:1.多线程。2.锁相同。

synchronized(r)//两者使用的是同一个锁

{

if(r.flag)//一1.flag
= false;不执行一6.flag =true

//一.7执行冻结当前线程wait()会自动抛异常,所以需要处理。

try{r.wait();}catch(Exceptione){}

if(x==0)一2.x=0执行改变name,sex。

{

r.name="mike";

r.sex="man";

}

else

{

r.name="丽丽";

r.sex= "女女女女女";

}

x= (x+1)%2;一3.x=-1;

r.flag= true;一4.flag
= true;

r.notify();一5.唤醒output线程即t2线程开启。

}

}

t2线程被启动,执行out中的run方法。一,第一次执行。二,第二次执行。

class Output implements Runnable

{

privateRes r ;

Output(Resr)

{

this.r= r;

}

publicvoid run()

{

while(true)

{

synchronized(r)

{

//二1.flag = true不执行。

if(!r.flag)//
二5.t2进入冻结状态。 try{r.wait();}catch(Exception e){}

//二2.输出被in修改的name和sex

System.out.println(r.name+"...."+r.sex);

r.flag= false;//二3.flat =false;

r.notify();//二4.唤醒in线程,即t1线程。

}

}

}

}

当t1被唤醒时,t1又开始一的上述步骤。当一5时t2又开始。

2. 解决安全问题。

3. 等待唤醒机制

4. 代码优化。

5. (必须掌握)练习:生产者消费者。

class ProducerConsumerDemo

{

publicstatic void main(String[] args)

{

Resourcer = new Resource();

Producerpro = new Producer(r);

Consumercon = new Consumer(r);

Threadt1 = new Thread(pro);

Threadt2 = new Thread(pro);

Threadt3 = new Thread(con);

Threadt4 = new Thread(con);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

/*

对于多个生产者和消费者。

为什么要定义while判断标记。

原因:让被唤醒的线程再一次判断标记。

为什么定义notifyAll,

因为需要唤醒对方线程。

因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

*/

class Resource

{

privateString name;

privateint count = 1;

privateboolean flag = false;

// t1 t2

publicsynchronized void set(String name)

{

while(flag)

try{this.wait();}catch(Exceptione){}//t1(放弃资格) t2(获取资格)

this.name= name+"--"+count++;

System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);

flag= true;

this.notifyAll();

}

// t3 t4

publicsynchronized void out()

{

while(!flag)

try{wait();}catch(Exceptione){}//t3(放弃资格) t4(放弃资格)

System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);

flag= false;

this.notifyAll();

}

}

class Producer implements Runnable

{

privateResource res;

Producer(Resourceres)

{

this.res= res;

}

publicvoid run()

{

while(true)

{

res.set("+商品+");

}

}

}

class Consumer implements Runnable

{

privateResource res;

Consumer(Resourceres)

{

this.res= res;

}

publicvoid run()

{

while(true)

{

res.out();

}

}

}


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