java基础(五)

You got a dream, you gotta protect it. People can’t do something themselves,they wanna tell you you can’t do it.If you want something, go get it.

Day12&13 多线程
一、多线程
1、概述
在一个进程中有多条执行路径。
A:进程 正在执行的程序,它代表着应用程序的执行区域。
B:线程 进程的执行路径.就是进程中一个负责程序执行的控制单元。
线程总是属于某个进程,进程中的多个线程共享进程的内存。
2、关于多线程的相关问题
 jvm的启动是多线程的还是单线程的,为什么?
多线程的,它至少启动了两个线程(主线程和垃圾回收线程),垃圾回收机制这个线程不可能是在程序执行完毕后才启动的,否则的话,我们的程序很容易出现内存溢出。
 调用start方法和run方法区别?
调用start方法后,线程进入就绪状态,此时线程对象仅有执行资格,还没有执行权。当该线程对象抢到了执行权时,方可调用run方法,当run方法执行完毕后,线程死亡,不能复生。
 线程的随机性的导致的原因?
在同一时刻,CPU只能执行一个程序,这个多线程的程序其实是CPU的高速切换造成的。
 什么时候使用多线程?以及创建线程的目的?
多线程的引入是为了解决现实生活中的需求的,提高解决问题的效率。比如购买火车票这个动作。当许多对象要对同一有限资源进行操作的时候,我们就要使用多线程。
 线程状态的内容和每一个状态的特点?
创建线程对象后,并对这个对象进行了一些初始化工作,当调用了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 final String getName() 获得线程名称
public final void setName(String name) 设置线程名称
public static Thread currentThread() 返回当前执行线程对象
public final int getPriority() 返回线程对象优先级
public final void setPriority(int newPriority) 设置线程优先级
线程休眠
public static void sleep(long millis)
线程加入 等待该线程被终止
public final void join()
线程礼让 暂定当前线程,执行其他线程,多个线程和谐,但不保证一人一次
public static void yield()
后台线程 标记为守护线程
public final void setDaemon(boolean on)
中断线程
public final void stop()
public void interrupt()
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变量的生命周期过长。
 对创建线程的第二种方式的设计的理解?
创建多线程的第二种方式是实现Runnable接口,并实现run()方法。由于第一种方式有资源不能共享的缺点,它需要创建很多的线程对象,而这些线程对象对同一资源又是独有的,如果设定为共享资源(设为静态),必将消耗太多内存资源,静态变量的生命周期过长。此外,如果一个线程对象类继承了其他类,此时他无法继承Thread类,也就不能使用第一种方式来创建线程。

4、多线程的安全问题
 产生的原因
A:线程访问的延迟
B:线程的随机性
 线程安全问题表现?原因?解决思想?解决具体的体现?
当一个线程对象在执行run方法的某一操作时,其他线程对象也进来了,并发的访问了临界资源,破环了原子操作,造成了数据的不一致。
多线程访问的延迟和线程的随机性产生了线程的安全问题。
当某一线程对象进入了run方法后,如果能做一个标记,说我已经在里面了,其他的哥们(线程对象)你就等着吧,等我操作完了,出来去掉标记你再进去吧。这样一来,原子操作就不会遭到破坏。
具体体现就是给那个原子操作加锁,使整个操作同步,不让其他线程对象破环,保证数据的一致性。
5、同步解决线程安全问题
A:同步代码块
同步代码块中的锁可以是任意对象,但是要在成员范围内定义.
在局部的话,会导致锁发生变化,因为你每次执行方法,都会重新创建一个对象.
**同步的前提
*至少要有两个线程
*同一个锁
**同步的好处 提高了安全性
**同步的弊端 效率较低
安全性和效率是你们一直要考虑的问题,而且很多时候,他们是对立的关系。
B:同步函数
 同步函数的使用
用synchronized关键字修饰方法即可。
public synchronized void show(){
//需要同步的代码块
}
 同步函数使用的锁
同步函数使用是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;
}
}
 同步的好处,弊端,前提?
它能解决多线程的安全问题,但是效率却下降了许多。前提有二:首先必须得有两个或者两个以上的线程,其次就是这些线程用的是同一把锁。
6、死锁
每个线程都不会释放自己拥有的锁标记,却阻塞在另外的线程所拥有的锁标记的对象锁池中,就会造成死锁现象。
 产生原因
假如有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();
}

}
 产生死锁的四个必要条件
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
只要上述条件之一不满足,就不会发生死锁。
 如何解决?
不同时在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、几个方法的使用
 为什么wait(),notify(),notifyAll()都定义在Object类中?
A:这些方法存在于同步中。
B:使用这些方法时必须要标识所属的同步的锁。
C:锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
 wait()和sleep()的区别
A:对时间指定而言
wait():可以不指定时间。
sleep():必须指定时间。
B:对执行权和锁而言
wait():释放cpu执行权(资格也没了),释放锁。存储于线程池
sleep():释放cpu执行权,不释放锁(会自动醒)。
 停止线程
A:通过控制循环
B:interrupt()方法
**stop已过时,被interrupt取代
 守护线程 后台线程
你只要把一个线程设置为守护线程,那么主方法线程结束,不管什么情况,守护线程就结束.
举例:坦克大战
A:setDaemon(boolean flag)
 join:加入线程,把执行权抢夺,自己执行完毕,其他的线程才可能有机会执行.
 toString():线程名称,优先级,线程组(是由多个线程组成的,默认的线程组是main)
 yield():让本线程暂停执行,把执行权给其他线程.
 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监视器对象。可以任意锁进行组合。
使用为一般是生产者是被消费者唤醒,消费者是被生产者唤醒。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值