Java中的多线程

一、多线程

1、 进程的概述:
 a) 进程:正在运行的程序,
  是系统进行资源分配和调用的独立单位,每一个进程都有内存空间和系统资源
 b) 单进程只能做一件事情
  多进程可以做多件事情,提高CPU的利用率
 c) 一边玩游戏,一边听音乐是同时进行吗?
  不是,单核CPU同一时间点只能做一件事情,感觉同时进行是因为CPU在做高效切换,
2、 线程的概述
 a) 在一个进程中可以执行多个任务,每一个任务可以看作一个线程
 b) 线程:程序的执行单元,执行路径,是程序使用CPU的基本单位
 c) 单线程:程序只有一条执行路径
  多线程:程序有多条执行路径
 d) 多线程的意义:提高程序的使用率
 e) 多线程的执行有随机性
3、 并行和并发的区别:
 a) 并行是逻辑上的同时发生,在某一时间内同时运行多个程序
 b) 并发是物理上的同时发生,在某一时间点同时运行多个程序
4、 Java程序运行原理:
 a) Java命令启动JVM(Java虚拟机),JVM启动就相当于启动了一个进程
 b) 该进程创建一个主线程调用main方法
 c) JVM虚拟机的启动是多线程还是单线程的?
  i. 多线程
  ii. 最少启动两个(主线程、垃圾回收线程),垃圾回收线程避免内存溢出
5、 共有两种方式实现多线程
 a) 继承Thread类
  步骤
   i. 自定义类继承Thread类
   ii. 自定义类中重写run()方法,此方法中包含被线程执行的代码
    run()方法和start()方法的区别
    run():仅仅是封装被线程执行的代码,直接调用相当于普通方法
    start():首先启动了线程,然后由Jvm去调用线程的run()方法
   iii. 创建自定义类对象
   iv. 启动线程
  线程不能多次启动,多线程需要多个对象
 b) 声明实现Runnable接口
  i. 自定义类实现Runnable接口
  ii. 重写run()方法
  iii. 创建自定义类的对象
  iv. 创建Thread类的对象,并把第三步的对象作为构造参数传递
   Thread(Runnable r,String name)
    r为实现Runnable接口类的对象,name为该线程的名称
   Thread(Runnable r) //线程名称由系统默认
  v. 启动线程
 c) 以上两种方法的区别:
  i. 方式2可以避免由于Java单继承而造成的局限性
  ii. 方式2适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,
   更好的体现了面向对象的设计
 下面给出两种方式的实例
6、 线程的两种调度模型
 a) 分时调度:所有线程轮流获得CPU的使用权,平均分配每个线程占用CPU的时间片
 b) 抢占式调度:优先让优先级高的进程使用CPU,如果线程优先级相同会随便选择一个,
  优先级高的线程获得的CPU时间片相对多一些
 c) Java使用抢占式调度优先级默认是5,最高优先级是10,最低优先级1,
  优先级高的线程获得的CPU时间片几率高(存在偶然性,运行一次并不一定能看到结果)
7、 线程中Thread类的常用方法
 a) run():封装被线程执行的代码,
 b) start():首先启动了线程,然后由Jvm去调用线程的run()方法
 c) public final String getName() 获取线程的名称
 d) public final void setName() 设置线程的名称
 e) public static Thread currentThread() 获取当前正在执行的线程对象
  可以获取或设置此对象的名称
  Thread.currentThread().getName() 获取对象的线程名称
 f) public final int getPriority() 返回线程对象优先级,
 g) public final void setPriority(int newPriority) 更改线程的优先级,
 h) public static void sleep(long millis) 在指定毫秒内让当前正在执行的进程休眠(暂停执行)
 i) public final void join() 等待该线程终止其他线程才可以运行
 j) public static void yield() 暂停当前正在执行的线程对象,并执行其他线程
  让多个线程尽量交替执行,但是此方法不能保证线程每次都是交替执行
 k) public final void setDaemon(boolean on) 设置该线程为守护线程
  当运行的线程都是守护线程时Java虚拟机退出(在退出的那一刻守护线程可能仍在运行,
  但很快就结束)
  该方法必须在启动线程前调用
 l) public void interrupt() 中断线程,
  终止线程当前状态并抛出InterruptedException
8、 线程的生命周期
 a) 新建:创建线程对象
 b) 就绪:有执行资格,但没有执行权
 c) 运行:有执行资格,有执行权
  运行时可能会出现阻塞状态,
  阻塞:由于一些操作让线程出现在此状态,无执行资格,无执行权
  激活阻塞状态后线程便处于就绪状态
 d) 死亡:线程对象变为垃圾,等待回收
在这里插入图片描述

