黑马程序员-------------多线程

多线程一:什么是多线程1.进程:进程就是正在进行中的程序。每一个进程执行都有一个执行顺序,该顺序叫执行路径。或者叫一个控制单元。例:我们电脑中多个运行程序,不是同时运行的,而是cpu在快速的切换运行,一个cpu只能在同一个时间内执行一个程序。之所以我们感觉实在同时运行是因为cpu切换的速度快。 2.线程:线程是进程中的内容,每个应用程序最起码有一个线程 。因为线程是程序中的
摘要由CSDN通过智能技术生成

------- android培训java培训、期待与您交流! ----------

多线程

一:什么是多线程

1.进程:进程就是正在进行中的程序。

每一个进程执行都有一个执行顺序,该顺序叫执行路径。或者叫一个控制单元。

例:我们电脑中多个运行程序,不是同时运行的,而是cpu在快速的切换运行,一个cpu只能在同一个时间内执行一个程序。之所以我们感觉实在同时运行是因为cpu切换的速度快。

 

2.线程:线程是进程中的内容,每个应用程序最起码有一个线程 。因为线程是程序中的控制单元。

或者叫执行路径。

线程就是进程中的一个独立的控制单元。

线程在控制着进程的执行。

 

3.Java有两个进程编译进程和运行进程。

Java运行进程:

java虚拟机启动的时候会有一个进程Java.exe

该进程中至少有一个线程赋值Java程序的执行。

而且这个线程运行的代码存在于main方法中。

该线程称之为主线程。

扩展:例如:其实更细节说明jvmjvm启动不只一个线程,还有负责垃圾回收机制的线程。

 

3.线程存在的意义:

多线程可以让多个代码同时运行。

例如:迅雷的多线程可以提高效率。

二.创建线程

1.线程(控制单元)真正是系统创建的。进程中的线程也是系统创建的,Java中的jvm依赖于系统,所以它只需要去调用系统当中的内容,就能完成这个动作了。这也就是说Java给我们提供好了对这类事物的对象。

2.创建新执行线程有两种方法。

1)一种方法是:继承方式

一,通过对api的查找,Java已经提供了对线程这类事物的描述,Thread类。将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。

二,步骤:

1.继承Thread

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

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

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

三,其中为什么要覆盖run方法呢?

Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代 码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存 储 线程运行的代码主线程的代码存在main方法。

四,复写Thread类中的run方法的原因:

其实创建Thread d = new Thread();//对象编译和运行也不会出错,

但我们调用d.start();方法时其调用的是d.run方法的内容,而d.run方法的

内容为空所以不显示。如果我们继承run的方法,就可以覆盖掉run的方法的

内容写上自己的代码,这样就能运行我们写的代码。

五,复写Thread类中的run方法的目的:

将自定义代码存储在run方法,让线程运行。

例:

class Demo extends Thread
{
public void run()
{
for(int x=0; x<60; x++)
System.out.println("demo run---+"+x);
}
}
 
class ThreadDemo1
{
public static void main(String[] args)
{
Demo d = new Demo();//建立好一个对象就创建好一个线程。
d.start();//运行线程
for(int x=0; x<60; x++)
System.out.println("hello world---+"+x);
}
}


 

2)创建线程的另外一种方法:实现方式

一,创建线程的另一种方法是声明实现 Runnable(可运行的意思)接口的类。 该 类然后实现 run 方法。

二,Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个 称为 run 的无参数方法。

三,步骤:

1.定义类实现Runnable接口

2.覆盖Runnable接口中的run方法。

将线程要运行的代码存放在该run方法中。

3.通过Thread类建立线程对象。

4.Runnable接口的子类作为实际参数传递给Thread类的构造函数;

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

例:

class Ticket implements Runnable//1.定义类实现Runnable接口
{
private int tick =100;
public void run()//2.覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中
{
while(true)
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"...sale..."+tick--);
}
}
}
}
 
class ThreadTest3
{
public static void main(String[] args)
{
Ticket t = new Ticket();//将Runnable接口的子类作为实际参数传递给Thread类的构造函数;
Thread t1 = new Thread(t);//3.通过Thread类建立线程对象。
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();//调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
t2.start();
t3.start();
t4.start();
}
}


