Java从零开始学四十四(多线程)
一、进程与线程
1.1、进程
进程是应用程序的执行实例。
1.2、线程
- 线程依赖进程生存
- 一个进程可以包含多个线程,而一个线程至少要有一个父进程
- 线程可以有自己的堆栈,程序计数器和局部变量。
- 线程与父进程的其他线程共享进程所有的全部资源
- 线程是独立运行,采用抢占方式。
- 一个线程可以创建和删除别外一具线程
- 同一个进程中的多个线程之间可以并发执行
- 线程的调试管理是由进程来完成的。
- 原则:编程时,必须确保线程不会妨碍同一进程的其他线程
1.3、线程分类
- 系统级线程:又称核心级线程,负责管理调度不同进程之间的多个线程,由操作系统直接管理
- 用户级线程:仅存于用户空间,在应用 程序中控制其创建,执行和消亡
1.4、多线程优势
- 改善用户体验
- 提高资源的利用率
二、Java中线程的实现
- 一种是继承Thread类
- 另一种就是实现Runnable接口
使用步骤:
定义一个线程----创建线程的实例--启动线程--终止线程

2.1、继承Thread类
/**
* 继承Thread类重写run方法
*
*/
public class MyThead1 extends Thread {
private int count; @Override
public void run() {
System.out.println("=======线程启动了=======");
while(this.count<100){
count++; }
System.out.println("count最终的值:"+count);
} public int getCount() {
return count;
} public void setCount(int count) {
this.count = count;
} }
测试类
/**
* 测试类
* MyTheadDemo
* 继承Thread类创建线程
* 1.继承Thread类
* 2.重写run方法
* 3.实例化线程类对象
* 4.调用start方法启动线程
*
* 继承Thread类存在问题
* Java中是单继承的,线程类不能继承其它的类
*
* 解决办法:实现Runnable接口
*
*/
public class MyTheadDemo1 { public static void main(String[] args) {
//实例化线程类对象
MyThead1 myThead1=new MyThead1();
//启动线程
myThead1.start();
/*
* start方法的作用
* 该方法公使操作系统初始化一个新的线程
* 由这个新线程来执行线程对象的Run方法
*/ } }
2.2、实现Runnable接口(推荐使用,使用接口可以解决Java中单继承的问题)
/**
* 实现Runnable接口
*
*/
public class MyRunnable implements Runnable {
private int count; @Override
public void run() {
System.out.println("=======线程启动了=======");
while(this.count<100){
count++; }
System.out.println("count最终的值:"+count);
} public int getCount() {
return count;
} public void setCount(int count) {
this.count = count;
} }
测试类
/**
* 测试类
* MyRunnable
* 实现Runnable接口创建线程
* 1.实现Runnable接口
* 2.实现run方法
* 3.实例化线程类对象
* 4.创建Thread类实例对象,并将实例化的线程对象传入
* 5.调用Thread类实例对象的start方法启动线程
*
*/
public class MyRunnableDemo { public static void main(String[] args) {
//实例化线程类对象
Runnable myRunnable=new MyRunnable();
Thread thread=new Thread(myRunnable);
//或者Thread thread=new Thread(new MyRunnable());
//启动线程
thread.start(); } }
2.3、启动线程
- 继承Thread类启动线程方法:
如果要想正确的启动线程,是不能直接调用run()方法的,应该调用从Thread类中继承而来的start()方法,才可以启动线程
- 实现Runnable接口启动线程方法:
2.4、Thread类和Runnable接口的区别
/**
* 实现Runnable接口
*
*/
public class MyRunnable implements Runnable {
private int count; @Override
public void run() {
count++; System.out.println("count最终的值:"+count);
} public int getCount() {
return count;
} public void setCount(int count) {
this.count = count;
} }
测试类
/**
* 测试类
* MyRunnable
* 实现Runnable接口创建线程
* 1.实现Runnable接口
* 2.实现run方法
* 3.实例化线程类对象
* 4.创建Thread类实例对象,并将实例化的线程对象传入
* 5.调用Thread类实例对象的start方法启动线程
*
*/
public class MyRunnableDemo { public static void main(String[] args) {
//实例化线程类对象
Runnable myRunnable=new MyRunnable();
Thread thread=new Thread(myRunnable);
Thread thread2=new Thread(myRunnable);
//或者Thread thread=new Thread(new MyRunnable());
//启动线程
System.out.println("线程1启动");
thread.start(); System.out.println("线程2启动");
thread2.start(); } }
结果:
线程1启动
线程2启动
count最终的值:1
count最终的值:2
再看下thread方式
/**
* 继承Thread类重写run方法
*
*/
public class MyThead1 extends Thread {
private int count; @Override
public void run() { count++;
System.out.println("count最终的值:"+count);
} public int getCount() {
return count;
} public void setCount(int count) {
this.count = count;
} }
/**
* 测试类
* MyTheadDemo
* 实现Runnable接口创建纯种
* 1.实现Runnable接口
* 2.重写run方法
* 3.实例化线程类对象
* 4.调用start方法启动线程
*
* 继承Thread类存在问题
* Java中是单继承的,线程类不能继承其它的类
*
* 解决办法:实现Runnable接口
*
*/
public class MyTheadDemo1 { public static void main(String[] args) {
//实例化线程类对象
MyThead1 myThead1=new MyThead1();
MyThead1 myThead2=new MyThead1();
//启动线程
System.out.println("线程1启动");
myThead1.start();
System.out.println("线程2启动");
myThead2.start();
/*
* start方法的作用
* 该方法公使操作系统初始化一个新的线程
* 由这个新线程来执行线程对象的Run方法
*/ } }
结果:
线程1启动
线程2启动
count最终的值:1
count最终的值:1

