A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© java木 中级黑马   /  2014-5-30 01:39  /  2445 人查看  /  18 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

            ---------------------- <a target="blank">ASP.Net+Unity开发</a>、<a target="blank">.Net培训</a>、期待与您交流! ----------------------
      
   java 多线程学习总结
多线程技术大大提高了软件程序的效率,尤其是当程序需要与硬件交互时。现在的软件产品越来越重视用户体验,而软件产品的执行效率直接决定了用户体验的好坏。
对多线程的学习需要了解以下几个概念。
进程:
   是一个正在执行的程序,每个进程都有执行顺序。该顺序是一个执行路径或一个控制单元。
线程:就是进程中的控制单元,线程控制着进程的执行。一个进程至少包含一个线程。
java开启线程的方式有两种。(准确的说java创建线程的的方式只有一种,就是创建 Thread类或其子类的对象。)
方式一:1
1.继承Thread类,复写其run()方法。将要开启线程执行的代码写入run()方法中。
2.调用start()方法开启线程。
具体代码如下:
//创建类,覆盖run方法
class MyThread extends Thread{
run(){
while(true){
  System.out.println("Hello Thread");
}
}
}
//启动线程
public static void main(String[]args){
MyThread my=new MyThread();
my.start();
}
第一种开启线程的思想是:根据java的面向对象思想。如果希望自己的类能成为一个线程并被JVM作为一个单独的线程独立执行的话,就应该加入该体系中,所以要继承Thread类。覆盖run方法是因为,run方法是用来存放线程要执行的代码,重写run方法就可以让我们创建的线程按我们要求的方式执行。
第二种方式:
1.定义类实现Runnable 接口。
2.覆盖run方法将需要运行的代码存放进去。
3.通过Thread类创建线程对象。
4.将实现Runnable接口的对象作为实际参数传入Thread的构造函数中。
5.调用Thread对象的start方法开启线程。
具体代码如下
//定义类实现Runnable接口,并复写run方法。
public class MyThread implements Runnable{
public void run(){
  while true(){
   System.out.println("Hello Thread")
  }
}
}
//开启线程
public static void main(String[]args){
      MyThread myt=new MyThread();
  Thread t=new Thread (myt);
  t.start();
}
第二种方式并不像第一种,我们要继承Thread类,创建一个线程。我们用第二中方式开启一个线程,只需要定义一个类实现Runnable 接口。让该类具备可以被作为一个独立的执行顺序,被线程运行的功能。把他的对象传入Thread的构造函数中。调用Thread对象的start()就可开启线程。
java 为什么要如此设计多线程的两种开启方式呢?
弄清这个问题,我们可以从这两种开启多线程方式的区别入手。
java两种多线程开启方式的比较:
1.如果一个类通过继承Thread类来开启线程,它就不能有别的父类。这就受到了java单继承的限制。而第二种方式,只需实现Runnable接口,仍然可以继承别的类,这可要灵活多了。
2.线程执行的代码不同,第一种方式代码是存在 Thread 类的run方法中。而第二种方法代码是存在实现Runnable接口类的run方法中。
3.从程序设计角度来说。如果我们的需求是创建一个多线程,并需要修改Thread类的一些基本方法时,就需要用第一种方式。而我们只是需要开启多线程时,只需要使用第二中即可。
所以,以后选择这两种方式,只需根据需求而定。不过大多时候我们的需求都只是需要开启线程。那么第二种开启线程方式是最常用的的。java也同样建议我们用第二种方式。
避免了单继承的局限性,可以使线程共享资源,在一定程度上可以说节省了内存。
线程的运行状态(生命周期)
                   wait()或
       start()            sleep(time)|
被创建状态----->运行状态----->冻结状态
            stop( ) |    <----
      或run()中的代码执行完毕 |     sleep时间到或
       |   notify()
            消亡状态
           
上图还省略了每个线程都会处于的一种状态,临时状态。
临时状态是指每个线程在处于运行状态时,具有了执行权,但没有被CPU执行,既没有运行权,我们亦可以将其称为阻塞状态。(多线程同时运行的原理是CPU瞬时间在各个线程间来回切换运行,所以我们感觉上是各个线程同时运行。qq和酷狗音乐同时玩着。)

