点击打开链接 点击打开链接 点击打开链接 android培训、<a">点击打开链接 点击打开链接 java培训、期待与您交流!">点击打开链接 点击打开链接
多线程
进程:是一个正在进行的程序。
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元。
线程控制着进程的执行
一个进程中至少有一个线程
JVM 启动的时候会有一个java.exe
该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在main方法中。该线程称之为主线程。
扩展:其实更细节说明jvm,jvm启动不止一个线程,还有辅助垃圾回收机制的线程。
多线程存在的意义
多线程编程简单、效率高,易于资源共享。
如何在自定义代码中自定义线程?
1.1将类声明为Thread的子类,具体步骤如下:
class Demo extends Thread //①定义类继承Thread
{
public void run() //②复写Thread的run方法
{
for(int i=0;i<40;i++)
{
System.out.println("demo run");
}
}
}
class Test
{
public static void main(String[] args)
{
Demo demo1=new Demo();
demo1.start(); //③调用线程的start方法,该方法有2个作用,启动线程和调用run方法
for(int i=0;i<40;i++)
{
System.out.println("main run-----");
}
}
}
1.定义类继承Thread
2.覆盖run方法
会发现运行结果每一次都不同,这是因为多个线程都获取CPU的执行权,CPU执行到谁,谁就运行。明确一点讲,就是在某一时刻,只能有一个程序在运行(多核除外)。CPU在做着快速地切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行行为看做是在互相抢夺CPU的执行权。这就是多线程的一个特性:随机性,谁抢到谁执行,至于执行多久,CPU说的算。
为什么覆盖run方法?
Thread类用于描述线程。
该类就定义了一个功能run,用于存储线程要执行的代码,该存储功能就是run方法,也就是说Thread类中的run方法,用于存储线程要运行的代码。
线程存在的几种状态:
Thread类
String / getName():返回该线程的名称,线程都有自己默认的名称,Thread-编号,从0开始。
Static Thread / currentThread():返回当前正在执行的对象的名称。
void setName(String):设置线程名称。构造函数也可以设置线程名称,不过要写构造函数继承父类。
static void / sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠。
void / start() 使该线程开始执行:java虚拟机调用该线程的run方法。
String / toString() 返回该线程的字符串表现形式,包括线程名称、优先级和线程组。
static void / yield() 暂停当前正在执行的线程对象,并执行其他线程。
void /setPriority(int newpriority): 更改线程的优先级,提供了三个常量:MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY。
void / setDeamon(boolean on):将该线程标记为守护线程或用户线程。
void / join():等待该线程终止,此外还有join(long millis)、jion(long millis,int nanos),设置等待该线程结束的时间。
boolean / isInterrupted() 测试该线程是否已经中断
boolean / isDemo() 测试该线程是否为守护线程
boolean / isAlive() 测试该线程是否处于活动状态
static boolean / interrupted() 测试当前线程是否已经中断
void / interrupt() 中断线程
创建线程的第二种方式
步骤:
1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法。
3.通过Thread类建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
自定义的run方法所属的对象是Runnable接口的子类对象,要让线程去执行指定对象的run方法,就必须明确run方法所属对象
5.调用Thread的start方法开启线程并调用Runnable接口子类的run方法。
class Demo1 implements Runnable //第一步,实现Runnable接口
{
public void run() //第二步,覆盖run方法
{
for(int i=0;i<40;i++)
{
System.out.println(Thread.currentThread().getName()+"::"+i);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo1 d=new Demo1(); //第三步,建立Runnable的子类对象
Thread th=new Thread(d); //第四步.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
th.start(); //第五步,调用Thread的start方法开启线程并调用Runnable接口子类的run方法。
}
}
实现方式和继承方式的区别
实现方式的好处:避免单继承的局限性
在定义线程时,建议使用实现方式
继承Thread:线程代码存放在Thread子类的run方法
实现Runnable:线程代码存放在接口的子类run方法中。
多线程的运行出现了安全问题
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分还没执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程都不参与执行。
Java对线程安全问题提供了专业的解决方式,就是同步代码块
synchronized(对象)
{
需要同步的代码块;
}
对象如同锁,持有锁的线程可在同步中执行。
没有锁的线程即使获得CPU的执行权也进不去,因为没有获取锁。
例:Test9
class Test9
{
public static void main(String[] args)
{
Demo d=new Demo();
Thread t1=new Thread(d);
Thread t2=new Thread(d);
t1.start();
t2.start();
}
}
class Demo implements Runnable
{
public int s=100;
public void run()
{
while(s>=0)
{
synchronized(Demo.class)
{
if(s>=0)
{
System.out.println(Thread.currentThread().getName()+"售出了第"+s+"张票");
s=s-1;
}
}
}
}
}
同步的前提:
1.必须要有两个或两个以上的线程
2.必须是多个线程使用同一个锁
3.必须保证同步中只有一个线程在运行。
同步的利与弊
好处:解决多线程的安全问题
弊端:多线程需要判断锁,较为耗费资源。
同步函数
函数需要被对象调用,那么函数都一个所属对象引用,就是this,所以同步函数的锁是this。如果同步函数被static修饰后,所使用的锁不是this,因为静态方法中不可以定义this,静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class,该对象的类型是class,静态的同步方法,使用的锁是该方法所在类的字节码文件对象:类名.class。
死锁:同步嵌套,而锁不同。Test10
class Test10 //死锁实例
{
public static void main(String[] args)
{
DeadLock d1=new DeadLock(true);
DeadLock d2=new DeadLock(false);
Thread t1=new Thread(d1);
t1.start();
Thread t2=new Thread(d2);
t2.start();
}
}
class DeadLock implements Runnable
{
private boolean flag;
DeadLock(boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
synchronized(Mylock.obj1)
{
System.out.println("if.....this");
synchronized(Mylock.obj2)
{
System.out.println("if.....flag");
}
}
}else
{
synchronized(Mylock.obj2)
{
System.out.println("else.....flag");
synchronized(Mylock.obj1)
{
System.out.println("else.....this");
}
}
}
}
}
class Mylock
{
static Object obj1=new Object();
static Object obj2=new Object();
}
线程间通讯:其实就是多个线程在操作同一个资源。但是操作的动作不同。
等待唤醒机制,例:Test11
class Test11
{
public static void main(String[] args)
{
Res s=new Res();
new Thread(new Input(s)).start();
new Thread(new Output(s)).start();
new Thread(new Input(s)).start();
new Thread(new Output(s)).start();
}
}
class Res //资源类
{
private String name;
private String sex;
boolean flag=false; //定义一个标记
//同步函数,输入资源
public synchronized void set(String name,String sex)
{
if(this.flag)//当标记为真时,让此线程等待
try{wait();}catch(Exception e){}
//当标记为假或者,线程被唤醒,,就会执行以下代码块
this.name=name;
this.sex=sex;
System.out.println("输入了……"+name+"::"+sex);
//把标记变为真,并唤醒线程池中等待的线程
this.flag=true;
this.notify();
}
//同步函数,输出资源
public synchronized void print()
{
//当标记为假时,让此线程等待
if(!this.flag)
try{wait();}catch(Exception e){}
//当标记为真,或者线程被唤醒,就会执行输出语句
System.out.println(this.name+"…………"+this.sex);
//输出完,让标记变为false,并唤醒输入线程
this.flag=false;
this.notify(); //通常唤醒线程池中第一个等待的
}
}
//输入类
class Input implements Runnable
{
private Res s;
Input(Res s)
{
this.s=s;
}
public void run()
{
int x=0;
while(true)
{
if(x==0)
{
s.set("make","nan");
}else
{
s.set("丽丽","女");
}
x=(x+1)%2;
}
}
}
//输出类
class Output implements Runnable
{
private Res s;
Output(Res s)
{
this.s=s;
}
public void run()
{
while(true)
{
s.print();
}
}
}
wait()
notify()
notifyAll()
这三个定义在都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操纵同步线程时,都必须要标识它们所操作线程中的锁。只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。
也就是说等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
例:Test12
class Test12 //生产消费实例
{
public static void main(String[] args)
{
Res s=new Res();
Thread t1=new Thread(new Produce(s)).start();
Thread t2=new Thread(new Produce(s)).start();
Thread t3=new Thread(new Consum(s)).start();
Thread t4=new Thread(new Consum(s)).start();
}
}
//资源类
class Res
{
private String name;
private int count=0;
private boolean flag=false;
public synchronized void set(String name)
{
if(flag) //t1、t2判断完标记后分别陷入在此等待,而后t1被唤醒,生产了一个商品,
//而后可能把t2给唤醒,t2会直接执行wait后的代码块,又生产一个商品。
//这显然不符合要求,生产了两个商品,而有一个没被卖掉,
//究其原因,是线程被唤醒后没再次判断标记引起的,为了解决这个问题,我们只要把if变成while,循环判断即可。
try{this.wait();}catch(Exception e){}
this.name=name+count;
System.out.println("生产了"+this.name);
count++;
flag=true;
this.notify();
}
public synchronized void out()
{
while(!flag)
try{this.wait();}catch(Exception e){}
System.out.println("售出了"+this.name+".....");
flag=false;
this.notify(); //当生产和销售方法里的if都变成whlie后,会产生一个新的问题,就是如果t1、t2、t3三个线程相继陷入等待,
//t4运行完,自个陷入等待,t1被唤醒,执行完,后唤醒的是线程池中第一个等待的线程t2,而t2一运行就会判断
//标记,再次陷入等待中,这样四个线程都在等待,程序就被挂起了。所以这里应该用notifyAll().
}
}
class Produce implements Runnable
{
private Res s;
Produce(Res s)
{
this.s=s;
}
public void run()
{
while(true)
{
s.set("苹果");
}
}
}
class Consum implements Runnable
{
private Res s;
Consum(Res s)
{
this.s=s;
}
public void run()
{
while(true)
{
s.out();
}
}
}
在以上的例子中,当生产或消费一方的线程运行后,我们希望唤醒的是另一方的线程,而notifyAll连本方线程一起唤醒,如何解决这个问题呢?
JDK1.5中提供了线程升级解决方案
l 将同步synchronized替换成实现lock操作
l 将object中的wait、notify、notifyAll替换了condition对象
l 该类可以对lock锁进行获取
l 该实例中实现了本方只唤醒对方的操作。
例Test13
import java.util.concurrent.locks.*;
class Test13 //生产消费实例升级版
{
public static void main(String[] args)
{
Res s=new Res();
new Thread(new Produce(s)).start();
new Thread(new Consum(s)).start();
new Thread(new Produce(s)).start();
new Thread(new Consum(s)).start();
}
}
class Res
{
private String name;
private int count=0;
private boolean flag=false;
private Lock lock=new ReentrantLock(); //同步换成锁
private Condition con_pro=lock.newCondition();
private Condition con_con=lock.newCondition();
public void set(String name) throws InterruptedException
{
lock.lock();
try
{
while(flag)
con_pro.await(); //wait替换成Condition的con_pro对象的await方法
this.name=name+count;
System.out.println("生产了"+this.name);
count++;
flag=true;
con_con.signal(); //notify替换成Condition的con_con对象的signal方法,只会唤醒对方等待线程
}
finally
{
lock.unlock();
}
}
public void out() throws InterruptedException
{
lock.lock();
try
{
while(!flag)
con_con.await();
System.out.println("售出了"+this.name+".....");
flag=false;
con_pro.signal(); //只会唤醒对方等待的线程
}
finally
{
lock.unlock();
}
}
}
class Produce implements Runnable
{
private Res s;
Produce(Res s)
{
this.s=s;
}
public void run()
{
while(true)
{
try{s.set("苹果");}catch(InterruptedException e){}
}
}
}
class Consum implements Runnable
{
private Res s;
Consum(Res s)
{
this.s=s;
}
public void run()
{
while(true)
{
try{s.out();}catch(InterruptedException e){}
}
}
}
Lock接口
在java.util.concurrent.locks包中,Lock实现提供了比使用synchronized方法和语句可获得更广泛的锁的操作。此实现允许更灵活的结构,可以具有很大差别的属性,可以支持多个相关的Condition 对象。
已知实现类ReentranLock、ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock,
方法摘要
void / lock():获取锁。
void /lockInterruptibly():如果当前线程未被中断则获取锁。
Condition / newCondition():返回绑定到此Lock实例的新Condition实例。
boolean / tryLock():仅在调用时锁为空闲状态则获取锁
boolean / tryLock(Long time,TimeUnit Unit):如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
void unlock():释放锁。
在同一个包中,Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
方法摘要:
void / await():造成当前线程等待
Boolean / await(long time,TimeUnit unit):在达到指定等待时间之前等待。uint的参数:TimeUnit.DAYS HOURS MICROSECONDS MILLISECONDS MINUTES、NANOSECONDS、SECONDS.
long await()Nanos(long nanosTimeout):造成当前线程在达到指定等待时间之前等待。
void / awaitUninterruptibly():造成当前线程在接收到信号之前一直处于等待状态。
boolean /awaitUnitil(Date deadline):造成当前线程在达到指定最后期限之前一直处于等待状态
void / signal()唤醒一个等待线程
void/signalAll()唤醒所有等待线程。
停止线程
1.定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即可
2.使用interrupt(中断)方法
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。
停止线程,stop方法已经过时,只有run方法运行结束。开始多线程运行,运行代码通常是循环结构。只要控制循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。
Interrupt()将处于冻结状态的线程强制让线程恢复到运行状态。
当没有指定方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态来,这样就可以操作标记让线程结束。
setDeamon(boolean b) 将该线程标记为守护线程或用户线程,当所有线程都为守护线程时,JVM退出。该方法必须在启动线程前调用。
join():等待该线程终止。当A线程执行到了B线程的Join方法时,A线程就会等待B线程执行完,A线程才执行。Join可以用来临时加入线程执行。
setPriority(int ~):线程优先级,三个常量:Thread.MAX_PRIORITY、Thread.MIN_PRIORITY、Thread.NORM_PRIORITY.
static void / yield():暂停当前正在执行的线程。