三、线程的状态


四、线程的优先级和方法
4.1、线程的优先级
- 默认情况下,一个线程继承其父类的优先级
- 优先级表示为一个整数值
- 优先级越高,执行的机会越大,反之,执行的机会就越小
- 高优先级的线程可以抢占低优先级线程的CPU资源
- 线程的优先级与线程执行的效率没有必然的联系

4.2、线程的方法
| No. | 方法名称 | 类型 | 描述 | 
| 1 | public Thread(Runnable target) | 构造 | 接收Runnable接口子类对象,实例化Thread对象 | 
| 2 | public Thread(Runnable target,String name) | 构造 | 接收Runnable接口子类对象,实例化Thread对象,并设置线程名称 | 
| 3 | public Thread(String name) | 构造 | 实例化Thread对象,并设置线程名称 | 
| 4 | public static Thread currentThread() | 普通 | 返回目前正在执行的线程 | 
| 5 | public final String getName() | 普通 | 返回线程的名称 | 
| 6 | public final int getPriority() | 普通 | 发挥线程的优先级 | 
| 7 | public boolean isInterrupted() | 普通 | 判断目前线程是否被中断,如果是,返回true,否则返回false | 
| 8 | public final boolean isAlive() | 普通 | 判断线程是否在活动,如果是,返回true,否则返回false | 
| 9 | public final void join() throws InterruptedException | 普通 | 等待线程死亡 | 
| 10 | public final synchronized void join(long millis) throws InterruptedException | 普通 | 等待millis毫秒后,线程死亡 | 
| 11 | public void run() | 普通 | 执行线程 | 
| 12 | public final void setName(String name) | 普通 | 设定线程名称 | 
| 13 | public final void setPriority(int newPriority) | 普通 | 设定线程的优先值 | 
| 14 | public static void sleep(long millis) throws InterruptedException | 普通 | 使目前正在执行的线程休眠millis毫秒 | 
| 15 | public void start() | 普通 | 开始执行线程 | 
| 16 | public static void yield() | 普通 | 将目前正在执行的线程暂停,允许其它线程执行 | 
| 17 | public final void setDaemon(boolean on) | 普通 | 将一个线程设置成后台运行 | 
| 18 | public final void setPriority(int newPriority) | 普通 | 更改线程的优先级 | 
五、线程调度
5.1、join()方法
作用:阻塞指定的线程等到另一个线程完成以后,再继续执行
package thead;
/**
* 实现join方法
* 1.join的线程运行完成后,才会继承运行当前线程
* 2.join之前要先启动线程start方法
*
*/
public class JoinRunnable implements Runnable { @Override
public void run() {
for (int i = 0; i <5; i++) {
System.out.println(Thread.currentThread().getName()+" 第"+i+"次");
} } public static void main(String[] args) {
//主线程
for (int i = 0; i <10; i++) {
if(i==5){
Thread thread=new Thread(new JoinRunnable());;
thread.setName("半路切入的线程:");
try {
//启动线程
thread.start();
//半路加入线程
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 第"+i+"次");
}
} }
结果:
main 第0次
main 第1次
main 第2次
main 第3次
main 第4次
半路切入的线程: 第0次
半路切入的线程: 第1次
半路切入的线程: 第2次
半路切入的线程: 第3次
半路切入的线程: 第4次
main 第5次
main 第6次
main 第7次
main 第8次
main 第9次
5.2、sleep()
阻塞当前线程,当前等待的线程将获得机会
调用sleep方法后,,当前线程会被扶起(暂停执行)当前线程会释放资源
package thead;
public class SleepRunnable implements Runnable {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                //每一秒执行一次
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Thread sleepThread=new Thread(new SleepRunnable());
        sleepThread.setName("等待中的线程");
        sleepThread.start();
        for (int i = 0; i < 10; i++) {
            if(i==5){
                try {
                    //主线程=等5秒
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+i);
        }
        }
    }
结果:
main0
main1
main2
main3
main4
等待中的线程0
等待中的线程1
等待中的线程2
等待中的线程3
main5
main6
main7
main8
main9
等待中的线程4
等待中的线程5
等待中的线程6
等待中的线程7
等待中的线程8
等待中的线程9
5.3、yield()方法
将当前线程转入可运行状态,
package yield;
/**
* 线程一
*
*/
public class MyThread1 implements Runnable { @Override
public void run() {
for (int i = 0; i <10; i++) {
Thread.yield();
System.out.println(Thread.currentThread().getName()+":"+i);
} } } package yield;
/**
* 线程二
*
*/
public class MyThread2 implements Runnable { @Override
public void run() {
for (int i = 0; i <10; i++) {
Thread.yield();
System.out.println(Thread.currentThread().getName()+": "+i);
} } } package yield;
/**
* yield方法使用
*2个线程抢占方式
*/
public class Test { public static void main(String[] args) {
Thread myThread1=new Thread(new MyThread1());
myThread1.setName("线程1");
Thread myThread2=new Thread(new MyThread2());
myThread2.setName("线程2");
//同时启动2个线程
myThread1.start();
myThread2.start();
} }
结果:
线程2: 0
线程1:0
线程2: 1
线程1:1
线程2: 2
线程1:2
线程2: 3
线程1:3
线程1:4
线程2: 4
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9
线程2: 5
线程2: 6
线程2: 7
线程2: 8
线程2: 9
sleep()和yield()方法比较
- sleep()方法使当前线程转入被阻塞的状态,而yield()方法使用当前线程转入可运行状态
- sleep()方法总是强制当前线程停止执行,百yield() 方法不一定
- sleep()方法可以使其它等待运行的线程有同样的执行级别而yield()方法只使相同或者更高优先级的线程获得执行机会
- 使用sleep()方法时需要捕获异常,而yield()方法无需要捕获异常
5.4、setDaemon()方法
将线程设置 为后台线程(守护线程)。
只能在线程启动之前设置.
package thead;
public class Daemon implements Runnable {
    @Override
    public void run() {
    for (int i = 0; i <10; i++) {
        System.out.println(Thread.currentThread().getName()+":"+i);
    }
    }
    public static void main(String[] args) {
        Thread daemon=new Thread(new Daemon());
        daemon.setName("后台线程");
        // 设置为后台线程
        daemon.setDaemon(true);
        daemon.start();
        for (int i = 0; i <10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
六、同步
6.1、简单例子
同一账户2个人同时取款
package demo;
public class Account {
    //余额
    private int balance=500;
    //检查余额
    public int getBalance() {
        return balance;
    }
    //取款
    public void withDraw(int amount){
        balance=balance-amount;
    }
    public void setBalance(int balance) {
        this.balance = balance;
    }
}
package demo;
public class TestAccount implements Runnable{
    private Account account=new Account();
    @Override
    public void run() {
    for (int i = 0; i <5; i++) {
        //一次取走100
        makeWithDraw(100);
        if(account.getBalance()<0){
            System.out.println("账户透支了!!!!!");
        }
    }
    }
    //取款参数  同步方法
    private synchronized void makeWithDraw(int amount){
        if(account.getBalance()>=amount){
            //当前余额是否足够可以取款
            System.out.println(Thread.currentThread().getName()+"  准备取款");
            try {
                //0.5秒后取款
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //完成取款
            account.withDraw(amount);
            System.out.println(Thread.currentThread().getName()+"  完成取款,当前余额为: "+account.getBalance());
        }else{
            //如果余额不足不能取款
            System.out.println(Thread.currentThread().getName()+"  余额不足以支付当前取款, 余额为:  "
                    +account.getBalance());
        }
    }
}
测试类
package demo;
/**
* 如果一个资源被多个线程使用,不上锁就会造成读取严重错误
* 如果想让当前资源被一个线程使用时,不会受到其他纯种的影响,应该给当前资源上锁
* Java中使用sychronized关键字保证数据同步
*
*/
public class Test { public static void main(String[] args) {
//创建线程类的实例
TestAccount ta=new TestAccount();
//创建线程
Thread thread1=new Thread(ta);
thread1.setName("张三");
Thread thread2=new Thread(ta);
thread2.setName("张三的妻子");
//启动线程
thread1.start();
thread2.start();
} }
上面是同步方法
下面是同步代码块
package demo;
public class TestAccount implements Runnable{
    private Account account=new Account();
    @Override
    public void run() {
    for (int i = 0; i <5; i++) {
        //一次取走100
        makeWithDraw(100);
        if(account.getBalance()<0){
            System.out.println("账户透支了!!!!!");
        }
    }
    }
    //取款参数
    private  void makeWithDraw(int amount){
        //同步代码块
        synchronized (account){
        if(account.getBalance()>=amount){
            //当前余额是否足够可以取款
            System.out.println(Thread.currentThread().getName()+"  准备取款");
            try {
                //0.5秒后取款
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //完成取款
            account.withDraw(amount);
            System.out.println(Thread.currentThread().getName()+"  完成取款,当前余额为: "+account.getBalance());
        }else{
            //如果余额不足不能取款
            System.out.println(Thread.currentThread().getName()+"  余额不足以支付当前取款, 余额为:  "
                    +account.getBalance());
        }
    }
    }
}
七、线程间通信的实现
- wait()方法:
- notify()方法:
- notifyAll()方法:
以上3个方法只能在同步方式或者同步代码块中使用
wait()方法:会挂起当前的线程,并且释放共享资源的锁。当前线程会从可运行状态转为阻塞状态直到调用了wait()方法所属的那个对象的notify()方法或者notifyAll()方法.
notify()方法:可以唤醒因为调用wait()方法而被挂起的那个线程,并且使这个线程退出阻塞状态进入可运行状态
notifyAll()方法:可以唤醒因为所有调用wait()方法而被挂起的那个线程,并且使这些线程退出阻塞状态进入可运行状态
生产者和消费者
package com.pb;
/**
* 商品共享数据
*
*/
public class SharedData { private char c;
private boolean idProducted=false;//信号量
//同步方法生产者生产产品的的方法
public synchronized void putSharedChar(char c){
//如果产品还没有被消费,则生产都等待
if(idProducted){
try {
System.out.println("消费者还未消费,因此生产都停止生产");
wait();
} catch (InterruptedException e) { e.printStackTrace();
}
}
this.c=c;
idProducted=true; //标记已经生产
notify();//通知消费都
System.out.println("生产者生产了产品"+c+",通知消费者");
}
//同步方法 消费者消费产品的的方法
public synchronized char getSharedChar(char c){
//如果产品还没有被消费,则生产都等待
if(idProducted==false){
try {
System.out.println("生产者还未生产,消费者停止消费");
wait();
} catch (InterruptedException e) { e.printStackTrace();
}
} idProducted=false; //标记已经消费
notify();//通知生产者
System.out.println("消费者消费了产品"+c+",通知生产者");
return this.c; }
}
package com.pb;
/**
* 生产者
*
*/
public class Producer implements Runnable {
//共享数据
private SharedData sharedData; public Producer(SharedData sharedData){
this.sharedData=sharedData;
} @Override
public void run() {
for (char c = 'A'; c <= 'D'; c++) { try {
Thread.sleep((int)Math.random()*3000); } catch (InterruptedException e) { e.printStackTrace();
}
//将产品生产后,放入仓库
sharedData.putSharedChar(c);
} } }
package com.pb;
/**
* 消费者
*
*/
public class Consumer implements Runnable {
//共享数据
private SharedData sharedData; public Consumer(SharedData sharedData){
this.sharedData=sharedData;
}
@Override
public void run() {
char ch = 0;
do {
try {
Thread.sleep((int)Math.random()*3000); } catch (InterruptedException e) { e.printStackTrace();
}
//从仓库中取中产品
ch=sharedData.getSharedChar(ch);
} while (ch!='D'); } }
测试类
package com.pb;
public class CommunicationDemo {
    public static void main(String[] args) {
        //共享资源,产品
        SharedData sharedData=new SharedData();
        Thread producer=new Thread(new Producer(sharedData));
        Thread consumer=new Thread(new Consumer(sharedData));
        //启动线程
        consumer.start();
        producer.start();
    }
}
结果:
生产者还未生产,消费者停止消费
生产者生产了产品A,通知消费者
消费者消费了产品A,通知生产者
生产者生产了产品B,通知消费者
生产者还未生产,消费者停止消费
生产者生产了产品C,通知消费者
消费者消费了产品B,通知生产者
生产者生产了产品D,通知消费者
消费者消费了产品C,通知生产者
Java从零开始学四十四(多线程)的更多相关文章
- Java从零开始学二十四(集合工具类Collections)
		一.Collections简介 在集合的应用开发中,集合的若干接口和若干个子类是最最常使用的,但是在JDK中提供了一种集合操作的工具类 —— Collections,可以直接通过此类方便的操作集合 二 ... 
- 从零开始学安全(十四)●Windows Server 2012 R2 本地搭建FTP服务器
		打开仪表盘添加角色和功能向导 下一步 等待安装完成 打开iis 新建站点 点击 选一个目录作为 ftp文件服务器的存储路径 后面就和iis 创建站点一样了 匿名就不需要密码 就可以访问基本需要特定的账 ... 
- Java从零开始学三十九(对象序列化)
		一.序列化 将对象的状态存储到特定存储介质中的过程 对象序列化,就是把一个对象变为二进制的数据流的一种方法,通过对象序列化可以方便的实现对象的传输或存储. 序列化保存对象的“全景图”,构建对象的“ ... 
- Java从零开始学三十三四(JAVA IO-流简述)
		一.流概念(stream) File类并不能对文件内容进行读写. 读文件就是指:把文件的内中的数据读取到内存中来 写文件就是指:把内存中的数据写入到文件中去. 通过什么读写文件呢?文件流. 1.1.流 ... 
- Java从零开始学三十二(正则表达式)
		一.为什么要有正则 正则表达式可以方便的对数据进行匹配,可以执行更加复杂的字符串验证.拆份.替换功能. 例如:现在要求判断一个字符串是否由数字组成,则可以有以下的两种做法: 不使用正则完成 使用正则完 ... 
- Java从零开始学三十(String和StringBuffer类)
		一.StringBuffer连接字符操作 当一个字符串的内容需要被经常改变时就要使用StringBuffer 在StringBuffer中使用append()方法,完成字符串的连接操作 二.Str ... 
- Java从零开始学二十二(集合Set接口)
		一.Set接口的定义 Set接口也是Collection接口的子接口,但是与Collection或List接口不同的是,Set接口中不能加入重复的元素 Set接口的主要方法与Collection是一致 ... 
- Java从零开始学二十(集合简介)
		一.为什么需要集合框架 数组的长度是固定的,但是如果写程序时并不知道程序运行时会需要多少对象.或者需要更复杂的方式存储对象,---那么,可以使用JAVA集合框架,来解决这类问题 二.集合框架主要接口 ... 
- Java从零开始学三十八(JAVA IO- 重定向IO)
		一.三个静态变量 java.lang.System提供了三个静态变量 System.in(默认键盘) System.out(默认显示器) System.err 二.重写向方法 System提供了三个重 ... 
- Java从零开始学三十六(JAVA IO- 字符流)
		一.字符流 BufferedReader:BufferedReader是从缓冲区之中读取内容,所有的输入的字节数据都将放在缓冲区之中 BufferedWriter:把一批数据写入到缓冲区,当缓冲区区的 ... 
随机推荐
- 【原创】MYSQL++源码剖析——前言与目录
			终于完成了! 从第一次想写到现在真的写好大概花了我3个月时间.原来一直读人家的系列文章,总感慨作者的用心良苦和无私奉献,自己在心里总是会觉得有那么些冲动也来写一个. 最开始的麻烦是犹豫该选哪个主题.其 ... 
- CentOS下Redis服务器安装配置
			说明: 操作系统:CentOS 1.安装编译工具 yum install wget make gcc gcc-c++ zlib-devel openssl openssl-devel pcre-de ... 
- WWDC2015 结束.新一波更新以及bug即将来袭.
			WWDC结束.新一波更新以及bug即将来袭. HTTPS 将成为标准链接. http被报错. GamePlayKit 这是搞那样. 还有ReplayKit 那些什么录像分享什么的还有活路么? Mod ... 
- webgame设计之功能模块的代理模式
			原文地址:http://chengduyi.com/blog/?post=27 在游戏设计中,通常会将一些实现了具体功能的模块进行封装,达到重用的目的.这些功能模块包括:1.网络通信模块(实现连接,断 ... 
- Unity 动画
			Unity 并没有自带建模工具. 3D建模工具 maya, 3dmax, blender Skinned Mesh Renderer Mesh Renderer Mesh Filter Modelli ... 
- AC_Dream 1224 Robbers(贪心)
			题意:n个抢劫犯分别抢到的金钱是k1, k2, k3,...,一共得到的金钱是m, 但是在分钱的时候是按照x1/y, x2/y, x3/y,....的比例进行分配的!这样的话 一些抢劫犯就会觉得不公平 ... 
- java中DatagramSocket连续发送多个数据报包时产生丢包现象解决方案
			try { //向指定的ip和端口发送数据~! //先说明一下数据是谁发送过来的! byte[] ip = InetAddress.getLocalHost().getHostAddress().ge ... 
- 理解并使用.NET 4.5中的HttpClient
			HttpClient介绍 HttpClient是.NET4.5引入的一个HTTP客户端库,其命名空间为System.Net.Http..NET 4.5之前我们可能使用WebClient和HttpWeb ... 
- Web Fram 2 for IIS7.X(Microsoft Web Farm Framework)
			Microsoft Web Farm Framework (WFF) 2.0 是微软开发的.基于IIS 7.x的小插件,能够帮助我们轻松实现Web网站的高性能.高可用性,用来在Web服务器群上提供和管 ... 
- IIS 7完全攻略之日志记录配置(摘自网络)
			IIS 7完全攻略之日志记录配置 作者:泉之源 [IT168 专稿]除了 Windows 提供的日志记录功能外,IIS 7.0 还可以提供其他日志记录功能.例如,可以选择日志文件格式并指定要记录的请求 ... 
