十一、多线程
1、系统结构图(xmind)
2、tips
——1.多线程概述
JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
1.执行main函数的线程,该线程的任务代码都定义在main函数中
2.负责垃圾回收的线程。
System类的gc方法告诉垃圾回收器调用finalize方法,但不一定立即执行。
——2.继承Thread类
创建线程的目的就是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行,而运行的指定代码就是这个执行路径的任务,jvm创建的主线程的任务都定义在了主函数中,而自定义的线程,它的任务在哪儿呢?Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述,这个任务就是通过Thread类中的run方法来体现,也就是说,run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程要运行的任务代码。开启线程是为了运行指定代码,所以只有继承Thread类,并覆写run方法,将运行的代码定义在run方法中即可。
多线程栗子:
class Demo extends Thread
{
private String name;
Demo(String name)
{
this.name = name ;
}
//复写Thread类的run方法
public void run()
{
//Thread.currentThread().getName()当前线程名字
for(int x = 0;x < 10;x++)
{
System.out.println(name+"...x="+x+"....Threadname:"+Thread.currentThread().getName());
}
}
}
class ThreadDemo4
{
public static void main(String[] args)
{
Demo d1 = new Demo("毛1");
Demo d2 = new Demo("毛2");
//开启线程,调用run方法
d1.start();
d2.start();
for(int x=0;x<10;x++)
{
System.out.println("x:"+x+"...over....."+"Threadname:"+Thread.currentThread().getName());
}
}
}
栗:
//准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行
//通过接口的形式完成
class Demo implements Runnable
{
//复写run方法
public void run()
{
show();
}
public void show()
{
for(int x=0;x<10;x++)
{
System.out.println(Thread.currentThread().getName()+"...."+x);
}
}
}
class ThreadDemo1
{
public static void main(String[] args)
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
运行结果:
——4.线程安全问题
栗:
/*
需求:模拟4个线程卖100张票。
*/
class Ticket implements Runnable
{
private int num = 100;
Object obj = new Object();
public void run()
{
while (true)
{
//同步锁,当有线程进入时,将会锁住,在当前线程为运行完前不允许进入
synchronized(obj)
{
if (num > 0)
{
System.out.println(Thread.currentThread().getName()+"..."+num--);
}
}
}
}
}
class TicketDemo2
{
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();
}
}
运行结果:
原因分析:从运行结果可以得知,安全问题已经解决,原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。当num=1时,Cpu切换到某个线程后,其他线程将无法通过同步代码块继而进行if判断语句,只有等待前一个线程执行完num--操作,并且跳出同步代码块后,才能抢到锁,其他线程即使抢到锁,然而,此时num已经是0,也就无法通过if语句判断,从而无法再执行num--操作,也就不会出现0、-1、-2的情况了。
——5.同步函数和同步代码块的区别
1.同步代码块的锁是任意的对象
2.同步函数的锁是固定的this。
建议使用同步代码块
同步函数的锁是固定的this,同步代码块的锁是任务的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。
静态的同步函数使用的锁是该函数锁属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。
栗:
class Ticket implements Runnable
{
private int num = 100;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
//使用this作为锁,而线程sleep需要抛出异常所以使用try/catch捕捉
synchronized(this.getClass())//Ticket.Class()
{
if(num>0)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...."+num--);
}
}
}
}
else
while(true)
show();
}
//当flag为false时执行同步函数
public synchronized void show()
{
if(num > 0)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...."+num--);
}
}
}
class SynFunctionLockDemo2
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
/*
需要sleep10毫秒的原因是因为可能线程t1尚未真正启动,flag就被设置为false,那么当ti执行
时,就会按照flag为false的情况执行,线程t2也按照flag为false的情况执行.
*/
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
t.flag = false;
t2.start();
}
}
运行结果:
——6.多线程下的单例模式
饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况
懒汉式存在安全问题,能够使用同步函数解决:
class Single
{
public static Single s =null;
public void Single(){}
public static Single getInstance()
{
if(s == null)
{
Synchronized(this.getClass())//Single.class
{
if(s == null)
s = new Single();
}
}
return s;
}
}
如果使用同步函数,效率较低,因为每次都需要判断,而上部代码的方式不会有这样的问题,当任何一个线程执行到第一个if判断语句时,Single对象已经创建,则直接能够获取,不用判断是否能够获取锁,相对于使用同步函数的方法就提升了效率。
——7.死锁
当同步中嵌套同步时就有可能出现死锁的现象。
栗:
/*
写一个死锁程序
*/
//定义一个类来实现Runnable,并复写run方法
class LockTest implements Runnable
{
private boolean flag;
LockTest(boolean flag)
{
this.flag=flag;
}
public void run()
{
if(flag)
{
while(true)
{
synchronized(LockClass.locka)//a锁
{
System.out.println(Thread.currentThread().getName()+"------if_locka");
synchronized(LockClass.lockb)//b锁
{
System.out.println(Thread.currentThread().getName()+"------if_lockb");
}
}
}
}
else
{
while(true)
{
synchronized(LockClass.lockb)//b锁
{
System.out.println(Thread.currentThread().getName()+"------else_lockb");
synchronized(LockClass.locka)//a锁
{
System.out.println(Thread.currentThread().getName()+"------else_locka");
}
}
}
}
}
}
//定义两个锁
class LockClass
{
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLock
{
public static void main(String[] args)
{
//创建2个进程,并启动
new Thread(new LockTest(true)).start();
new Thread(new LockTest(false)).start();
}
}
运行结果:
——8.线程中通信
等待/唤醒机制设计的方法
1.这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2.必须要明确到底操作的是哪个锁上的线程!
wait和sleep区别?
1)wait可以指定时间也可以不指定。sleep必须指定时间。
2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法,监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方式一定在object类中。
栗:
/*
有一个资源
一个线程往里存东西,如果里边没有的话
一个线程往里取东西,如果里面有得话
*/
//资源
class Resource
{
private String name;
private String sex;
private boolean flag=false;
public synchronized void setInput(String name,String sex)
{
if(flag)
{
try{wait();}catch(Exception e){}//如果有资源时,等待资源取出
}
this.name=name;
this.sex=sex;
flag=true;//表示有资源
notify();//唤醒等待
}
public synchronized void getOutput()
{
if(!flag)
{
try{wait();}catch(Exception e){}//如果木有资源,等待存入资源
}
System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出
flag=false;//资源已取出
notify();//唤醒等待
}
}
//存线程
class Input implements Runnable
{
private Resource r;
Input(Resource r)
{
this.r=r;
}
public void run()//复写run方法
{
int x=0;
while(true)
{
if(x==0)//交替打印张三和王羲之
{
r.setInput("谢",".....man");
}
else
{
r.setInput("毛","..woman");
}
x=(x+1)%2;//控制交替打印
}
}
}
//取线程
class Output implements Runnable
{
private Resource r;
Output(Resource r)
{
this.r=r;
}
public void run()//复写run方法
{
while(true)
{
r.getOutput();
}
}
}
<strong>
</strong>
class ResourceDemo6
{
public static void main(String[] args)
{
Resource r = new Resource();//表示操作的是同一个资源
new Thread(new Input(r)).start();//开启存线程
new Thread(new Output(r)).start();//开启取线程
}
}<strong>
</strong>
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
/*
生产者生产商品,供消费者使用
有两个或者多个生产者,生产一次就等待消费一次
有两个或者多个消费者,等待生产者生产一次就消费掉
*/
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count=1;
private boolean flag = false;
//多态
private Lock lock=new ReentrantLock();
//创建两Condition对象,分别来控制等待或唤醒本方和对方线程
Condition condition_pro=lock.newCondition();
Condition condition_con=lock.newCondition();
//p1、p2共享此方法
public void setProducer(String name)throws InterruptedException
{
lock.lock();//锁
try
{
while(flag)//重复判断标识,确认是否生产
condition_pro.await();//本方等待
this.name=name+"......"+count++;//生产
System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产
flag=true;//控制生产\消费标识
condition_con.signal();//唤醒对方
}
finally
{
lock.unlock();//解锁,这个动作一定执行
}
}
//c1、c2共享此方法
public void getConsumer()throws InterruptedException
{
lock.lock();
try
{
while(!flag)//重复判断标识,确认是否可以消费
condition_con.await();
System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费
flag=false;//控制生产\消费标识
condition_pro.signal();
}
finally
{
lock.unlock();
}
}
}
//生产者线程
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res=res;
}
//复写run方法
public void run()
{
while(true)
{
try
{
res.setProducer("商品");
}
catch (InterruptedException e)
{
}
}
}
}
//消费者线程
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res=res;
}
//复写run
public void run()
{
while(true)
{
try
{
res.getConsumer();
}
catch (InterruptedException e)
{
}
}
}
}
class ProducerConsumer
{
public static void main(String[] args)
{
Resource res=new Resource();
new Thread(new Producer(res)).start();//第一个生产线程 p1
new Thread(new Consumer(res)).start();//第一个消费线程 c1
new Thread(new Producer(res)).start();//第二个生产线程 p2
new Thread(new Consumer(res)).start();//第二个消费线程 c2
}
}
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+"....run");
}
}
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.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
t1.interrupt();//清除冻结状态
t2.interrupt();
st.changeFlag();//改变循环标记
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
运行结果:
当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。
2、setPriority()方法用来设置优先级
MAX_PRIORITY 最高优先级10
MIN_PRIORITY 最低优先级1
NORM_PRIORITY 分配给线程的默认优先级
3、yield()方法可以暂停当前线程,让其他线程执行。