现在的位置: 首页 > 综合 > 正文

多线程

2018年06月08日 ⁄ 综合 ⁄ 共 8511字 ⁄ 字号 评论关闭
文章目录

 

                                            多线程

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

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

一个进程中可以有多个线程, 但是一个线程只能有一个进程   同一进程中的线程共享进程中的资源

多线程:如果需要一个程序中实现多段代码交替运行,就需要产生多个线程,并指定每个线程上所要运行的代码段,这就是多线程

当程序执行时自动会产生一个线程 ,主函数就是在这个线程上面运行的,当不再产生新的线程时,程序就是单线程的。

创建多线程的两种 方法:继承Thread 类主实现 Runnable接口

start()方法的作用:1,启动线程 2,调用run()方法

多线程的特点:随机性,谁先抢到资源谁执行,执行时间由cpu决定

原因是多个线程都获取CPU的执行权,但是CPU执行到谁谁就运行。

注:在某一个时刻,只有一个程序在运行,其他的都是在等待执行,程序在计算机里是并发执行的,

我们同时打开两个程序他们的执行的所有的都是一样的可能我们就会认为他们是在同一时间执行的那么你错了,所有的程序都是不能在同一时间执行的(多核除外)只是他们在一个很短的时间内由计算机完成了执行操作,但不是同一时间点

利用Thread类创建线程:

java线程是通过java.lang.Thread类来控制的一个Thread类代表一个线程,通过 Thread类和它定义的对象 我们可以得到当前线程对象,获取某个线程的名称,可以实现 控制线程暂停一段时间 等 功能。

public class ThreadDemo
{
public static void main(String []args)
{
new TestThread().run();
while(true)
{
System.out.println("main thread is running");
}
}
}
class TestThread
{
public void run()
{
while(true)
{
System.out.println("Thread.currentThread().getName()is running");
}
}
}

我们使用Thread.currentThread()静态函数获得该代码当前执行时对应的那个线程对象。得到这个线程对象 后双调用 了线程对象 的getname()方法取出当前线程的名称字符串

运行后我们会发现第一个代码块没有运行而且是只运行了第二个代码并且是无限循环。也就导致了代码块一处永远没有机会执行

public class ThreadDemo
{
pubnlic static void main(String[] args)
{
new TestThread().start();
while(ture)
{
System.out,println("main thread is running");
}
}
}
class TestThread extends Thread//这里让TestThread类继承Thread类
{
public void run()//这里调用了上面的.start函数因为继承所以可以使用
{
while(ture)
{
System.out.println("Thread.cuurrentThread().getName()is running");
}
}
}

这样因为有了继承这里ThreadDemo类的全部特点也就让TestThread类有了然而程序没有直接 调用 TestThread类的对象

 run方法也是调用了该 类对象 从Thread类继承来的start方法也就让两个while循环达到了交替运行的效果。
在代码段run()中,也可以通过 线程的静态方法 Thread.currentThread()得到当前线程实例对象。得到当前线程对象后又调用了getName方法取出当前线程的名称字符串

1.要将一段代码在一个新的线程上运行,该代码中必须在一个类的run函数中,并且run函数所在的类是Thread类中的子类。也就是要实现 多线程,必须要有一个继承了Thread类的子类,子类要覆盖Thread类中的run函数,在子类的run函数中调用 想在新线程上运行的程序代码

2.启动一个新的线程不是直接调用 Thread子类对象的run方法,也是调用Thread子类对象 的start(继承的那个方法)方法Thread类对象start方法将产生一个新的线程,并在该线程上运行该Thread类对象 中的run方法,根据面向对象 的多态性,在该 线程上实际运行的是thread子类对象中的run方法

3.由于线程在run方法中,那么该方法执行完后线程也就结束了,因些可以通过控制run方法中的循环条件来控制线程的终止

如果Thread类的子类没有覆盖run方法,编译运行时有明显的错误或异常么?运行结果又是什么呢?

程序调用 Thread类中的run方法而该方法什么都 不做,所以新的线程刚创建那么就结束了这样的线程对我们来说没有任何意义的

而且 直接 在程序 中写new Thread().start();这样的语句编译和运行时有明显的错误和异常么?运行结果又是什么?

这个与上面是一样因为线程对象不是通过 Thread子类创建的而是通过 Thread类直接创建的,新的线程将直接 调用 Thread中的run()方法。

使用Runnable接口创建多线程:

这是一个构造方法Thread(Runnable target)构造方法.Runnable接口中只有一个方法run()方法。当使用Thread(Runnable target)方法创建线程时,需为该方法传递一个实现 了Runnable接口的类对象中的run()方法作为其运行代码而不再调用Thread类中的run方法了

public class ThreadDemo
{
public static void main(String args[])
//new TestThread().start();
TestThread tt=new TestThread();//创建TestThread类的一个实例
Thread t= new TestThread();//创建一个Thread类的实例
t.start();//使线程进入Runnable状态  
while(true)
{
System.out.println("main thread is running");
}
}
}
class TestThread implements Runnable//extends Thread
{
public void run()//线程的代码段,当执行start()里,线程开始执行
{
while(true)
{
System.out,println("Thread.currentThread().getName() is running");
}
}
}

