黑马程序员技术交流社区

标题: 多线程 ,生产者消费者细节 [打印本页]

作者: ice24    时间: 2015-6-24 12:15
标题: 多线程 ,生产者消费者细节
创建线程的第一种方式
步骤:1,创建一个线程子类继承Thread类
      2,在创建的子类中重写run方法
      3,实例化子类对象,并调用start()方法

为什么要创建一个新的线程子类并继承Thread类?
直接使用Thread去创建线程其实也是可以的,但是Thread类是java语言在设计时就已经存在的类了,类中相关功能都已经书写好了,而我们使用多线程想要执行的是自己书写的任务代码(而Thread类中任务代码却已经固定写好了,不能使用Thread中的代码),只能创建自己的线程类并继承Thread类重写run方法

为什么继承了Thread类后,一定要重写run方法?
当线程开启之后(调用start方法),JVM就会自动的把run方法加载到新开辟的运行通道中执行(java语言底层已经把JVM自动调用任务代码的功能固定死了)

为什么不直接去调用run方法,而是调用start方法?
start()方法是用来开辟一个新的线程通道。
如果没有执行start方法而是直接去执行run()方法,进程中就没有新的运行通道,还是单线程在执行

继承Thread类的弊端:
1、类和类之间只能单一继承,当继承Thread类后就不能再继承其它的类了(单继承)
2、Thread类属于线程类,按照面向对象的方式来讲,线程类就应该是操作线程功能的,但是Thread类除了可以操作线程的相关功能外,还可以执行任务代码。这种情况会让Thread类的耦合度变高(耦合高问题)
解决方案:不论是解决单一问题,还是解决耦合高的问题,在java中都是使用接口(Runnable)

创建线程第二种方式的步骤:
1,创建一个实现Runnable接口的子类
2,子类中重写Runnable接口中的run方法
3,实例化子类对象,并把子类对象作为参数传递给Thread对象
4,调用Thread对象中的start()方法

Runnable接口和Thread类的区别:
Thread类属于线程类,可以用来直接操作多线程。
Runnable接口只把任务代码单独的抽离出来单独实现,用来描述任务功能。想要执行任务代码,必须借助Thread类或其子类

线程默认命名格式:Thread-x      x是一个数字,自动增长。从0开始
注:主线程的名称是:main

多线程安全问题的产生:多个线程同时都操作相同的一个数据,造成数据的不安全性
解决多线程安全问题的方案:在操作共享数据时,添加同步代码块
同步的关键字:synchronized

同步代码块的格式:
synchronized(任意对象) //任意对象也称为对象锁
  {
         //线程要操作的共享数据代码
  }
同步代码块使用注意事项:
同步代码块是添加在任务代码中操作共享数据的位置上;
检查对象锁是否使用的是同一个对象锁;

同步方法格式(在方法上添加一个同步关键字)  
修饰符  synchronized  返回值类型  函数名(参数列表)
{

}
注:同步方法分为静态同步方法和非静态同步方法
小结:当同步代码块想要和非静态同步方法保持共享数据一致(不出现线程安全问题),需要在同步代码块中使用同步方法上的锁:this对象

单例模式(原子模式、单态模式):
解决的问题:保证所创建出来的对象是唯一的。
实现步骤:1、私有化构造方法;
          2、在类的内部书写属于自己的本类对象
          3、对外提供一个方法,可以让其它程序访问属于本类的对象

小结:同步代码块想要和静态同步方法使用同一个对象锁,需要使用:类名.class

多个线程存储同一个相同元素或删除同一个相同元素
解决方案:使用Collections工具类中的方法

同步的好处:解决了线程不安全问题(保证多个线程在操作共享数据不会出现不安全数据)
同步的弊端:降低程序的执行效率(执行同步时每次都要判断是否有对象锁存在)
生产者消费者思路:
生产者在生产完商品后,通知消费者可以来消费了。当生产者生产完商品后,就要处于等待状态(等待消费者消费完商品后,通知生产者继续生产)
消费者在消费完商品后,通知生产者可以继续生产商品了。当消费者消费完商品后,也要处于等待状态(等待生产者生产完商品后,通知消费者可以继续消费)

