『11.1』多线程概述
1.进程:是一个正在执行中的程序,每一个进程都有一个执行顺序(执行路径)(控制单元)
2.线程:就是进程中的一个独立的控制单元。线程控制进程执行。
『11.2』创建线程-继承Thread类
创建线程的第一种方法:继承Thread类
步骤:
1.定义类继承Thread
2.复写Thread类中的run方法,目的:将自定义代码存储在run方法中,让线程运行
3.调用线程的start方法,该方法有两个作用:启用线程,调用run方法。
public class D1 {
public static void main(String[] args)
{
Show s=new Show();
s.start(); //创建线程并调用
//s.run(); 创建线程但不调用run
for(int x=0;x<100;x++)
System.out.println(x);
}
}
class Show extends Thread
{
public void run()//run方法要复写掉Thread中的run方法
{
for(int x=0;x<100;x++)
System.out.println("show:"+x);
}
}
为什么要覆盖run方法呢?
因为Thread类中的run方法是public void run(){},如果不覆盖,start后运行就父类中的函数,没有任何指令。达不到要的目的。
『11.3』创建线程-run和start的特点
d.start();//开启线程并执行该线程的run方法。
d.run();//仅仅是对象调用方法,而线程创建了,却并没有运行。
『11.4』练习
public class D2 {
public static void main(String[] args)
{
Student s1=new Student("aaa");
Student s2=new Student("bbb");
s1.start();
s2.start();
}
}
class Student extends Thread
{
String name;
Student(String name)
{
this.name=name;//这是给一个新变量name赋值
//super(name);//而super(name)是因为Thread(String name),这里的name指的是线程名
}
public void run()
{
for(int x=0;x<100;x++)
System.out.println((currentThread())+getName()+"........."+x);//这里的currentThread()和getName()都省略了this. Thread.
}
}
设置线程名称:setName或者构造函数。currentThread():获取当前线程对象
『11.5』线程运行状态
线程运行的五种状态:
(1)被创建
(2)运行
(3)阻塞(临时状态),具备资格但没有执行权
(4)冻结,放弃执行资格
(5)消亡
注:建立线程子类对象的同时线程也被建立
『11.6』获取线程对象和名称
1.两个属性:setName和getName
2.原来线程都有自己默认的名称,Thread-编号,该编号从0开始。
『11.7』售票的例子
class Ticket extends Thread
{
private int tick =100;
public void run()
{
while(true)
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+" sale "+tick--);
}
}
}
}
class D3
{
public static void main(String[] args)
{
Ticket t1 = new Ticket();//创建4个对象,就是有400张票,但要求只卖100张
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
/*Ticket t = new Ticket();------------------------------这方法才是共卖100张票
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()
*/
『11.8』创建线程-实现Runnable接口
步骤:
1.定义实现Runnable接口
2.覆盖Runnable接口的run方法
3.通过Thread类来建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为自定义的run方法所属的对戏那个是Runnable接口的子类对象,所以要让线程去 控制指定对象的run方法。就必须明确该run方法所属的对象。
2、实现方式和继承方式有什么不同?
实现方式的好处:避免了单继承的局限性;在定义线程时,建议使用实现方式。
3、两种方式的区别:
继承Thread:线程代码存放在Thread子类的run方法中。
实现Runnable:线程代码存放在接口的子类的run方法中。
『11.9』多线程的安全问题
class Ticket implements Runnable
{
private int tick = 100;
// Object obj = new Object();
public void run()
{
while(true)
{
// synchronized(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);
}
}
}
}
}
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();
}
}
过分析,发现,打印出0,-1,-2等错票。多线程的运行出现了安全问题。
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行
『11.10』多线程的同步代码块
8、多线程中的同步代码块
Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。
synchronized(对象)
{
需要被同步的代码
}
同步代码块:哪些代码需要同步,就看哪些语句在操作共享数据
对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取了cpu执行权,也进不去,因为没有获取锁。
同步的前提:
1.必须要有两个或者两个以上的线程。
2.必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源
『11.11』同步函数
在复写run()方法的函数里,程序不能抛,因为Thread类中没有该方法
需求:银行有一个金库,有两个储户分别存300元,每次存100,存三次。
目的:该程序是否有安全问题,如果有,如何解决?
如何找问题:
1.明确哪些代码是多线程运行代码。
2.明确共享数据。
3.明确多线程运行代码中哪些语句是操作共享数据的。
synchronized有两种表现形式:1.同步代码块;2.同步函数。
class Bank
{
private int sum;
//Object obj = new Object();
public synchronized void add(int n)//第二种表现形式是同步函数
{
//synchronized(obj)//第一种表现形式是同步代码块
//{
sum+=n;
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println("sum="+sum);
//}
}
}
class Cus implements Runnable
{
private Bank b = new Bank();
public void run()
{
for(int x=0;x<3;x++)
{
b.add(100);
}
}
}
class BankDemo
{
public static void main(String[] args)
{
Cus c = new Cus();
Thread c1 = new Thread(c);
Thread c2 = new Thread(c);
c1.start();
c2.start();
}
}
『11.12』同步函数的锁是this
验证:使用两个线程来卖票,一个线程在同步代码块中,一个在同步函数中,都在执行卖票
class Ticket implements Runnable
{
private int tick =100;
//Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(this)
{
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code: "+tick--);
}
}
}
}
else
while(true)
show();
}
public synchronized void show()//this
{
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();
try{Thread.sleep(10);}catch(Exception e){}
t.flag =false;
t2.start();
}
}
『11.13』静态函数的锁是Class对象
通过验证,发现不再是this,因为静态方法中也不可以定义this。
静态进内存,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是Class synchronized(类名.class)
静态的同步方法,使用的锁是该方法所在类的字节码文件对象:类名.class
『11.14』单例设计模式
懒汉式用于延时加载,多线程加载的时候容易出现问题,可以用同步代码块解决问题,但效率稍低;而加上双重判断的时候则可以提高效率,加同步的时候,该类所属的字节码对象:类名.class
class Single
{
private static Single s = null;//让本类对象创建一个引用
private Single(){}//私有构造函数
public static Single getInstance()//通过双重判断,提供一个公共的访问方式
{
if(s==null)//双重判断,提高效率
{
synchronized(Single.class)//给同步代码添加一把锁
{
if(s==null)
s= new Single();
}
}
return s;
}
}
懒汉式和饿汉式有什么不同?
解答:懒汉式的特点在于实例的延时加载;懒汉式的延时加载有没有问题?有,多线程访问时会出现安全问题,可以加同步来解决,而用双重判断的方法可以解决效率问题;加同步的时候,使用的锁是哪个?该类所属的字节码对象
『11.15』死锁
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
synchronized(MyLock.locka)
{
System.out.println("if locka");
synchronized(MyLock.lockb)
{
System.out.println("iflockb");
}
}
}
else
{
synchronized(MyLock.lockb)
{
System.out.println("elselockb");
synchronized(MyLock.locka)
{
System.out.println("elselocka");
}
}
}
}
}
class MyLock
{
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
『12.1』线程间通讯信
指的是,多个线程在操作同一个资源,但是操作的动作不同。
wait();
notify();
nitifyAll();
都是要对有锁的线程操作,所以要在同步中使用
为什么以上方法都是Objectj里的方法?
因为这些方法在操作同步中线程时,都必须要标识它们锁操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。不可以对不同锁中的线程进行唤醒。
也就是说:等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中
『12.2』线程间通讯-解决安全问题
『12.3』线程间通讯-等待唤醒机制
class Res
{
String name;
String sex;
boolean flag = false;
}
class Input implements Runnable
{
private Res r;
//Object obj = new Object();
Input(Resr)
{
this.r = r;
}
public void run()
{
int x=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
{
private Res r;
//Object obj = new Object();
Output(Resr)
{
this.r = r;
}
public void 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
{
public static void main(String[] args)
{
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
『12.4』线程间通信-代码优化
class Res
{
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name,String sex)//同步函数,让锁变得唯一,其中这里的锁是this
{
if(flag)
try{this.wait();}catch(Exceptione){}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out()
{
if(!flag)
try{this.wait();}catch(Exceptione){}
System.out.println(name+"......"+sex);
flag = false;
this.notify();
}
}
class Input implements Runnable
{
private Res r;
Input(Resr)
{
this.r = r;
}
public void run()
{
int x=0;
while(true)
{
if(x==0)
r.set("mike","man");
else
r.set("丽丽","女女女女女女");
x= (x+1)%2;
}
}
}
class Output implements Runnable
{
private Res r;
Output(Resr)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class InputOutputDemo2
{
public static void main(String[] args)
{
Res r = new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
『12.5』线程间通信-生产者消费者
当生产者与消费者均有多个的时候,需要循环判断flag标记,
因此这里就需要将if换成while语句进行循环判断;
而为了防止多个线程出现失去执行权的时候,这里就需要将全部线程唤醒,用notifyAll();
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag)//while循环可进行多次判断
try{this.wait();}catch(Exceptione){}
this.name = name+"---"+count++;
System.out.println(Thread.currentThread().getName()+".....生产者....."+this.name);
flag = true;
this.notifyAll();//既唤醒本方又唤醒对方
}
public synchronized void out()
{
while(!flag)
try{this.wait();}catch(Exceptione){}
System.out.println(Thread.currentThread().getName()+"........消费者........."+this.name);
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resourceres)
{
this.res = res;
}
public void run()
{
while(true)
{
res.set("商品");
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resourceres)
{
this.res = res;
}
public void run()
{
while(true)
{
res.out();
}
}
}
当生产者与消费者均有多个的时候,需要循环判断flag标记,因此这里就需要将if换成while语句进行循环判断;而为了防止多个线程出现失去执行权的时候,这里就需要将全部线程唤醒,用notifyAll();
对于多个生产者和消费者,为什么要定义while判断标记呢?
原因:让被唤醒的线程再一次判断标记。
为什么定义notifyAll?
因为需要唤醒对方线程,因为只用notify,容易出现只唤醒奔放线程的情况,导致程序中的所有线程都等待。
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法
的使用。 Lock替代synchronized,Condition替代Object
『12.6』线程间通信-生产者消费者JDK5.0升级版
将同步synchronized替换成现实lock操作。
将Object中的wait,notify,notifyAll替换成了Condition对象。
该对象可以通过Lock锁进行获取。
该示例中,实现了本方只唤醒对方的操作。
import java.util.concurrent.locks.*;//记得导入相应的包文件class ProducerConsumerDemo2
{
public static void main(String[] args)
{
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(String name)throws InterruptedException
{
lock.lock();
try
{
while(flag)
condition_pro.await();//await()会出现异常,需要在函数上抛出
this.name= name+"---"+count++;
System.out.println(Thread.currentThread().getName()+".....生产者....."+this.name);
flag= true;
condition_con.signal();
}
finally
{
lock.unlock();//释放锁的动作一定要执行。
}
}
public void out()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(Resourceres)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.set("商品");
}
catch(InterruptedException e)
{
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resourceres)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch(InterruptedException e)
{
}
}
}
}
『12.7』停止线程
1.定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即可。
2.使用interrupt(中断)方法。
该方法是结束线程的冻结状态,是线程回到运行状态中来。
注意:stop方法已经过时不再使用。
3.setDaemon :将该线程标记为守护线程或用户线程。
特点:该方法必须在启动线程钱调用;当正在运行的线程都是守护线程时,Java虚拟机退出。
4.stop方法已经过时,如何停止线程?
只有一种,run方法结束。开启多线程运行,运行代码通常是循环结构。
只要控制住循环体,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态。就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程回复到运行状态时,这时就需要对冻结进行清除。强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
Thread类提供了该方法:interrupt();
class StopThread implements Runnable
{
private boolean flag = true;
public synchronized void run()
{
while(flag)
{
try
{
wait();
}
catch(InterruptedException e)
{
System.out.println(Thread.currentThread().getName()+"....Exception");
flag = false;
}
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();
t2.start();
int num=0;
while(true)
{
if(num++==60)
{
//st.changeFlag();
//t1.interrupt();
//t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"...main...."+num);
}
System.out.println("over");
}
}『12.8』守护线程
1、关键字: void Daemon(boolean on) 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。且该方法必须在线程启动前调用。
守护线程属于“后台线程”,随着前台线程的退出而退出。
『12.9』join方法
join:等待该线程终止。
当A线程执行到了B线程的.join()方法是,A就会等待,等B线程都执行完,A才会执行。
join可以用来临时加入线程执行。
class Demo implements Runnable
{
public void run()
{
for(int x=0; x<60; x++)
{
System.out.println(Thread.currentThread().getName()+"...."+x);
}
}
}
class JoinDemo
{
public static void main(String[] args) throws Exception
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
t1.join();//让t1申请线程,只有当t1线程执行完后,才会执行其余的。
/*
当t1.join();在t1.start();下面一行时,t1线程会首先执行完后,t2和主线程就会交替执行完;
当t1.join();在t2.start();下面时,t1会与t2交替执行,直到t1线程执行完,没有执行完的t2线程就会与主线程交替执行。
*/
for(int x=0;x<70;x++)
{
System.out.println(Thread.currentThread().getName()+"....main");
}
System.out.println("over");
}
}
『12.10』优先级&yield方法
toString()、yield、setPriority
1.String toString() 返回该线程的字符串表现形式,包括线程名称、优先级和线程组。
2.static void yield() : 暂停当前正在执行的线程对象,并执行其他线程。
3.void setPriority(int newPriority) : 更改线程的优先级,其中,所有线程的默认优先级是5
数据固定的用常量(所有字母大写),数据共享有static静态,全局常量使用格式publicstatic final int MAX_PRIORITY