两种实现 多线程 方式的对比分析:

实现Runnable接口相对 于继承Thread类来说的好处

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

2.可以避免由于java单继承特性带来的局限。。即当我们要将已经继承了某一个类的子类 放入多线程中,

由于一个类不能同时有两个父类所以不能用继承Thread类的方式。所以就只能用Runnable接口来实现了

3.有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的,多个线程可以操作相同的数据,与代码无关。当共享相同的对象时,即共享相同的数据。当线程被构造时,需要的代码和数据通过 一个对象 作为构造函数实参传递进去,这个对象 就是一个实现 了Runnable接口的类的实例。

后台线程与联合线程:

1.后台线程与setDaemon方法

简单的说吧前台线程结束这个程序就结束。只要有一个前台线程在运行那么就没有结束如果只有后台线程在运行那么这个程序就会挂掉

我们以前用到的都是前台进程,如果对某个线程对象 启动(start方法)之前调用了setDaemon(true)方法,这个线程就会变成后台线程。

public class DemonThread
{
public static void mian(String[] args)
{
ThreadTest t=new ThreadTest();
Thread tt=new Thread(t);
tt.setDaemon(true);
tt.start();
}
}
class ThreadTest implements Runnable
{
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName()+"   is running.")
}
}
}//这里我们虽然创建了一个无限循环的线程,因为他是后台线程整个进程在主线程结束时就终止了运行。这就说明了上面的理论。

2.联合线程与join方法:

join(long millis)和join(long millis,int nanos)这是两个带参数的join方法,他们是两个线程合并指定的时间后又开始分离回到以前的样子

还有一个是无参数的join方法

方法名.join(),即pp.join();语句  他的作用就是把pp所对应的线程合并到调用 pp.join();语句的线程中

多线程在实际中的应用:

如何将数据库一个表中的所有记录复制到另一个表中,

boolean bFlag =true;
while(bFlag)
{
复制程序
}

多线程的同步:

 线程安全问题:

 问题原因

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

解决方法

对多条操作共享数据的语句,只能让一个线程都执行完,在执行的过程中,其他线程不可以参与执行

或者说吧:就是一个资源分配的问题。

死锁的必要条件(也是解决线程安全问题的一个方法如果线程出现了死锁我们就可以用下面这些方式来干掉他)

1.互斥地使用资源       第个资源只能给一个进程使用

 2.占有且等待资源      一个进程 申请资源得不到满足时处于等待资源的状态,且不释放占用资源

 3.非抢夺式分配资源        任何一个进程 不能抢夺另一个线程所占的资源,也就是被占用资源只能由占用线程自己来释放

  4循环等待资源         存一组队线程,其实第个线程分别等待另一个进程所占用的资源

对多条操作共享数据语句,只能让一个线程都执行完,在执行过程中其他线程不可以参与执行

java对待这些问题又提供了专业的解决方法

synchronized(对象)