3.实现方式与继承方式有什么区别?

1)实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。 2)两种方式的区别:

继承Thread:线程代码存放在Thread子类run方法中。

实现Runnable:线程代码皴法在接口的子类的run方法。

三.线程的特性

多线程的特性:随机性,谁抢到谁执行,至于执行多长,CPU说的算。

简单来说:

运行多个程序的时候cpu在做快速的切换进程,更确切的说cpu在切换进程中的线程。发现运行结果每次都不同,因为多个线程都在获取cpu的执行权,CPU执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序有一个程序运行。(多核除外)

cpu在做着快速的切换,已达到看上去是同时运行的效果。我们可以形象把多线程的运 行行为在互相抢夺cpu的执行权。

四.线程的运行状态

1.线程的运行状态:

进程结束就代表存储空间没了。

2.线程的常见四种状态:

1)被创建

通过start()到运行这状态;

2)运行

运行状态既有执行资格,又有执行权;

   1.运行通过sleep(time)到冻结这状态;  

   2.wait()运行到冻结这状态;如果没有传参数就回不到运行状态;   

3)冻结:分为睡眠和等待。

   冻结状态就是放弃执行资格。

   

   当从冻结状态恢复时,不一定是回到运行状态,可能先回到阻塞状态;

   说明这个程序有执行资格,但还不一定有执行权。

   

1.sleep冻结时间到返回运行状态;

2.notify();冻结回到运行状态

4)消亡

stop();运行到消亡状态;run方法结束;

5)阻塞(临时状态)

如果运行多个线程时,cpu只运行一个线程,其他线程就是

阻塞状态,他们在等待cpu执行权。

专业术语:具备运行资格,但没有执行权

五.多线程的安全问题

1.多线程产生问题的原因:

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

例子:

假设:程序运行有三个线程1,2,3,1线程进来后,cpu运行到判断语句后 33 行后跳到2线程运行也执行到判断语句33行后,再跳到3线程运行,三线程也 一样。现在返回1线程,cpu34行语句开始读,如果这是输入的数据为0,再运tick--则打印票数就变成-1的票数,而实际生活中没有-1的票数则程序就除了问 题。

class Ticket implements Runnable
{
private int tick =100;
Object obj = new Object();
public void run()//覆盖run方法;
{
while(true)
{
synchronized(obj)//同步代码块;
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...sale..."+tick--);
}
}
}
}
}
 
class ThreadDemo6
{
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.解决多线程出现的问题:线程同步

什么是线程同步:

例:

假设有4个线程,当线程1抢到执行权,读取synchronized(obj)语句后,相当于给synchronized(obj)代码块的内容上一把锁,让其他2,3,4线程读取不了程序块内的内容当线程1执行完synchronized(obj)代码块内的内容时候,才把代码块的锁解开,这时其他线程才能访问这个代码块,这就保证也该线程一定运行完此代码块。

而不会因为出现判断语句,而出现程序错误

 

程序的同步的代码格式:

synchronized名称为锁旗标或监视器。

synchronized(对象)

{

需要被同步的代码

}

同步的前提:

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

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

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

同步的优点与弊端:

同步的好处:

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

同步的弊端:

多个程序需要判断,较为消耗资源。

例:

class Ticket implements Runnable
{
private int tick =100;
Object obj = new Object();
public void run()//覆盖run方法;
{
while(true)
{
synchronized(obj)//同步代码块;
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}//这个可以不写
System.out.println(Thread.currentThread().getName()+"...sale..."+tick--);
}
}
}
}
}
 
class ThreadDemo6
{
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();
}
}


 

3.同步有两种表现形式:

第一种:同步代码块。

同步代码块,用的是obj的锁

Object obj = new Object();

public void add()

{

synchronized(obj)

{

}

}

第二种:同步函数。

同步函数的锁是this.

public synchronized void add()

{

}

同步函数特例:

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

类名.class 该对象的类型是class

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

类名.class这个对象在内存是唯一,使用后就不再重新加载。

例:

