一、为什么需要多线程?
我们一直都在说使用多线程或者并发编程,但我们应该想想为什么需要用它?
百度百科给的解释是:多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
通俗易懂的讲,如果不考虑多线程的话,那么在程序只有一条执行路径,代码里串行执行:顺序执行、选择或者循环。单线程就像你用你惯常的手去写字,多线程编程就要求你左手画圆,右手画方。如果使用得当,可大大提高代码执行效率。
二、程序 进程 线程
1.程序:一系列指令的集合,简称指令集(属于静态概念)
2.进程:操作系统结构的基础 调度程序(属于动态概念)
进程作为资源分配的单位,在操作系统中能同时运行多个任务
- 进程是程序的一次动态执行过程,占用特定的地址空间
- 每个进程是独立的,由3部分组成cpu,data,code
- 缺点:内存的浪费,cpu的负担
3.线程:在进程内的多条执行路径
调度和执行的单位,在同一应用程序汇总有多个顺序流同时执行
- 线程又称为轻量级进程
- 一个进程可拥用多个并行的线程
- 相互独立
- 一个进程中的线程共享相同的内存单元,即可以访问相同的变量和对象,而且它们从同一堆中分配对象,即进行通信,数据交换或同步操作(容易造成并发问题)
- 由于线程中的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这使得通信简便且信息传递速度更快
三、Java中实现多线程(A)
模拟龟兔赛跑
1、创建多线程:继承Thread + 重写run方法(线程体)
2、使用线程:创建子类对象 + 对象.start() 线程启动
public class Rabbit extends Thread{
@Override
public void run(){
for(int i=0;i<100;i++)
Sysmtem.out.println("兔子跑了"+i+"步");
}
}
class Tortoise extends Thread{
@Override
public void run(){
for(int i=0;i<100;i++)
Sysmtem.out.println("乌龟跑了"+i+"步");
}
}
public class RabbitApp{
public static void main(String [] args){
//创建子类对象
Rabbit rab = new Rabbit();
Tortoise tor = new Tortoise();
//调用start方法
rab.start();//不要调用run方法(一条路径)
tor.start();
}
}
继承Thread类方式的缺点:那就是如果我们的类已经从一个类继承(如小程序必须继承着Applet类),则无法再继承Thread类。
四、Java中实现多线程(B)
静态代理 设计模式
1、真实角色
2、代理角色:持有真实角色的引用
3、二者 实现相同接口
public class StaticProxy{
public static void mian(String [] args){
//创建真实角色
Marry you = new You();
//创建代理角色+真实角色的引用
WeddingCompany company = new WeddingCompany(you);
//执行方法
company.marry();
}
}
interface Marry{
public abstract void marry();
}
//真实角色
class You implements Marry{
@Override
public void marry(){
System.out.println("you and 嫦娥结婚了!");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry you;
public WeddingCompany(){
}
public WeddingCompany(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();
}
}
使用Runnable创建线程
1、类实现Runnable接口+重写run()
2、启动多线程 使用静态代理
1)创建真实角色
2)创建代理角色+真实角色引用
3)调用.start()启动线程
public class Programmer implements Runnable{
@Override
public void run(){
for(int i=0;i<100;i++)
System.out.println("打码....");
}
}
public class ProgrammerApp{
public static void main(String [] args){
Programmer pro = new Programmer();
Thread proxy = new Thread(pro);
proxy.start();
for(int i=0;i<100;i++)
System.out.println("聊天....");
}
}
以上例子通过Runnable接口实现多线程
优点:可以同时实现继承,实现Runnable接口方式要通用一些
- 避免单继承
- 方便共享资源,同一份资源,多个代理访问
接下来是一个模拟抢票的多线程例子
public class Web12306 implements Runnable{
private int number = 50;
@Override
public void run(){
while(true){
if(num<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"抢票"+num--);
}
}
public static void main(String [] args){
//真实角色
Web12306 web = new Web12306();
//代理
Thread t1 = new Thread(web,"路人甲");
Thread t2 = new Thread(web,"黄牛已");
Thread t3 = new Thread(web,"工程师");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
五、Java中实现多线程 (C)
使用Callable创建线程
public class Call(){
public static void mian(String []args) throws Exception{
//创建线程
ExecutorService ser = Executors.newFixedThreadPool(1);
Race tortoise = new Race("乌龟",1000);
Race rabbit - new Race("兔子",500);
//获取值
Future<Integer> result1 = ser.submit(tortoise);
Future<Integer> result2 = ser.submit(rabbit);
Thread.sleep(2000);//停止2秒
tortoise.setFlag(false); //停止线程体循环
rabbit.setFlag(false);
int num1 = result1.get();
int num2 = result2.get();
System.out.println("乌龟跑了"+num1+"步");
System.out.println("兔子跑了"+num2+"步");
//停止服务
ser.shutdownNow();
}
class Race implements Callable<Integer>{
private String name; //名称
private long time; //延时时间
private boolean flag = true;
private int step = 0;//步数
public Race(){
}
public Race (String name ){
super();
this.name = name;
}
public Race (String name ,long time ){
super();
this.time = time;
this.name = name;
}
@Override
public Integer call() throws Exception{
while(flag){
Thread.sleep(time);//延时
step++;
}
return strp;
}
setters and getters;
}
六、线程状态
一句话总结:新生状态,start()后进行就绪状态,cpu调度后进行运行状态,运行完死亡状态
停止线程
- 自然终止,线程体正常执行完毕
- 外部干涉 ,如定义线程使用的标识,通过使用该标识改变线程的状态
public claass StopDemo01{
public static void mian(String []args){
Study s = new Study();
new Thread(s).start();
//外部干涉
for(int i=0;i<100;i++){
if(i==50){
s.stop();
}
System.out.println("main....."+i);
}
}
}
class Study implements Runnable{
(1)线程类中 定义 线程体使用的标识
private boolean flag = true;
@Override
public void run(){
(2) 线程体使用该标识
while(flag){
System.out.println("study thread...");
}
}
(3)对外提供方法改变标识
public void stop(){
this.flag = false;
}
}
阻塞
1、join:合并线程
public class JoinDemo01 extends Thread{
public static void main(String [] args){
JoinDemo01 demo = new JoinDemo01();
Thread t = new Thread(demo);
t.start();
for(int i =0 ;i<1000;i++){
if(i==50){
t.join(); //main阻塞
}
System.out.println("main...");
}
}
@Override
public void run(){
for(int i =0 ;i<1000;i++){
System.out.println("join...");
}
}
}
2、yield:暂停当前线程,是static方法
public class YieldDemo01 extends Thread{
public static void main(String [] args){
YieldDemo01 demo = new YieldDemo01();
Thread t = new Thread(demo);
t.start();
for(int i =0 ;i<1000;i++){
if(i%20==0){
Thread.yield();//暂停本线程main
}
System.out.println("main...");
}
}
@Override
public void run(){
for(int i =0 ;i<1000;i++){
System.out.println("yield...");
}
}
}
3、sleep:休眠,暂停当前线程,不释放锁
倒计时
1.倒数10个数,一秒内打印一个
public class SleepDemo01{
public static void main(String []args){
int num = 10;
while(true){
System.out.println(num--);
Thread.sleep(1000);//暂停1秒
if(num<0){
break;
}
}
}
}
七、线程基本信息
- isAlive() 判断线程是否还活着,即线程是否还未终止
- getPriority 获得线程的优先级数值
- setPriority() 设置线程的优先级数值
- setName() 给线程一个名字
- getName() 取得线程的名字
- currentThread() 取得当前正在运行的线程对象也就是取得自己本身
八、同步:并发,多个线程访问同一份资源,确保资源安全---->线程安全
1、同步块
synchronized(引用类型/this/类.class){
}
2、同步方法
public class SynDemo{
public static void main(String []args){
// Jvm jvm1 = Jvm.getInstance();
//Jvm jvm2 = Jvm.getInstance();
JvmThread thread1 = new JvmThread(100);
JvmThread thread2 = new JvmThread(500);
thread1.start();
thread2.start();
}
}
class JvmThread extends Thread{
private long time;
public JvmThread(){
}
public JvmThread(Long time){
this.time = time;
}
@Override
public void run(){
System.out.println("Thread.currentThread().getName()+"创建"+Jvm.getInstance());
}
}
单例设计模式
确保一个类只有一个对象
懒汉式
1、 构造器私有化,避免外部直接创建对象
2、 声明一个私有的静态变量
3、 创建一个对外的公共的静态方法 访问该变量,如果变量没有对象,创建该对象
class Jvm{
//声明一个私有的静态变量
private static Jvm instance = null;
//构造器私有化,避免外部直接创建对象
private Jvm(){
}
//创建一个对外的公共的静态方法 访问该变量 如果没有对象则创建
public static Jvm getInstance1(Long time ){
if(instance = null){
THread.sleep(time); //延时,放大错误
instance = new Jvm()
}
return instance;
}
public static synchronized Jvm getInstance(Long time ){
if(instance = null){
THread.sleep(time); //延时,放大错误
instance = new Jvm()
}
return instance;
}
}
九、生成者消费者模式(阻塞队列实现)
阻塞队列的特点
- 当队列元素已满的时候,阻塞插入操作;
- 当队列元素为空的时候,阻塞获取操作。
生产者
import java.util.concurrent.BlockingQueue;
public class Producer implements Runnable{
private final BlockingQueue blockingQueue;
//设置队列缓存的大小,生存过程中超过这个大小就暂时停止生产
private final int QUEUE_SIZE = 10;
public Producer(BlockingQueue blockingQueue){
this.blockingQUueue = blockingQueue;
}
int task = 1;
@Override
public void run(){
while(true){
try{
System.out.println("正在生产:"+task);
//将生产出来的产品放在队列缓存中
blockingQueue.put(task);
++task;
//停止一会,便于查看效果
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
消费者
import java.util.concurrent.BlockingQueue;
public class Consumer implements Runnable{
private final BlockingQueue blockingQueue;
public Consumer(BlockingQueue blockingQueue){
this.blockingQueue = blockingQueue;
}
@Override
public void run(){
//只要阻塞队列中有任务,就一直去消费
while(true){
try{
System.out.println("正在消费:"+blockingQueue.take());
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
测试
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class TestConPro{
public static void main(String []args){
BlockingQueue blockingQueue = new LinkedBlockingQueue(5);
Producer p = new Producer(blockingQueue);
Consumer c = new Consumer(blockingQueue);
Thread tp = new Thread(p);
Thread tc = new Thread(c);
tp.start();
tc.start();
}
}
十、任务调度
- Timer 定时器类
- TimerTask 任务类
Timer类的schedule方法实现任务调度
schedule(TimerTask task, Date firstTime, long period)
安排指定的任务在指定的时间开始进行重复的固定延迟执行
public class TestTimer{
public static void main(String []args){
Timer time = new Timer();
time.schedule(new TimerTask(){
//通过Timer类的schedule调用线程
//在newTimeTask的时候通过内部类实现run方法
@Override
public void run(){
System.out.println("So easy!!");
}
},new Date(System.currentTimeMillis()+1000),200);
}
}
在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间如果需要完全独立运行的话,最好还是一个Timer启动一个TimerTask实现。
总结:多线程对同一份资源共同使用 ,造成资源的不安全性,为了保证资源的安全与准确,需要加入同步