线程安全问题:
当多线程操作共享数据时,就会出现多线程安全问题。请看以下示例代码:
package com.heima.duoxiancheng;
/*
* 本文件将用代码方式模仿现实中车站多窗口卖票情景
* 并描述多线程安全问题产生原因,和解决方法。
*/
public class DuoXianChengAnQuan {
public static void main(String[] args) {
  // TODO Auto-generated method stub
  TicketThread th=new TicketThread();
  Thread t1=new Thread(th);
  Thread t2=new Thread(th);
  Thread t3=new Thread(th);
  Thread t4=new Thread(th);
  t1.start();
  t2.start();
  t3.start();
  t4.start();
}
}
class TicketThread implements Runnable {
int ticket = 1000;
Object obj=new Object();
public void run() {
  while (true) {
    synchronized (obj) {
     /*synchronized同步语句块中是多条句
       操作线程共享数据。会有发生多线程安全问题的可能。假设在ticket=1时,线程一经过if语句进入这里,由于cpu在切换到别的线程,它进入阻塞状态停在这里,现程二又进入了,然后线程一又被cpu执行,将ticket变为0,线程二执行,注意这时它就会将ticket变为-1.这就是对ticket的错误操作(ticket是大于0的)。这时synchronized语句块的作用就体现出来了。它将会将这些语句锁起来,在已有一个线程进来时,直到该线程执行完所有语句,把锁打开时,synchronized语句块才允许另一个线程进来。这就从而解决了多线程安全问题。
      */
     if (ticket > 0) {
     try {
      Thread.sleep(10);//该方法是       为了让多线程安全问题更容易产生。
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
     ticket--;
     System.out.println("窗口" + Thread.currentThread().getName()
       + "卖出第" + ticket + "张火车票");
    }
   
   }
  }
}
}
从以上代码中可以看出,多线程安全问题的原因是,有多条语句操作多线程共享数据,由于多线程在运行时CPU的随机切换线程执行,会导致一个线程没有执行完那些语句,另外的线程就又参与进来执行,导致数据处理的错误,引发了多线程安全问题。
多线程安全问题的的解决:
在知道多线程安全问题引发的原因,那解决他的原理也就一目了然了。
对多条线程共享数据的操作语句,要保证只能让一个线程执行完,再让另一个线程执行。在执行过程中不能让让别的线程参合进来。java中提供了专业的解决办法synchoized的语句块和synchoized函数。
格式如下
synchoized(对象名){
操作共享数据的代码
}
synchoized void play(){
操作共享数据的代码
}


评分

参与人数 1技术分 +1 收起 理由
李小然 + 1

查看全部评分

18 个回复

正序浏览
兄弟,在黑马论坛不用加    ---------------------- <a target="blank">ASP.Net+Unity开发</a>、<a target="blank">.Net培训</a>、期待与您交流! ----------------------  这个是在博客中需要加的
回复 使用道具 举报
支持一下楼主啊!!
回复 使用道具 举报
我练习的时候都是用的同步锁,没用synchronized,感觉搞懂他的原理就可以了
回复 使用道具 举报
咱们是不是应该抛弃Synchronized,使用ReentrantLock啊,不得跟着软件更新走啊
回复 使用道具 举报
学习学习
回复 使用道具 举报
复习一遍
回复 使用道具 举报
1、必须要有两个或者两个以上的线程。
2、必须是多个线程使用同一个锁。
回复 使用道具 举报
哇 楼主好有心 借用了
回复 使用道具 举报
日 这都给技术分   这根本就跟无异于水贴啊
回复 使用道具 举报
赞一个,进来学习下
回复 使用道具 举报
学习学习了
回复 使用道具 举报
自己写的这只是一部分
回复 使用道具 举报
这是你自己写的?还是copy的
回复 使用道具 举报
谢谢了。。。。。。
回复 使用道具 举报
我今天刚看完多线程 ....复习下
回复 使用道具 举报
我也进来复习了哥们 不过我认为你应该在加上

同步的前提:
1、必须要有两个或者两个以上的线程。
2、必须是多个线程使用同一个锁。

同步函数的锁是this
静态同步函数的锁是Class对象

单例设计模式之懒汉式:
class Single
        {
                private Single(){}
                private static Single single = null;
                public  staticSingle getInstance()
                        {
                                if(single==null)
                                        {//避免每次都判断锁,只有当对象为null的情况下才判断
                                                synchronized(Single.class)
                                                {
                                                        if(single==null)/*如果一个线程跑到第一个if后死了,另一个线程进来创建了对象释放了锁,然后那个线程醒了,进来后还要判断*/
                                                        single =new Single();
                                                }
                                        }
                                return single;
                        }
        }

wait() notify() notifyAll()都是用在同步中,因为要对持有监视器(锁)的线程操作,所以使用在同步中,因只有同步中有锁。
为什么这些操作线程的方法要定义在object类中呢?
答:因为这些方法在操作同步中的线程时,都必须要标示他们所操作线程只有的锁。
只有同一个锁上的被等待线程可以被同一个锁上的线程被notify唤醒。同时不可以对不同所中的线程进行唤醒。
也就是说等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义在object中。

Notify():往往唤醒线程池中的第一个,所以可能唤醒本方导致数据错乱全部等待。可以用notifyall全部唤醒。
当出现多个生产者消费者同时要做这件事情的时候要用循环while和notifyall,(通用1.while判断标记,2.把对方唤醒)当只有一个生产者和消费者时可以用if

停止线程:Stop方法已过时,如果停止线程呢:只有一种,run()方法结束
原理:开启多线程运行,运行代码通常是循环。只要控制循环,皆可以让run方法结束即线程结束。

守护线程:线程对象.setDaemon(true);将该线程标记为守护线程或用户线程。
当正在运行的线程都是守护线程时,Java虚拟机退出,程序结束。

多线程(Join方法)
特点:当A线程执行到了B线程的.join()方法时,那么A线程就会等待B线程都执行完A才会执行。它可以用于临时加入线程执行


回复 使用道具 举报
进来学习了。
回复 使用道具 举报
貌似你的进度和我差不多呢,兄弟
回复 使用道具 举报 1 0
您需要登录后才可以回帖 登录 | 加入黑马