------- <a href="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="blank">android培训</a>、<a href="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="blank">java培训</a>、期待与您交流! ----------
1.异常处理机制:
(1)概念:就是程序在运行时出现的不正常情况。
(2)error与Exception的区别:
Error:对于严重的问题,Java通过Error类进行描述。一般不编写针对性的代码
对其进行处理。
Exception:对于非严重的问题,Java通过Exception来进行描述,可以使用针对
性的方法去处理。
结论:无论Error还是Exception都有一些共性的内容比如:不正常的内容,引发原因,有共同的父类Throwable。
(3)异常的处理:
Java提供了特有的语句进行处理
try
{
需要检测的代码;
}
catch(异常类 变量)
{
处理异常的代码;
}
finally
{
一定会执行的语句;
}
(4)对捕获到的异常处理的方法:
String getMessage(); 获取异常信息。
String toString(); 异常名称,异常信息,以字符串形式返回。
printStackTrace()异常名称,异常信息,异常出现的位置 jvm默认的异常处理机制。
1、声明异常时,建议声明更为具体的异常,这样处理的更为具体。
2、对方声明几个异常,就对应几个catch代码块如果多个异常的出现继承关系,父类异常catch放在最下面自定义异
常。
(5)自定义异常:
1、项目中会出现特有的问题,而这些问题别没有被java所描述并封装成对象
2、自定义异常必须手动抛出 用throw关键字。
3、当在函数内部出现了throw抛出异常,必须给出相应的处理动作要么在函
数内部try catch 要么在函数上声明让调用者处理 RuntimeException除外。
4、一般,函数内部出现异常,要在函数上声明。
5、自定义异常必须是自定义类继承Exception,因为异常体系有一个特点,异
常类和异常对象都具有可抛性,而可抛性是throwable体系特有的特点。
6、那么该如何定义异常信息呢?因为父类已经把异常信息的操作都完成了,
所以子类在构造函数时,通过super语句将信息传递给父类,就可以直接
通过getMessage获得自定义的异常信息。
代码例子:
需求:在这个运行程序中,除数不能是负数。
代码例子:
class FuShuException extends Exception
{
FuShuException(String msg)
{
super(msg);
}
}
class Demo
{
int div(int a,int b)throws FuShuException
{
if(b<0)
throw new FuShuException("出现了商为负数的情况-------by FuSu");
return a/b;
}
}
class ExceptionDemo
{
public static void main(String[] args)
{
Demo d =new Demo();
try
{
int x=d.div(4,-1);
System.out.println(x);
}
catch (FuShuException e)
{
System.out.println(e.toString());
}
}
}
(6)throws与throw的区别:
throws用在函数上
throw用在函数中
throws后面跟的是异常类,可以跟多个,用逗号分开
throw后面跟的是异常对象,。
(7)运行时异常RuntimeException
1、在函数内出现了这个异常,函数上可以不去声明,编译一样可以通过,如果在函数上声明了这个异常,调用者
可以不用处理,编译也可以通过。
2、运行时异常之所以可以不用在函数上声明,是因为不需要让调用者处理,当该异常发生,希望程序停止,对代
码进行修改。
3、在自定义异常时,如果这个异常的发生,无法再进行运算,可以让着个异常继承运行时异常。
4、异常分为两种:
4.1、编译时被检测的异常,在函数上标示出,让调用者去处理,问题可以处理,不需要修改代码。
4.2、编译时不被检测的异常(运行时异常),必须停下程序,修改代码。
(8)finally语句:
一定要执行的语句,比如数据库操作:断开连接 ,通常用于关闭资源。
(9)异常处理的格式:
1、try{} catch(){}
2、try{} catch(){} finally{}
3、try{} finally{}
代码例子:
try{} catch(){} 语句:
class Demo
{
//不需要声明因为内部已经解决
public void method()
{
try
{
thow new Exception();
}
catch(Excetion e)
{
}
}
}
try{} finally{} 语句:
class Demo
{
public void method()
{
try
{
thow new Exception();
}
finally
{
}
}
}
没有catch,代表异常没有被处理,如果这个异常是编译时异常,那么这个异常必须声明。
注意:try catch中的return语句要先检查finally是否在,然后才能返回。
(10)异常在子父类中的体现:
1、子类覆盖父类异常 时,如果父类抛出了异常,那么子类的覆盖方法,只能抛出父类的异常或者父类异常的子
类。
2、如果父类抛出多个异常,那么子类只能抛出父类抛出异常的子集。
3、如果父类或者接口没有抛出异常,那么子类在覆盖方法时也不能抛出异常如果子类发生了异常,只能try 不能
抛出。
2.String:
(1)String特点:
1、字符串是一个特殊的对象。
2、字符串一旦初始化就不可以被改变。
3、String不能被继承。
4、String str = “abc”与 String str1 = new String(“abc”);的区别:
“abc”是一个对象,前者只创建了一个对象,后者为两个对象。
(2)String里面的常用方法:
1、获取的方法:
//字符串中的包含的字符数,也就是字符串的长度。
int length();
//根据指定位置获取位置上的某个字符。
char charAt(int index);
//返回的是ch第一次在字符串中出现的位置。
int indexOf(int ch);
//返回的是从fromIndex指定位置开始,查找 ch在在字符串中出现的位置。
int indexOf(int ch,int fromIndex);
//返回指定子字符串在此字符串中第一次出现处的索引。
int indexOf(String str);
//从fromIndex指定位置开始,获取子字符串在字符串中出现的位置。
int indexOf(String str,int fromIndex);
//返回的是指定子字符串在此字符串中最右边出现的位置。
int lastIndexOf(String str);
//从指定位置开始获取该字符串中的一部分,包含结尾。
String substring(int index);
String substring(int index,int fromIndex);
2、判断
boolean contains(str); //字符串中是否包含某一个子串。
boolean isEmpty(); //字符串中是否有内容。
boolean startsWith(str); //字符串是否是以指定内容开头。
boolean endsWith(str); //字符串是否是以指定内容结尾。
boolean equalsIgnoreCase(); //判断内容是否相同,并忽略大小写。
boolean equals("String str") //判断字符串内容是否相同
3、转换
将字符数组转换成字符串:
构造函数:
String(char[]), // 将整个字符数组转成字符串
String(char[],offset,count); // 将数组中的一部分转成字符串
静态方法:
static String copyValueOf(char[]); //将整个字符数组转成字符串。
static String copyValueOf(char[],offset,count);//将数组中的一部分转成字符串
static String valueOf(char[]);
将字符串转换成字符数组:
char[] toCharArray();
将字节数组转换成字符串:
byte[] getBytes(); 将字符串转换成字节数组。
4、替换:
replace(oldchar,newchar);
返回的是一个新的字符串。如果要替换的字符不存在,返回的还是原字符串。
replace(str,restr);
5、切割
String[] split(regex);
6、子串
String substring(begin);
String substring(begin,end);
7、转换大小写,去除空格,比较
7.1、将字符串转成大写或小写。
String toUpperCase();
String toLowerCase();
7.2、将字符串两端的多个空格去除。
String trim();
7.3、对两个字符串进行自然顺序的比较。
int compareTo(String);
(3)练习1
模拟一个trim方法,去除字符串两端的空格。
思路:
1、判断字符串第一个位置是否是空格,如果是,就继续向后判断,直到不
是空格为止,结尾处判断空格也是一样。
2、当开始和结尾都判断到不是空格时,就是要获取的字符串。
3、必须保证开始的角标小于等于结尾的角标,否则没有意义。
代码例子:
public static void main(String[] args)
{
String s1 =" zhang hao ";
int start=0,end =s1.length()-1;
while(start<=end && s1.charAt(start)==' ')
start++;
while(start<=end && s1.charAt(end)==' ')
end--;
//因为包含头不包含尾,所以结 尾处需要加1。
System.out.println(s1.substring(start,end+1));
}
(4)练习2
将一个字符串进行整体反转。
思路:
1、将字符串变成字符数组
2、将数组进行反转
3、将数组变成字符串
4、只要将反转的部分的开始和结束位置作为参数传递即可。
代码例子:
class StringDemo1
{
public static void main(String[] args)
{
String s = "zhanghao haha";
System.out.println(reverseString(s));
}
public static String reverseString(String s)
{
char[] arr =s.toCharArray(); //字符串变字符数组
reverse(arr); //将数组进行反转
return new String(arr); //将数组变成字符串
}
private static void reverse(char[] arr)
{
for(int start=0, end=arr.length-1; start<end; start++,end--)
{
swap(arr,start,end);
}
private static void swap(char[] arr,int x,int y)
{
char temp =arr[x];
arr[x]=arr[y];
arr[y]=temp;
}
}
将字符串中的指定部分进行反转。
代码例子:
class StringDemo2
{
public static void main(String[] args)
{
String s = "zhanghao haha";
System.out.println(reverseString(s,2,7));
}
public static String reverseString(String s,int start,int end)
{
char[] arr =s.toCharArray();
reverse(arr,start,end);
return new String(arr);
//System.out.println(new String(arr));
}
private static void reverse(char[] arr,int x,int y)
{
for(int start=x, end=y-1; start<end; start++,end--)
swap(arr,start,end);
}
private static void swap(char[] arr,int x,int y)
{
char temp =arr[x];
arr[x]=arr[y];
arr[y]=temp;
}
}
(5)练习3:
获取一个字符串在另一个字符串中出现的次数。
“abkkcdkkefkkskk”
思路:
1、定义一个计数器。
2、获取KK第一次出现的位置。
3、从第一次出现的位置后,剩余的字符串中继续获取kk出现的位置,每获取一就计数一次。
4、当获取不到时,计数完成。
代码例子:
//第一种方式,比较繁琐,用的是indexOf(String x)和substring()。
class StringDemo1
{
public static void main(String[] args)
{
String str ="abkkcdkkefkkskk";
System.out.println(getSubCount_2(str,"kk"));
}
public static int getSubCount_2(String str,String key)
{
int count =0;
int index =0;
while((index=str.indexOf(key))!=-1)
{
str=str.substring(index+key.length());
count++;
}
return count;
}
}
//第二种方式,比较简单,用的是indexOf(String x,int y);
class StringDemo2
{
public static void main(String[] args)
{
String str ="abkkcdkkefkkskk";
System.out.println(getSubCount(str,"kk"));
}
public static int getSubCount(String str,String key)
{
int count =0;
int index =0;
while((index=str.indexOf(key,index))!=-1)
System.out.println("index"+index);
index=index+key.length();
count++;
}
return count;
}
(6)练习4:
获取俩个字符串中最大相同的子串。
思路:
1、将短的那个字串按照长度递减的方式获取到。
2、将每次获取到的子串去长度中判断是否包含,如果包含,说明已经找到了。
代码例子:
class StringDemo2
{
public static void main(String[] args)
{
String s1 ="abcwerthelloyuiodef";
String s2 ="cvhellobnm";
System.out.println(getSubString(s1,s2));
}
public static String getSubString(String s1,String s2)
{
for(int x =0; x<s2.length(); x++)
{
for(int y=0,z=s2.length()-x; z!= s2.length()+1; y++,z++)
{
String temp = s2.substring(y,z);
if(s1.contains(temp))
return temp;
}
}
return "";
}
}
3.StingBuffer
(1)StingBuffer的特点及作用:
字符串的组成原理就是通过该类实现的。
StingBuffer这个类被final修饰,不能被继承。
StingBuffer是线程同步的。
StingBuffer可以对字符串内容进行增删。
StingBuffer是一个容器。
很多方法与StingBuffer相同。
StingBuffer是可变长度的。
可以直接操作多个数据类型。
一次最终会通过toString方法变成字符串。
(2)StingBuffer里面的常用方法:
1、存储:StingBuffer append();
2、删除:StingBuffer delete(int start,int end);
StingBuffer deleteCharAt(int index);
3、获取:char charAt(int index);
int indexOf(String str);
int lastIndexOf(String str);
4、修改:StingBuffer replace(int start, int end, String str);
5、反转:StingBuffer reverse();
(3)什么时候使用缓冲区:
当数据类型不确定,数据的个数不确定,而且最后变成字符串的时候。
4.StingBuilder
(1)StringBuilder特点与作用:
一个可变的字符序列,此类提供一个与StingBuffer兼容的API,但不保证同步。
该类被设计用作StingBuffer类的一个简易的替换,用在字符串缓冲区被单个线程使用个时候,它比StingBuffer的效率要快。
(2)StingBuffer与StringBuilder的区别:
StingBuffer是线程同步的,安全的,需要判断锁,效率慢。
StringBuilder是线程不同步的,不安全,不需要判断锁,效率快。
5.正则表达式:
(1)概念:符合一定规则的表达式。
1、特点:用一些特定的符号来表示一些代码的操作。
2、作用:专门用于操作字符串。
3、好处:可以简化对字符串的复杂操作。
4、弊端:符号定义越多,正则越长,阅读性越差。
(2)正则表达式的构造摘要:
常用的:
1、字符类
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
[a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集)
[a-z&&[def]] d、e 或 f(交集)
[a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去)
[a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去)
2、预定义字符类:
. 任何字符(与行结束符可能匹配也可能不匹配)
\d 数字:[0-9]
\D 非数字: [^0-9]
\s 空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 单词字符:[a-zA-Z_0-9]
\W 非单词字符:[^\w]
3、边界匹配器 :
^ 行的开头
$ 行的结尾
\b 单词边界
\B 非单词边界
\A 输入的开头
\G 上一个匹配的结尾
\Z 输入的结尾,仅用于最后的结束符(如果有的话)
\z 输入的结尾
4、Greedy 数量词:
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好 n 次
X{n,} X,至少 n 次
X{n,m} X,至少 n 次,但是不超过 m 次
(3)具体的操作功能:
1、匹配: 用的是String中的matches方法。
用规则匹配整个字符串,只有有一处不符合规则,就匹配结束,
返回false,返回的是真假。
2、切割:用的是String 中的split方法。
返回的是规则以外的字符串数组。
3、替换:用的是String中的replaceAll方法。
返回的是一个替换后的字符串,replaceAll里面需要传两个参数,
一个替换的规则,一个就是要替换的字符串。
4、获取:将字符串中符合规则的子串取出。
步骤:
1、将正则表达式封装成对象。
2、让正则对象和要操作的字符串想关联。
3、关联后获取正则匹配引擎。
4、通过引擎对符合规则的字符串进行操作,比如取出。
代码例子:
import java.util.regex.*;
class RegexDemo
{
public static void main(String[] args)
{
getDemo();
}
public static void getDemo()
{
String str = "ming tian jiu yao fang jia le ,da jia。";
System.out.println(str);
String reg = "\\b[a-z]{4}\\b";
//将规则封装成对象。
Pattern p = Pattern.compile(reg);
//让正则对象和要作用的字符串相关联。获取匹配器对象。
Matcher m = p.matcher(str);
//System.out.println(m.matches());
//System.out.println("matches:"+m.matches());
while(m.find())
{
System.out.println(m.group());
System.out.println(m.start()+"...."+m.end());
}
}
}
6.多线程:
(1)线程和进程概念:
1、进程:正在进行中的程序。
2、线程:进程中的多条执行路径。
(2)进程与线程的关系:
1、线程控制着进程的执行进度。
2、一个进程中至少有一个线程,就是主函数。
(3)多线程的特点:
随机性,每个线程都获取CPU的执行权,到底谁执行,执行多久由CPU来决定。多个线程之间交 替执行。
(4)启动线程:
启动线程的唯一方法是:线程类对象.start();
用于开启线程并执行线程中的run();
线程类的run()方法,用于封装要运行的代码。
(5)创建的线程的两种方式:
1、定义线程的第一种方法:
步骤:
1.1、定义一个类继承Thread类。
1.2、复写Thread类中的run方法,用于将自定义代码存储到run方法中,
让线程运行。
1.3、调用线程的start方法来启动线程。
代码例子:
class Demo1 extends Thread
{
//覆盖run方法。
public void run()
{
System.out.println("demo1 run");
}
}
class ThreadDemo
{
public static void main(String[] args)
{
//定义子类对象。
Demo1 d=new Demo1();
//启动线程。
t.start();
}
}
2、定义线程的第二种方法:
2.1、定义一个类实现Runnable接口。
2.2、覆盖Runnable中的run方法。
2.3、通过Thread类建立线程对象。
2.4、将Runnable接口的子类对象作为实际参数传递到Thread类的构造函数中。
2.5、调用Thread类的start方法启动线程,调用Runnable接口的子类的run方
法。
代码例子:
class Demo2 implements Runnable
{
//覆盖Runnable中的run方法
public void run()
{
System.out.println("demo2 run");
}
}
class RunnableDemo
{
public static void main(String[] args)
{
//定义一个Runnable接口的子类对象。
Demo2 d=new Demo2();
Thread t = new Thread(d);
//调用Thread类中的start方法启动线程,并调用Runnable接口中的run
方法。*/
t.start();
}
}
3、线程的两种创建方式有什么区别吗?
3.1、继承Thread类,线程代码存方法在Thread类的run方法中。
实现Runnable接口,线程代码存方法在Runnable接口的run方法中。
3.2、实现方式的好处:
避免了单继承的局限性,在定义线程时,使用实现方式能够对外提供
功能,而且还能被多线程操作。
代码例子:
售票:
需求:简单的卖票程序,多个窗口同时买票。
class Ticket implements Runnable
{
//定义100张票。
private int ticket=100;
public void run()
{
while(true)
{
if(ticket>0)
{
//获取线程的名称,以及各个线程买票的情况。
System.out.println(Thread.currentThread().getName()+"..."+ticket--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket tic=new Ticket();
//创建4个线程。
Thread t1 = new Thread(tic);
Thread t2 = new Thread(tic);
Thread t3 = new Thread(tic);
Thread t4 = new Thread(tic);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
(6)多线程安全问题:
问题出现的状况:
当多个线程在操作同一个共享数据时,一个线程的多条语句中只执行了一
部分,还没有执行完,另一条线程就进来执行了,导致共享数据的错误。
解决办法:
对于有多条操作共享数据的语句,只能让一个线程全部执行完,在执行的过程中,其它的线程不能参与执行。
Java对于多线程的安全问题提供了专业的解决方法。
1、同步代码块来实现安全:
synchronized(对象)
{
需要被同步的语句;
}
1.1、那么那些数据需要被同步呢?
看那些语句在操作共享数据,就同步这些语句。
1.2、里面存放的对象如同锁,没有锁的线程就算取得了CPU的执行权也不能执行,线程加锁带来的弊端:要有锁对象,所以耗资源,要判断锁,所以效率稍减。
2、同步函数
public synchronized void method(Type args)
{
需要被同步的语句;
}
2.1、同步非静态函数用的锁是this。
2.2、如果同步静态函数:所用的锁不是this,因为静态方法中不能出现this,
用的是 类名.class是Class类型对象。
3、如果一个程序中有安全问题,使用同步时应注意:
3.1、明确多线程运行代码(一般为run方法里调用的语句,以及其附带语句(调
用了其它的方法)有哪些
3.2、明确共享数据。
3、明确多线程运行代码中哪些语句是操作共享数据的。
4、同步的前提:
1、必须有两个或两个以上的线程。
2、必须是多个线程使用同一把锁。
解释:也就是保证同步中只能有一个线程在运行。
(7)单例设计模式
1、单例设计模式的作用:
解决一个类在内存中只用一个对象。
2、 饿汉式步骤:
2.1、将构造函数私有化。
2.2、在类中创建一个私有并静态的本类对象。
2.3、对外提供一个方法可以获取到该对象。
代码例子:
class Single1
{
//将构造函数私有化。
private Single1(){}
//在类中创建一个私有并静态的本类对象。
private static Single1 s1 =new Single1();
//对外提供一个方法可以获取到该对象。
public static Single1 getInstance()
{
return s1;
}
}
class Single1Demo
{
public static void main(String[] args)
{
//用类名调用获取对象的方法。
Single1 ss1 =Single1.getInstance();
}
}
3、懒汉式步骤:
3.1、私有并静态一个对象的引用。
3.2、将构造函数私有化。
3.3、对外提供一个方法,加入同步代码块,当方法被调用时才会初始化。
代码例子:
class Single2
{
private static Single2 s2 = null;
private Single2 (){}
public static Single2 getInstance()
{
if(s==null)
{
synchronized(Single2.class)
{
if(s2==null)
{
s2 = new Single();
}
}
}
return s2;
}
public static void main(String[] args)
{
Single2 ss2 = Single2.getInstance();
}
}
解释:
当a线程进来的时候,判断s2==null,满足条件,就获取了锁,但是它获取锁以后,执行权被b线程抢走了,它就停在这了,获取执行权的b线程进来判断s2==null,满足条件,往下执行的时候发现已经上锁了,所以它没有进去同步代码块就停下了,并且释放了执行权这时a线程获取了执行权,继续往下执行,判断为空,就创建了一个对象,并且释放了锁,于是b线程又获得了执行权,它进来同步一看,s2不等于空了,于是它就释放了执行权,这时又有一条线程c进来了,它判断s2不等于空,就不用再判断锁了。
4、饿汉式和懒汉式有什么区别?
4.1、懒汉式特点:实例的延时加载。
4.2、懒汉式在多线程访问时存在安全问题:
获取实例的方法中有多条语句操作共享资源,所以用同步代码块和同步函数都能解决此问题,但效率稍低;可以用双重判断来减少判断锁的次数。
4.3、懒汉式加同步时使用的锁是哪个?
这个锁是该类所属的字节码文件对象。
(8)死锁:
1、死锁的产生:同步中嵌套同步,而使用的锁不一样。
代码例子:
class DeadLock
{
public static void main(String[] args)
{
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
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("if...lockb");
}
}
}
else
{
synchronized(MyLock.lockb)
{
System.out.println("else...lockb");
synchronized(MyLock.locka)
{
System.out.println("else...locka");
}
}
}
}
}
class MyLock
{
static Object locka = new Object();
static Object lockb = new Object();
}
结果是:一个拿着a锁不放,一个拿着b锁不放,拿着a锁的进不了b锁,
拿着b锁的进不了a锁。
(9)多线程通信
多线程通信:即多个线程操作同一个资源,但操作的动作不同。
1、等待唤醒机制:
原理:输入线程如果获取到了CPU的执行权,它存了一个 zhangsan,man,它存的
时候别的线程进不来,它存完出了同步以后,输入线程和输出线程都有可
能抢到CPU的执行权,所以说输入线程可能有抢到了执行权,所以它又把
“丽丽”,“女女女女”存进去了,然后不断的抢,不断的存,不段的覆盖
到某一时刻时,输出线程抢到了执行权,然后开始打印,但是它不可能只
抢到一次,所以就不断的打印同一个名字和性别,所以打印的时候是一大
片相同的,而不是间隔开来的。但是我们想要的是输入一个就跟着输出一
个,所以就用到了等待唤醒机制,那么就是定义一个标记,当输入线程判
里面没有名字时,也就是为false,然后就往里面存一个名字,存完以后输
入线程会把false改为true,此时输入线程还持有执行权,所以它进来以判
断标记为true就不存了,然后它就等待了,等待了也就是冻结了,也就是
放弃了执行资格,然后输出线程就获得了执行权,那么它进去一判断标记
为true,说明有数据,然后就打印一次,打印完以后吧标记改为false,此
输出线程仍持有执行权,当它再去判断的时候发现标记为false,所以就等
待了,也就是放弃了执行资格,等待之前它把输入线程唤醒了,然后输入
线程继续存数据。
代码例子:
public class ThreadCommunication
{
public static void main(String[] args)
{
Res r = new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
//共享资源
class Res
{
private String name;
private String sex;
private boolean flag = false;
//设置方法
public synchronized void set(String name, String sex)
{
//flag=true表示设置过的还未打印,有数据则等待
if(flag)
try
{
this.wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
//打印方法
public synchronized void out()
{
if(!flag)
try
{
this.wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println(name + ".........." + sex);
flag = false;
this.notify();
}
}
//输入线程
class Input implements Runnable
{
private Res r;
Input(Res r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
if(x==0)
{
r.set("zhangsan", "man");
}
else
{
r.set("丽丽", "女女女女");
}
x = (x+1)%2;//控制x在0和1之间不断交替,从而让设置的内容不同。
}
}
}
//输出线程
class Output implements Runnable
{
private Res r;
Output(Res r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
2、总结:
2.1、在线程池里等待的线程,往往先唤醒的是第一个。
wait()、notify()、notifyAll()。当前线程必须拥有此对象监视器(即锁),锁只
有在同步中才有。
2.2、那么上述方法为何被定义在Object类中?
因为上述方法由锁调用,锁可以是任意对象,所以定义在任意对象的父类
Object中。
2.3、wait()和sleep()有何区别:
wait()释放资源,释放锁。
sleep()释放资源,不释放锁。
等待和唤醒的必须是同一个锁。而锁可以是任意对象,所以定义在Object
中。
(10)Lock接口和Condition接口
1、JDK1.5中提供了替代同步中隐式锁的synchronized为显式锁方式Lock接口和
Condition接口将Object中的wait(),notify(),notifyAll()替换成了Condition对象
的avait(),signal(),signalAll()方法,Condition对象可以由Lock获取,实现了本方只
会唤醒对方的操作。
2、生产者消费者问题替代方案:
1.5版本时,提供了显式的锁机制,以及显式的锁对象上的等待唤醒操作机制
一个Lock锁,对应了多个Condition。
代码例子:
public class ProducerConsumerJDK5
{
public static void main(String[] args)
{
Resource res = new Resource();
Producer p1 = new Producer(res);
Consumer c1 = new Consumer(res);
Thread t1 = new Thread(p1);
Thread t2 = new Thread(p1);
Thread t3 = new Thread(c1);
Thread t4 = new Thread(c1);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource
{
private String name;
private int count =1;
boolean flag = false;
Lock lock = new ReentrantLock();
Condition condition_pro = lock.newCondition();
Condition condition_con = lock.newCondition();
public void set(String name) throws Exception
{
lock.lock();
try
{
/*用while循环判断生产标记,让被唤醒的线程再次判断标记,确保
不会出现生产一个而消费两个的情况*/
while(flag)
condition_pro.await();
this.name = name + "..." + count++;
//让生产进度变慢,消费进度也随之变慢
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() +" 生产 "+
this.name);
//生产后将生产标记设为true,意为已经生产一个了,快打印。
flag = true;
condition_con.signal();//激活对方线程。
}
finally
{
lock.unlock();//释放锁的动作一定要执行
}
}
public void out() throws Exception
{
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;
public Producer(Resource res)
{
this.res = res;
}
public void run()
{
//不停的生产
while(true)
{
try
{
res.set("包子");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
public Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
3、停止线程:
3.1、直接结束run()方法。
原理:开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可
以让run方法结束。
特殊情况:当线程处于冻结状态的时候,就不会读取到标记,那么这时线程
就不会结束。冻结状态不是停止线程,是将线程挂起。
3.2、使用interrupt()方法。
强制清除其冻结状态,然后让其恢复到运行状态,然后就能读标记了。
4、守护线程:
4.1、setDaemon(true):设置线程为守护线程(也可将其称为后台线程)。
当正在运行的线程都是守护线程时,jvm即退出,该方法必须在启动线程前调用。
4.2、前台线程和后台线程:在执行过程中没有区别(同样抢占cpu的执行权),
只在结束时有区别,守护线程依赖于主线程(前台线程),当主线程结束后,
所有守护线程自动结束。
5、等待该现场终止:
方法join(),临时加入线程,当A线程执行到了B线程的B.join()方法,A线程就等待,直到B线程结束后,A线程才从冻结状态回到运行状态。
一般使用方式:当满足一定的条件时,让某一线程加入进来。
6、优先级:
所有的线程默认的优先级都是5,线程最高优先级是10,线程最低优先级是1。
这里的main是线程组名(Thread-1由main开启),线程默认的优先级是5,ThreadGroup,可以让程序员自己创建线程组。(几乎用不到)
setPriority(Thread.MAX_PRIORITY)设置优先级最高
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
如果有两个线程操作同一段代码,代码中加入Thread.yield(),运行的效果类似是a线程执行一次b线程执行一次,均匀交替的执行。
------- <a href="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="blank">android培训</a>、<a href="http://www.itheima.com" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="blank">java培训</a>、期待与您交流! ----------