------- android培训、java培训、期待与您交流! ----------
Day12&13 多线程
一、多线程
1、概述
在一个进程中有多条执行路径。
A:进程 正在执行的程序,它代表着应用程序的执行区域。
B:线程 进程的执行路径.就是进程中一个负责程序执行的控制单元。
线程总是属于某个进程,进程中的多个线程共享进程的内存。
2、关于多线程的相关问题
u jvm的启动是多线程的还是单线程的,为什么?
多线程的,它至少启动了两个线程(主线程和垃圾回收线程),垃圾回收机制这个线程不可能是在程序执行完毕后才启动的,否则的话,我们的程序很容易出现内存溢出。
u 调用start方法和run方法区别?
调用start方法后,线程进入就绪状态,此时线程对象仅有执行资格,还没有执行权。当该线程对象抢到了执行权时,方可调用run方法,当run方法执行完毕后,线程死亡,不能复生。
u 线程的随机性的导致的原因?
在同一时刻,CPU只能执行一个程序,这个多线程的程序其实是CPU的高速切换造成的。
u 什么时候使用多线程?以及创建线程的目的?
多线程的引入是为了解决现实生活中的需求的,提高解决问题的效率。比如购买火车票这个动作。当许多对象要对同一有限资源进行操作的时候,我们就要使用多线程。
u 线程状态的内容和每一个状态的特点?
创建线程对象后,并对这个对象进行了一些初始化工作,当调用了start方法后,这个状态就有了执行资格,但是此时还未获得执行权,进入到了就绪状态。当抢到执行权后进入了运行状态,此时该线程既有了执行资格,又有了执行权,当调用了run方法后,此线程进入了死亡状态。当然你也可以调用stop方法令其强制死亡。在运行状态的时候,如果该线程调用了sleep或者wait等方法后,他会进入到阻塞状态,此时,这个对象释放了执行资格和执行权,当他的sleep或者wait时间结束后,亦或者调用了notify(notifyAll)方法,该线程被唤醒,又进入了就绪状态。如此周而复始。
3、创建线程的方式
A:继承Thread类
**步骤
a:定义一个类继承Thread类;
b:重写Thread类中的run()方法,run()方法里边是多线程要运行的代码;
c:创建定义的那个类的对象;
d:调用start()方法开启线程,执行run()方法里的内容。
另外可以通过Thread的getName()获取线程的名称。
public class ThreadDemo extends Thread{
public void run(){
for(int x=0;x<10;x++){
System.out.println("线程"+getName()+"正在运行:"+x);
}
}
public static void main(String[] args) {
ThreadDemo t1 = new ThreadDemo();
ThreadDemo t2 = new ThreadDemo();
t1.start();
t2.start();
}
}
**线程的生命周期 创建-----阻塞------运行------死亡
B:实现Runnable接口
**步骤
a:定义一个类实现Runnable接口;
b:重写Runnable接口中的run()方法,run()方法里是多线程要运行的代码;
c:创建定义的那个类的对象,并将其作为参数放置于Thread类的对象里。线程的 任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须 明确要运行的任务
d:调用start()方法启动线程,执行run()方法里的内容。
public class RunnableDemo implements Runnable {
public void run() {
for(int x = 0;x<10;x++){
System.out.println(Thread.currentThread().getName()+" "+x);
}
}
public static void main(String[] args) {
RunnableDemo rd = new RunnableDemo();
Thread t1 = new Thread(rd);
Thread t2 = new Thread(rd);
t1.start();
t2.start();
}
}
总结:两种方法的比较:实现Runnable接口,将线程的任务从线程的子类中分离出来的,进行了单独的封装,按照面向对象的思想将任务的封装成对象,避免了java中单继承的局限性。(创建线程第二种方式较好)static变量的生命周期过长。
u 对创建线程的第二种方式的设计的理解?
创建多线程的第二种方式是实现Runnable接口,并实现run()方法。由于第一种方式有资源不能共享的缺点,它需要创建很多的线程对象,而这些线程对象对同一资源又是独有的,如果设定为共享资源(设为静态),必将消耗太多内存资源,静态变量的生命周期过长。此外,如果一个线程对象类继承了其他类,此时他无法继承Thread类,也就不能使用第一种方式来创建线程。
4、多线程的安全问题
ü 产生的原因
A:线程访问的延迟
B:线程的随机性
ü 线程安全问题表现?原因?解决思想?解决具体的体现?
当一个线程对象在执行run方法的某一操作时,其他线程对象也进来了,并发的访问了临界资源,破环了原子操作,造成了数据的不一致。
多线程访问的延迟和线程的随机性产生了线程的安全问题。
当某一线程对象进入了run方法后,如果能做一个标记,说我已经在里面了,其他的哥们(线程对象)你就等着吧,等我操作完了,出来去掉标记你再进去吧。这样一来,原子操作就不会遭到破坏。
具体体现就是给那个原子操作加锁,使整个操作同步,不让其他线程对象破环,保证数据的一致性。
5、同步解决线程安全问题
A:同步代码块
同步代码块中的锁可以是任意对象,但是要在成员范围内定义.
在局部的话,会导致锁发生变化,因为你每次执行方法,都会重新创建一个对象.
**同步的前提
***至少要有两个线程
***同一个锁
**同步的好处 提高了安全性
**同步的弊端 效率较低
安全性和效率是你们一直要考虑的问题,而且很多时候,他们是对立的关系。
B:同步函数
n 同步函数的使用
用synchronized关键字修饰方法即可。
public synchronized void show(){
//需要同步的代码块
}
n 同步函数使用的锁
同步函数使用是this对象锁,静态同步函数的锁是(类名.class)
//采用双重判断完成,同步函数效率低,所以采用同步代码块完成单例的延迟加载
public class Singleton {
private Singleton(){}
private static Singleton s = null;
public static Singleton getInstance(){
if(s==null){
synchronized(Singleton.class){
if(s==null){
s = new Singleton();
}
}
}
return s;
}
}
n 同步的好处,弊端,前提?
它能解决多线程的安全问题,但是效率却下降了许多。前提有二:首先必须得有两个或者两个以上的线程,其次就是这些线程用的是同一把锁。
6、死锁
每个线程都不会释放自己拥有的锁标记,却阻塞在另外的线程所拥有的锁标记的对象锁池中,就会造成死锁现象。
n 产生原因
假如有A和B两个锁,在A锁中要使用B锁,在B锁中要是A锁,而他们都不想让,最终导致了死锁.
因为系统资源不足。
进程运行推进的顺序不合适。
资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则
就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
public class DeadLockDemo implements Runnable {
public boolean flag = false;
public void run() {
if (!flag) {
while (true) {
synchronized (this) {
synchronized (DeadLockDemo.class) {
System.out.println("true");
}
}
}
} else {
while (true) {
synchronized (DeadLockDemo.class) {
synchronized (this) {
System.out.println("false");
}
}
}
}
}
public static void main(String[] args) {
DeadLockDemo dld = new DeadLockDemo();
Thread t1 = new Thread(dld);
Thread t2 = new Thread(dld);
t1.start();
try{
Thread.sleep(10);
}catch(InterruptedException e){
}
dld.flag=true;
t2.start();
}
}
n 产生死锁的四个必要条件
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
只要上述条件之一不满足,就不会发生死锁。
n 如何解决?
不同时在A锁中用B锁,B锁中用A锁.
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和
解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确
定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态
的情况下占用资源。因此,对资源的分配要给予合理的规划。
7、线程状态图解
创建:使用start()开启线程。
运行:具备着执行资格,具备着执行权
冻结:释放执行权,同时释放执行资格
从运行到冻结的方式:
sleep(time),sleep(time)时间到,进入临时阻塞状态(具备着执行资格,但是不具备执行权,正在等待执行权)
wait()线程等待,notify()线程唤醒,进入临时阻塞状态。
消亡:
从运行到消亡的方式:
stop()中止线程;
run()方法结束,线程的任务结束。
二、线程间的通信
1、概述
以前呢,是同一个操作的多个线程来执行一个资源。
现在呢,需求变了,不同的操作,多个线程来完成对某个资源操作。
举例: 池水 放水的时候,加水。
一堆煤 一辆车把煤拉走,一辆车把煤拉到。
2、应用(等待唤醒机制)
需求:一个线程给学生赋值,另一个线程输出学生的内容。
class Student{
String name;
int age;
}
class Input implements Runnable{
private Student s;
Input(Student s){
this.s = s;
}
public void run(){
int x = 0;
while(true){
if(x==0){//对共享数据的操作分了多条语句来做的
s.name="aa";
s.age = 10;
}else{
s.name="bb";
s.age = 20;
}
}
}
}
class Output implements Runnable{
private Student s;
Output(Student s){
this.s = s;
}
public void run(){
while(true){
System.out.println(s.name+" "+s.age);
}
}
}
class StudentTest{
public static void main(String[] args){
Studnet s = new Student();
Input in = new Input(s);
Output out = new Output(s);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
到此我们发现程序出问题了,就是说aa有可能拿的是bb的年龄,bb有可能拿的是aa的年龄
为什么会出这个问题呢?
线程的随机性.
线程安全问题的产生:
1:共享数据
2:对共享数据的操作分了多条语句来做的。
安全问题产生后,我们怎么解决的?
1:同步代码块
2:同步函数
经过比较,我们发现同步代码块比较合适,而且在这个过程中,我们一直在找同步代码块中的对象用谁比较合适Object--this--Student s------>资源唯一,可以作为锁!
class Input implements Runnable{
private Student s;
Input(Student s){
this.s = s;
}
public void run(){
int x = 0;
while(true){
synchronized(s){
if(x==0){
s.name="aa";
s.age = 10;
}else{
s.name="bb";
s.age = 20;
}
}
}
}
}
class Output implements Runnable{
private Student s;
Output(Student s){
this.s = s;
}
public void run(){
while(true){
synchronized(s){
System.out.println(s.name+" "+s.age);
}
}
}
}
这个时候呢,我们已经完成了线程间的通信,但是效果不是很理想,我们想的是,有值我就输出,没有值,我就先产生一个值。找java,问他有没有提供这样的一个操作,发现它提供了
wait() 让线程等待
notify() 唤醒线程
notifyAll() 唤醒线程池中的所有线程
这个时候,我们是如何改进代码的呢?继续
class Student{
private String name;
private int age;
private boolean flag ;
}
class Input implements Runnable{
private Student s;
Input(Student s){
this.s = s;
}
public void run(){
int x = 0;
while(true){
synchronized(s){
if(s.flag){
try{s.wait();}catch (InterruptedException ie){}
}
if(x==0){
s.name="aa";
s.age = 10;
}else{
s.name="bb";
s.age = 20;
}
s.flag = true;
s.notify();
x = (x+1)%2;
}
}
}
}
class Output implements Runnable{
private Student s;
Output(Student s){
this.s = s;
}
public void run(){
while(true){
synchronized(s){
if(!s.flag){
try{s.wait();}catch (InterruptedException ie){}
}
System.out.println(s.name+" "+s.age);
s.flag = false;
s.notify();
}
}
}
}
wait(),notify(),notifyAll()
通过抓人游戏生动的描述了一下。
3、几个方法的使用
u 为什么wait(),notify(),notifyAll()都定义在Object类中?
A:这些方法存在于同步中。
B:使用这些方法时必须要标识所属的同步的锁。
C:锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
u wait()和sleep()的区别
A:对时间指定而言
wait():可以不指定时间。
sleep():必须指定时间。
B:对执行权和锁而言
wait():释放cpu执行权(资格也没了),释放锁。存储于线程池
sleep():释放cpu执行权,不释放锁(会自动醒)。
u 停止线程
A:通过控制循环
B:interrupt()方法
**stop已过时,被interrupt取代
u 守护线程 后台线程
你只要把一个线程设置为守护线程,那么主方法线程结束,不管什么情况,守护线程就结束.
举例:坦克大战
A:setDaemon(boolean flag)
u join:加入线程,把执行权抢夺,自己执行完毕,其他的线程才可能有机会执行.
u toString():线程名称,优先级,线程组(是由多个线程组成的,默认的线程组是main)
u yield():让本线程暂停执行,把执行权给其他线程.
u setPriority(int num):设置线程优先级
getPrinrity():获取线程优先级
线程的级别:1 - 10
默认级别为5.
4、生产者与消费者
解决了多个生产者与多个消费者的问题
public class ThreadTest{
public static void main(String[] args){
Resource r = new Resource();
Shengchan sc = new Shengchan(r);
Xiaofei xf = new Xiaofei(r);
Thread t1 = new Thread(sc);
Thread t2 = new Thread(sc);
Thread t3 = new Thread(sc);
Thread t4 = new Thread(xf);
Thread t5 = new Thread(xf);
t1.start();
t2.start();
t3.start();
t4.start();
t5.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 (InterruptedException e){
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + "...生产者...."+ this.name);
flag = true;
notifyAll();
}
public synchronized void out(){
while (!flag)
try {
this.wait();
} catch (InterruptedException e){
}
System.out.println(Thread.currentThread().getName()
+ ".........消费者........." + this.name);
flag = false;
notifyAll();
}
}
class Shengchan implements Runnable{
Resource r;
Shengchan(Resource r){
this.r = r;
}
public void run(){
while (true)
r.set("烤鸭");
}
}
class Xiaofei implements Runnable{
Resource r;
Xiaofei(Resource r){
this.r = r;
}
public void run(){
while (true)
r.out();
}
}
5、Lock&Condition接口
JDK1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了对象中,将隐式动作编程了显示动作。对多线程中的内部细节进行了升级改良。
它的出现替代了同步代码块或者同步函数。将同步的隐式锁操作编程了显示锁操作。 同事更为灵活。可以一个锁上加上多组监视器。
在java.util.concurrent.locks包中提供了一个Lock接口。Lock接口中提供了lock()获取锁,unlock()释放锁(通常需要定义finally代码块中)的操作。Lock接口更符合面向对象的思想,将锁这种事物封装成了对象。
public void run(){
synchronized(obj){//获取锁。
code...
//释放锁。
}
}
但是对于释放和获取锁的操作,都是隐式的。
JDK1.5后,就有了新的方法,将锁封装成了对象。因为释放锁和获取锁动作,锁自己最清楚。
锁对象的类型就是Lock接口。并提供了,显示的对锁的获取和释放的操作方法。
Lock lock;
public void run(){
try{
lock.lock();//获取锁。
code...throw ...
}finally{
lock.unlock();//释放锁.
}
}
Lock接口替代了synchronized
Condition替代了Object类中监视器方法 wait notify notifyAll。
将监视器方法单独封装成了Condition对象。而且一个锁上可以组合多组监视器对象。
实现了多生产者多消费者时,本方只唤醒对方中一个的操作,提高效率。
await()睡眠;signal(),signalAll()唤醒;将这些监视器方法单独进行封装,变成了Condition监视器对象。可以任意锁进行组合。
使用为一般是生产者是被消费者唤醒,消费者是被生产者唤醒。
6、Scanner 类(扩展)
接受从键盘输入的数据
(1)这是java中提供的类,在util包中
(2)用法基本格式:
Scanner sc = new Scanner(System.in);
通过sc对象就可以取得值了。
输入每个数据换行
int--int OK
String--String OK
int--String No
String--int OK
输入数据在一行上.就没有问题
Scanner类的使用 猜数字小游戏
import java.util.Scanner;
import java.util.Random;
class CaiShuTest {
public static void main(String[] args) {
System.out.println("请输入一个1~100之间的整数!");
Random r = new Random();
int b = r.nextInt(100);
guessNum(b);
}
public static void guessNum(int b){
Scanner sc = new Scanner(System.in);
while (true){
int a= sc.nextInt();
if (a<b){
System.out.println("你猜的数太小了");
System.out.println("请继续输入!");
}else if (a>b){
System.out.println("你猜的数太大了");
System.out.println("请继续输入!");
}else{
System.out.println("恭喜您猜对了");
break;
}
}
}
}
------- android培训、java培训、期待与您交流! ----------