多线程
线程概述
程序、进程、线程
1、程序:指令集 静态概念
2、进程:操作系统 调度程序 动态概念
每个进程都是独立的,有3部分组成:cpu,data(数据),code(代码区)
缺点:内存浪费,cpu的负担
3、线程:Thread,是进程中一个“单一的连续控制流程”
一个进程可拥有多个并行的线程
一个进程中线程共享相同内存单元/内存地址空间-->可以访问相同的变量和对象,
而且它们从同一堆中分配对象-->通信、数据交换、同步操作
由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这
就使得通信更简便而且信息传递的速度也更快。
线程与进程区别:
1、根本区别:进程作为资源分配的单位,线程作为调度和执行的单位
2、开销:每个进程有独立代码和数据空间,进程间的切换开销大
线程是轻量级的进程,同以内线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,线程切换开销小
3、分配内存:系统运行时候会给每个进程分配不同内存区域
线程使用的资源是它所属的进程的资源,线程组只能共享资源
4、包含关系:进程包含线程
线程的创建
实现多线程(一):继承Thread类
package cn.pb.thread.create;
public classRabbit extendsThread{
@Override
public void run() {
//线程体
for(int i=0;i<100;i++){
System.out.println("兔子跑了"+i+"步");
}
}
}
class Torroise extends Thread{
@Override
public void run() {
//线程体
for(int i=0;i<100;i++){
System.out.println("乌龟跑了"+i+"步");
}
}
}
public static void main(String[] args) {
//创建子类对象
Rabbit rabbit = newRabbit();
Torroise torroise = newTorroise();
//调用start方法
rabbit.start(); //不要调用run方法
torroise.start();
}
实现多线程(二):实现Runnable接口(推荐)
public classRabbit2 implementsRunnable{
@Override
public void run() {
//线程体
for(int i=0;i<100;i++){
System.out.println("兔子跑了"+i+"步");
}
}
}
class Torroise2 implements Runnable{
@Override
public void run() {
//线程体
for(int i=0;i<100;i++){
System.out.println("乌龟跑了"+i+"步");
}
}
}
public static void main(String[] args) {
//1):创建真实角色
Rabbit2 r = newRabbit2();
Torroise2 t = newTorroise2();
//2):创建代理角色 + 正式角色引用
Thread proxyR = newThread(r);
Thread proxyT = newThread(t);
//3):调用start() 启动线程
proxyR.start();
proxyT.start();
}
小结:创建多线程方法
一、继承Threand + run()
启动:创建之内对象+ 对象.start()
二、实现Runnable + run()
启动:使用静态代理
1、 创建真实对象
2、 创建代理角色Thread+引用
3、 代理角色.start()
推荐使用实现runnable接口
1、 避免单继承局限性
2、 便于共享资源
3、 通用 可以多实现,不能多继承
静态代理
package cn.pb.thread.create;
public classStaticProxy {
public static void main(String[] args) {
//创建真实角色
You you = newYou();
//创建代理角色 + 真实角色的引用
WeddingCampany wc = newWeddingCampany(you);
//执行任务
wc.marry();
}
}
//接口
interface Marry{
public abstract void marry();
}
//真实角色
class You implementsMarry{
@Override
public void marry() {
System.out.println("you and 嫦娥.");
}
}
//代理角色
class WeddingCampany implements Marry{
private Marry you;
public WeddingCampany() {
}
public WeddingCampany(Marry you){
this.you = you;
}
private void before(){
System.out.println("布置猪窝.");
}
private void after(){
System.out.println("闹玉兔.");
}
@Override
public void marry() {
before();
you.marry();
after();
}
}
状态
一、线程状态
新生状态:
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。
就绪状态:
处于就绪状态的线程已经具备了运行条件,但还没有分配到cpu,处于线程就绪队列,等待系统为其分配cpu。等待状态不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行的状态进入执行状态。线程挑选的动作称之为“cpu调度”。一旦获得cpu。线程就进入运行状态兵自动调用自己的run方法。
运行状态:
在运行状态的线程执行自己的run方法中代码。直到调用其他方法而终止,或等待某资源而阻塞或完成任务而死亡,如果在给定的时间片内没有执行结束,就会被系统换下来回到等待执行状态。
阻塞状态:
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法。或等待I/O设备资源,将让出cpu并暂时停止自己的运行,并进入阻塞状态。在阻塞状态的线程不能进入就绪队列,只有当引起阻塞的原因消除时,如睡眠事件已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
死亡状态:
死亡状态是线程生命周期中的最后一个阶段,线程死亡的原因有两个。一个是正常运行的线程完成了它全部工作;另一个是线程被强制性的终止,如通过指向stop或destory方法来终止一个线程(不推荐使用这两个方法,前者会产生异常,后者是强制终止,不会释放锁)。
二、停止线程
1、 自然终止:线程体正常执行完毕
2、 外部干涉:
1)、线程类中定义线程体使用的标识
2)、线程体使用该标识
3)、提供对外的方法改变该标识
package cn.pb.thread.start;
public classDemo1 {
public static void main(String[] args) {
Study study = newStudy();
new Thread(study).start();
//外部干涉
for(int i=0;i<100;i++){
if(i==50){ //外部干涉
study.stop();
}
System.out.println("main..."+i);
}
}
}
class Study implementsRunnable{
//1.线程类中定义线程体使用的标识
private boolean flag = true;
@Override
public void run() {
//2.线程体使用该标识
while(flag){
System.out.println("study threaf...");
}
}
//3.对外提供方法改变标识
public void stop(){
this.flag=false;
}
}
三、阻塞
1、join:合并线程
2、yield:暂停自己线程 static
3、sleep:休眠,不释放锁
1)、与时间相关:倒计时
2)、模拟网络延时
public classSleepDemo01 {
public static void main(String[] args) throws InterruptedException {
int num=10;
while(true){
System.out.println(num--);
Thread.sleep(1000); //暂停
if(num<=0){
break;
}
}
}
}
public classSleepDemo02 {
public static void main(String[] args) throws InterruptedException {
Date endTime = newDate(System.currentTimeMillis()+10*1000);
long end = endTime.getTime();
while(true){
//输出
System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
//构建下一秒时间
endTime = newDate(endTime.getTime()-1000);
//等待1秒
Thread.sleep(1000);
//10秒内继续否则退出
if(end-10000>endTime.getTime()){
break;
}
}
}
}
同步
同步:并发 多个线程访问一分资源 确保资源安全-à线程安全
synchronized à同步
一、 同步块
synchronized(引用类型(this)类.class){
}
二、 同步方法
synchronized(this){
}
三、死锁:过多的同步容易导致死锁
单例模式
class Jvm{
//声明一个私有的静态变量
private static Jvm instance = null;
//构造器私有化,避免外部直接创建对象
private Jvm(){
}
//创建一个对外的公共的静态方法 访问该变量,如果变量没有对象,创建该对象
public static Jvm getInstance(){
if(null==instance){ //提高已经存在对象的访问效率
synchronized(Jvm.class){
if(null==instance){ //安全
instance = new Jvm();
}
}
}
return instance;
}
}
class Jvm2{
private static Jvm2 instance = new Jvm2();
private Jvm2(){
}
public static Jvm2 getInstance(){
return instance;
}
}
生产者消费者模式
也称为限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程-----即所谓的“生产者”和“消费者”------在实际运行是会发生的问题。生产者的主要作用是生成一定量的数据。该问题的关键就是要保证生产者不会在缓冲区时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常常用的方法有信号灯法,管程法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都将会陷入休眠,等待对方唤醒自己。
public class Movie {
private String pic;
private boolean flag = true;
public synchronized void play(String pic) {
if(!flag){ //生产者等待
try {
this.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
try {
//开始生产
Thread.sleep(500);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产了:"+pic);
//生产完毕
this.pic = pic;
//通知消费
this.notify();
//生产者停下
this.flag=false;
}
public synchronized void watch() {
if(flag){ //消费者等待
try {
this.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
try {
//开始消费
Thread.sleep(200);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费了:"+pic);
//消费完毕
//通知生产
this.notifyAll();
//消费停止
this.flag=true;
}
}
总结:
创建线程的两种方式
线程的状态:
新生—>startà就绪—>运行-à阻塞-à终止
线程终止
阻塞: sleep(线程方法) (与wait(Object方法)区别)