class Ticket implements Runnable
{
private int tick =100;
Object obj = new Object();
boolean flay = true;
//同步代码块,用的是obj的锁
public  void run()。
{
if(flay)
{
while(true)
{
synchronized(this)//run方法有所属对象//(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code..."+tick--);
}
}
}
}
else
while(true)
show();
}
//同步函数用的是this的锁。
public synchronized void show()
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...show..."+tick--);
}
}
}
 
class ThisLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
//这个代码让主线程停止10毫秒,这样能运行的只有t1程序。
try{Thread.sleep(10);}catch(Exception e){}
t.flay = false;
t2.start();
 
}
}


4.单例模式下的懒汉式的多线程:

懒汉式与饿汉式不同:

1.懒汉式在于实例的延迟加载

2.如果懒汉式在多线程会出现安全问题。

这时可以加同步来解决。用双重判断可以解决效率问题。

举例:

class Single
{
private static  Single = null;
pivate Single(){}
public static Single getInstance()
{
if(s= null)//第一重循环
{
synchronized(Single.class)//不满足方法体的判断语句,锁不会自动解开。
{
if(s==null)//第二重循环
s=new Single();
}
}
return s;
}
}
 
class SingleDemo
{
public static void main(String[] args)
{
System.out.println("Hello world");
}
}


5.同步的弊端:死锁

1)口头话描述死锁:

就是你持有一个锁,我也持有一个锁,我要带你那里面去运行,我则要拿你的锁,而你要到我这边运行,你也得跟我要锁,我不放我的锁要进你那边去,你不放你的锁要进我这边来,谁都不放,着就导致了死锁的现象。当死锁的产生程序就会挂在哪里不动了。都在互相要锁。

2)死锁的出现:通常同步中嵌套同步。

例:

class Ticket implements Runnable
{
private  int tick =1000;
Object obj = new Object();
boolean flay = true;
 
public  void run()
{
if(flay)
{
while(true)
{
synchronized(obj)
{
show();
}
}
}
else
while(true)
show();
}
public  synchronized void show()
{
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.flay = false;
t2.start();
 
}
}


六.多线程API中常用方法

1.public final void join()

                throws InterruptedException等待该线程终止。 

join的特点:

A线程执行到了B线程的.join();方法时,A就会等待,等B线程都执行完,A线程才会执行。

join可以用过来临时加入线程执行。

2.守护线程:void setDaemon(boolean on)

将该线程标记为守护线程或用户线程。 

1.将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时, Java 虚拟机退出。 

2.该方法必须在启动线程前调用。 

守护线程:可以理解为后台线程。

后台线程特点:

1.开启后和前台线程(例如:主线程)抢夺cpu的执行权运行。

2.当所有前台线程结束后,后台线程会自动结束。

3.停止线程:

1.定义循环结束标记

因为线程运行代码一般都是循环,只要控制循环即可。

2.使用interrupt(中断)方法。

该方法是结束线程的冻结状态,是线程回到运行状态中来。

注:stop方法已经过时不再使用。

3.停止线程只有该一种方法,run方法结束

4.例子:

class StopThread implements Runnable
{
private boolean flag = true;
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+"...run");
}
}
public void changeFlag()
{
flag = false;
}
}
 
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
//守护线程在此添加:
t1.setDaemon(true);
t2.setDaemon(true);
 
t1.start();
//添加代码:
t1.join();
//join的意思是说:主线程在向下走的时候读到t1.join();t1要抢夺
//cpu执行权。主线程把执行权让出,等T1执行完毕后,主线才恢复	执行权。
t2.start();
/*
如果把t1.join();放在t2下面:发生的现象:主线程开启了t1,t2线程,	这时候执行权可能在主线程手里,这时当主线程碰到t1.join();时候,主线程释	放执行权,这时候还有t1,t2有资格抢夺执行权,这时候cpu就对t1,t2进行交	替执行。可是主线程什么时候活,主线程要等到t1结束,主线程才活。就	是说谁让主线程释放执行权,主线程就等谁运行结束后才有资格抢夺cpu的执	行权。
*/
int num = 0;
while(true)
{
if(num++ == 60)
{
st.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"...."+num);
}
}
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值