对象锁
任意对象锁都是隐式的获取锁,释放锁;
任意对象锁都具有等待唤醒机制;
一个线程只能绑定一个对象锁;
常用方法格式
Object lock=new Object();创建对象锁
synchronized(lock){ }
lock.notifyAll();唤醒处于等待状态的所有线程
lock.notify(); 唤醒处于等待状态的线程
lock.wait(); 让xxx线程处于等待状态
注:wait()和notify()是属于对象锁的

Lock锁
至JDK1.5后,lock锁把等待唤醒机制从对象锁上分开,1个lock锁可以具有多个等待唤醒机制;
多个线程可以绑定一个Lock锁;
获取锁、释放锁使用格式
Lock lock=new ReentrantLock();//创建lock锁
lock.lock()   获取锁;
lock.unlock() 释放锁;
Lock锁比同步方法或同步代码块更灵活
注意:在使用Lock锁对象时,需要手动的书写代码用来:获取锁、释放锁(在同步代码块中不需要手动的书写获取锁、释放锁的代码。但是,同步代码块在执行时底层还是会获取锁、释放锁)

Condition(等待唤醒机制)
Condition中使用await/signal/signalAll固定格式
Lock lock=new ReentrantLock();//创建一个Lock锁
Condition xxxx=lock.newCondition();//xxx线程绑定的等待唤醒机制
xxxx.await() 让xxx线程等待;
xxxx.signal() 唤醒一个等待线程;
xxxx.signailAll()唤醒所有等待线程;
Lock锁代替了synchronized同步代码块的书写
Condition代替了同步代码块中锁对象上的等待唤醒

多线程细节
wait()和sleep()方法的区别:
sleep方法属于线程的;
sleep()方法中必须指定毫秒值;
sleep()只是让运行的线程暂时的等待,当时间一到,就自动转为运行状态或阻塞状态
wait()会让运行的线程一直处于等待状态,只有唤醒后,才会转为运行状态或阻塞状态
wait()在程序中只能书写在同步代码块中;sleep()可以书写在同步代码块中也可以书写在外部
wait()方法中可以指定毫秒值也可以不指定;
wait()属于对象锁的;
在创建线程时,每一个线程其实都具备了一个执行优先级别。默认的优先级别为:5
线程的优先级别范围是:1~10
在开发中,通常对于线程的优先级别会固守设置为:1、5、10;
提示:当线程的优先级别之间数值间距比较小时,对CPU而言就感觉不到优先级别的差异

线程优先级
max_priority    最高优先级
min_priority    最低优先级
norm_priority   默认优先级
获取/设置线程优先级
getPriority() 获取线程优先级
setPriority() 设置线程优先级

守护线程
普通线程改为守护线程书写格式:
ThreadDaemon t2=new ThreadDaemon();
t2.setName(“xxxx”)//给守护线程对象取名
t2.setDaemon(true) //将该线程标记为守护线程,true为守护线程
守护线程是随着普通线程的运行而运行,当普通线程结束,守护线程也会随着结束
注:守护线程必须与普通线程同时创建存在



作者: 风一样的少年    时间: 2015-6-24 12:21
我还没有学到这里,赞一下
作者: ice24    时间: 2015-6-24 12:24
风一样的少年 发表于 2015-6-24 12:21
我还没有学到这里,赞一下

加油,一起努力。
作者: 1千克=1024克    时间: 2015-6-24 12:58
这么详细...   线程这方面看来是吃透了
作者: 王冲6060    时间: 2015-6-24 18:50
感谢分享!
作者: 腹黑生姜    时间: 2015-6-24 21:00
很赞的总结!
作者: Foundmoon    时间: 2015-6-24 21:03
你写得好详细啊




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2