9、 解决线程安全问题的基本思想
 a) 出现问题的原因
  i. 是否是多线程环境
  ii. 是否有共享数据
  iii. 是否有多条语句操作共享数据
 b) 如何解决问题
  前两个产生问题的原因无法避免,因此只能改变第三个原因
  i. 把操作共享数据的多条代码包装成一个整体,让某个线程执行时其他线程不能再继续执行
  ii. 同步有两种方式:
   1. 同步代码块
   2. 同步方法
  iii. 同步代码块:
   synchronized(对象){
    需要同步的代码;
   }
   对象即为同步锁,可以为任意对象,下面多线程实现方法二的实例中用了同步代码块的方法
  iv. 同步方法的格式及锁对象:
   同步关键字加在方法上
   同步方法对象是this
  v. 静态方法的锁对象:
   静态方法的锁对象是:类的字节码文件对象(当前类类名.class)
  vi. 同步的特点:多个线程,且多个线程都是使用同一个锁对象
   同步的好处:解决了多线程的安全问题
   同步的弊端:
     1、当线程很多时,每个线程都会去判断同步上的锁,耗费资源,降低了程序的运行效率
     2、如果出现同步嵌套容易产生死锁问题
      死锁指两个或两个以上的线程在执行过程中由于争夺资源产生的一种相互等待的现象。
  vii. 多个不同种类的线程控制同一个资源加锁时应注意
   1. 不同种类的线程都要加锁
   2. 加的锁必须为同一把锁
10、 锁接口Lock
 a) 实现类ReentrantLock()
 b) void lock() 加锁
 c) void unLock() 解锁
 通过实现类来调用加锁和解锁的方法,加锁解锁的方法比同步代码块看起来更直接
 下面给出加锁和解锁的实例
11、 线程间通信:不同种类的线程对同一资源的操作
 a) 等待唤醒机制:
  Object类中的方法,必须通过锁对象调用下列方法(下面给出实例)
  i. wait () 线程等待,并释放锁
  ii. notify () 唤醒单个线程,唤醒并不代表立即可以执行,还需要抢CPU执行权
  iii. notifyAll () 唤醒所有线程
  iv. 为什么这些方法都在Object类而不是在Thread类中?
  这些方法调用都是依赖于锁对象的,同步代码块的锁对象任意,所以这些方法定义在Object类中
 b) sleep()和wait()方法的区别:
  i. sleep () 必须指定时间,不释放锁
  ii. wait () 可以不指定时间,释放锁
12、 线程组:
 将多个线程组合到一起,从而实现对一批线程进行分类管理,
 java允许程序直接对线程组进行操作
 线程默认情况下属于main线程组
 a) 获取线程组(Thread类中的方法)
  public final ThreadGroup getThreadGroup()
 b) 获取线程组的名称(线程组中的方法)
  public final String getName()
 c) 修改线程组:
  i. 设置一个线程组
   构造方法 ThreadGroup(String name)
  ii. 创建一个线程,并指定一个线程组
   构造方法 Thread(ThreadGroup group ,Runnable target ,String name)
   group为线程组对象,target为Runnable接口实现类对象,name为线程名称
13、 线程池(Executors线程池工具类):
 a) 程序启动一个新线程需要与操作系统进行交互,成本较高,而使用线程池可以提高性能,
  当创建大量生存期很短的线程时应该考虑使用线程池。
 b) 线程池里的每一个线程代码结束后并不会死亡,而是再次回到线程池中称为空闲状态,
  等待下一个对象来使用
 c) 创建线程池的步骤(代码实例在下面给出)
  i. 创建线程池对象
   public static ExecutorService newFixedThreadPool(int nThreads)
   nThreads为线程池中线程数目
  ii. 线程池中的线程可以执行Runnable对象或者Callable对象代表的线程
   Runnable需要实现类来实现此接口
   Callable是带泛型的接口

二、程序实例
1、多线程的两种实现方法:实现三个窗口卖票的功能

方法一:继承Thread类

package test1_SellTickets;
/*
 * 三个窗口共同售卖100张电影票
 */
public class SellTicketsDemo1 {
	public static void main(String[] args) {
		
		//创建对象
		SellTickets1 st1=new SellTickets1();
		SellTickets1 st2=new SellTickets1();
		SellTickets1 st3=new SellTickets1();
		
		//设置线程名称
		st1.setName("窗口1");
		st2.setName("窗口2");
		st3.setName("窗口3");
		
		//启动线程
		st1.start();
		st2.start();
		st3.start();
	}
}

package test1_SellTickets;

public class SellTickets1 extends Thread{
	public static int tickets=100;
	//重写run()方法
	public void run() {
		while(tickets>0) {
			//由于继承Thread类,可以直接使用getName()方法
			System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
		}
	}
}

方法二:实现Runnable接口

package test1_SellTickets;
/* 
 * 运行可能产生负数票
 * 		随机性和延迟性导致
 * 
 * 相同票出现多次
 * 		CPU的一次操作必须是原子性的
 * 
 */