{

需要被同步的代码

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

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

线程睡眠是可以被打断的通过Thread.interrupt()当然一个线程中呆以调用另外一个线程的interrupt(),线程睡眠被打断后进入Runnable状态。由于Thread.sleep()的定义中的

trows关键字声明该方法中有可能引发异常所以我们的程序在调用该方法时必须使用try....catch代码块处理,否则编译将出错。这正是java语句强健性的一个方面。

同步代码块:

 如果代码不同步可能会出现很多的安全问题所以我们要想一个办法来解决?那么同步是最好的办法

同步要做到些啥呢?用一个例子来说:就好比宿舍的单人床吧!如果 是两个人睡到一起那么肯定会掉下来一个,所以我们就只能一个一个的睡觉。

也就是程序中有多个线程同时在这两句代码之间但是只能有一个执行。

同步必须满足下面的三个好求:

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

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

 3 必须保证同步代码中只有一个线程在执行

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

       弊端:耗费内存资源,每次进锁都要判断!也耗费时间

我们必须要明确他的共享数据。执行代码、3 明确多线程运行代码中哪些操语句是操作共享数据的。

synchrinized语句格式 synchrinized(ibject){代码段}//object为任一对象。将具有原子性的代码放入这个语句 内那么就形成了同步代码块。在同一个时刻只能有一个线程可以进入同步代码块内运行。

class ThreadTest implements Runnable
{
private int tickets=100;
String str=new String("");
public void run()
{
while(true)
{
synchronized(str)
{
if(ticket>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
System.out.println(Thread.currentThread().getName()"is saling ticket"+ticket--);
}
}
}
}
}

同步函数

我们也可以对函数进行同步,只要在需要同步的函数定义前面加上syncthronized关键字即可。

class ThreadTest implements Runnable
{
private int tickets=100;
public void run()
{
while(true)
{
sale();
}
}
public synchronized void sale()//这里就是对函数进行同步。
{
if(tickets>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
System.out,println(Thread.currentThread().getName()+"is saling ticket"+tickets--);
}
}
}

在同一个类中,使用synchronized关键字定义的若干方法 可以在多全线程之间同步。当一个线程进入了synachronized修饰的方法,

其他线程就不能进入同一个对象 所使用了synachronized修饰方法,直到第一个线程执行完它所进入 synchronized修饰的方法为止

代码块与函数间的同步: 

 线程同步靠的是检查同一的标志位,只要让代码块与函数使用同一个监视器,那么我们就可以做到让代码块和函数同步了,类中的非静态方法只能访问本类对象,即this

也就是同步函数汽用的监视器对象只能是this。

public class ThreadDemo1
{
public static void main(String[]args)
{
ThreadTest t=new ThreadTest();
new Thread(t).start();//这个线程调用同步代码块
try{Thread.sleep(1);}
catch(Exception e){}
t.str new String("method");
new Thread(t).start();//这个线程调用同步函数
}
}
class ThreadTest implements Runnable\
{
private int tickets=100;
String str=new String("");
public void run()
{
if(str.equals("method"))//这里不能用==比较运行符因为是比较值,也就成了比较地址值。所以我们这里要用equals因为我们比较的是内容
{
while(true)
{
sale();
}
}
else
{
synchronized(this)//这样才会实现同步如果不是this 是str可能不会同步
{
if(tickets>0)
{
try 
{
Thread.sleep(10);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}System.out.print("Test:  ");//一个检测语句
System.out,println(Thread.currentThread().getName()+"is saling ticket"+tickets--);
}
}
}
}
public synchronized void sale()
{
if(tickets>0)
{
try
{
Thread.sleep()10;
}
catch(Exception e)
{
System.out.println(e.getMessage);
}
System.out,println(Thread.currentThread().getName()+"is saling ticket"+tickets--);
}
}
}
————————————————————————————————————
public void push(shar c)
{
data[idx]=c;
idx++;
}

 如果一个线程刚执行完push方法中的data方法中的data[idx]=c语句,CPU便切换到另外一个线程上执行push方法,第二个线程将覆盖第一个线程执行的data[idx]=c语句的结果

还有共享访问数据,应当是类的private数据成员,从而禁止来自类外的随意访问破坏数据的一致性。

上面的代码如果这样写容易出现安全问题

死锁:

什么是死锁呢?

简单的说嘛:就是个犯罪分子在抢了银行后拿起枪 ,抓起人质威胁警察,警察想救人,犯罪分子想走,然后犯罪的叫警察先给他准备东西然后再放人质。而且警察要他先放人质在给他东西让他走。最后就谁也不让。然后就出现了两边对峙。谁都不干的状况也就导致事件不能继续下去。

解决方法:

就是让一个线程先执行完然后再执行另外的就OK了也就是同步可以解决死锁问题

线程间通信: 

 其实就是多个线程在操作同一个资源。但是操作的动作不同。 

等待唤醒机制:两个或多个线程同时访问共享数据

   wait() 释放资源,释放锁         sleep() 释放资源,不释放锁。            notify() 唤醒最先wait的线程。             notifyAll()唤醒所有wait的线程。

              都是用在同步中,因为要对持有监视器(锁)的线程操作

              所以要使用在同步中,因为只有同步才具有锁。

              为什么这些操作线程的方法要定义在object类中呢?

              因为这些方法在操作同步线程时,都必须要标示它们所操作线程持有的锁

              只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒

              不可以对不同锁中的线程唤醒。

              也就是说,等待和唤醒必须是同一个锁。

              而锁可以是任意对象,锁可以被任意对象调用的方法定义在object类

       也可以用前面 说到的         join       当a线程执行到了b线程的join方法时,那么a线程就会等待,等b线程都执行完

              a才会执行。join可以用来临时加入线程执行。

        优先级

所有线程默认优先级都是5   1<=优先级<=10 MIN_PRIORTY  MORM_PRIORTYMAX_PRIORTY

              setPriority(int);设置优先级

             

stop方法已经过时

       所以停止线程只有一种,run方法结束。

       开启多线程运行,运行代码通常是循环结构。

       只要控制住循环,就可以让run方法结束,即线程结束

       特殊情况

       当线程处于冻结状态。就不会读取到标记,线程就不会结束

       当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。

       强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束

       Thread类提供方法 interrupt();

线程生命的控制:

任何东西都有一个生命周期,线程也是如此

线程运行过程中的去向:

1.没有遇到任何阻隔,运行完成直到结束,也就是run()方法执行完毕

2.调度器CPU分配给其他线程,这个线程Runnable状态

3.请求锁旗标却得不到,这个时候 它要等待对象的锁旗标,得到锁旗后又会进入Runnable状态开始运行

4.遇到wait方法它会被放入等待池中继续待会,直到有notify()或interrupt()方法执行,它才会被 唤醒或打断开始等待对象 锁旗标,等 到进入Runnable状态继续执行

其实控制线程生命周期的方法有很多种。   suspend     resume    stop方法便是不推荐使用这三种

suspend   和stop方法  会导致死锁   它允许A线程直接控制B线程的代码来直接控制B线程

STOP会导致数据不完整性

推荐使用控制 run方法中循环条件的方式来结束一个线程。



抱歉!评论已关闭.