public class SellTicketsDemo2 {
	public static void main(String[] args) {
		//创建接口实现类对象
		SellTickets2 st=new SellTickets2();
		
		//创建Thread对象
		Thread t1=new Thread(st, "窗口1");
		Thread t2=new Thread(st, "窗口2");
		Thread t3=new Thread(st, "窗口3");
		
		//启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}

package test1_SellTickets;

public class SellTickets2 implements Runnable {
	private int tickets=1000;
	private Object obj=new Object(); 
	//重写run()方法
	public void run() {
		while(true) {
			//同步,obj为同步对象,用于控制同步锁,此处的对象可以任意 
			//当某个线程运行时会自动关闭此锁,运行完后此锁打开,
			//锁打开后三个线程继续争夺时间片
			synchronized(obj) {
				if(tickets>0) {
					//未继承Thread类,getName()方法不可以直接用
					//可以使用此方法获取名字Thread.currentThread().getName()
					System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
					try {
						Thread.sleep(5);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}else {
					break;
				}
				
			}
		}
		
		
	}

}

方法二采用了同步锁的方法,能够使线程更加安全

2、加锁和解锁的实例

依然是实现买票功能

package test2_lockunLock;

public class Demo {
	public static void main(String[] args) {
		SellTicketsDemo std=new SellTicketsDemo();
		
		Thread t1=new Thread(std, "窗口1");
		Thread t2=new Thread(std, "窗口2");
		Thread t3=new Thread(std, "窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

package test2_lockunLock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicketsDemo implements Runnable{

	private int tickets=100;
	private Lock l=new ReentrantLock();
	public void run() {
		while(true) {
			//为避免中间环节出错导致不能解锁所以用try……finally语句
			try {
				l.lock();
				if(tickets>0) {
					
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+"正在售出第"+(tickets--)+"张票");
					
				}else{
					break;
				}
				
			}
			//保证一定能解锁
			finally {
				l.unlock();
				
			}
			
		}
		
	}
	
}
3、生产者和消费者(运用wait()和notify()方法解决线程同步通信的问题)

测试类

package test3_productercustomer;
/*
 * 功能:
 * 		设置线程(生产者)和获取线程(消费者)针对同一个对象进行操作
 * 
 * 如果想依此交替输出对象,需要用等待唤醒机制
 * 	设置flag标志位
 * 	有数据(设置数据后没输出)状态:flag=true,
 * 		这时应该等待输出,输出后设置flag为false
 * 
 * 	没数据(输出后还未设置数据)状态:flag=false,
 * 		等待设置数据,设置数据后更改flag为true
 * 	
 * 	资源类:Student
 * 	设置学生数据:Set
 * 	获取学生数据:Get
 * 	测试类:StudentDemo
 * 
 * 问题:
 * 		1、有可能线程Get先输出,之后线程Set再设置学生对象,这时输出为	null---0
 * 		
 * 		2、输出多个学生对象时有可能不是对应的名字和年龄输出
 * 			原因,Set仅仅设置了此对象的name,还用的上一个对象age,Get便进行了输出
 * 			解决:加入同步代码块(两个线程共用一个数据,所以同步代码块的锁对象应该相同)
 * 
 * 				
 * 
 */
public class Demo {
	public static void main(String[] args) {
		Student s=new Student();
		
		Set get=new Set(s);
		Get set=new Get(s);
		
		Thread t1=new Thread(set);
		Thread t2=new Thread(get);
		
		t1.start();
		t2.start();
	}
}

资源类

package test3_productercustomer;

public class Student {
	String name;
	int age;
	boolean flag;
}

设置学生数据类

package test3_productercustomer;

public class Set implements Runnable{

	private Student s;
	private int x=0;
	public Set(Student s){
		this.s=s;	
	}
	
	public void run() {
		while(true) {
			synchronized (s) {
				if(s.flag==true) {
					try {
						s.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				if(x%2==0) {
					s.name="zfliu";
					s.age=18;
				}else {
					s.name="Java";
					s.age=20;
				}
				x++;
				
				s.flag=true;
				s.notify();
			}
			
		}
		
		
	}

	
}

获取学生数据类

package test3_productercustomer;

public class Get implements Runnable {
	private Student s;
	
	public Get(Student s){
		this.s=s;	
	}
	
	public void run() {
		while(true) {
			synchronized (s) {
				if(s.flag==false) {
					try {
						s.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				System.out.println(s.name+"---"+s.age);
				
				s.flag=false;
				s.notify();
			}
			
		}
		
	}

}

4、线程池
package test4_Executors;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
	public static void main(String[] args) {
		//创建线程池,一共包含3个线程
		ExecutorService pool=Executors.newFixedThreadPool(3);
		
		//调用Runnable接口实现类对象,每个实现类是一个线程
		//因为线程池中包含3个线程,所以调用3次
		//如果调用4次那么线程1会被调用两次
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		
		//关闭线程池,
		//如果线程池没有关闭,程序会一直运行,
		//运行结束的线程会变为空闲状态,再次回到线程池中等待下一个对象使用
		pool.shutdown();
	}
}

package test4_Executors;

public class MyRunnable implements Runnable{

	private int x;
	
	public void run() {
		for(x=0;x<100;x++) {
			System.out.println(Thread.currentThread().getName()+":"+x);
		}
		
	}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值