一.进程和线程
1.进程
- 执行程序的一次执行过程,它是一个动态的概念,
- 一个应用程序的运行就可以被看做是一个进程,有独立的内存空间和系统资源
- 通常一个进程中可以包含若干个线程
2.线程
- 是cpu调度和分派的基本单位,是应用程序的执行实例,运行中的实际的任务执行者
3.多线程
1)定义
- 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”,多个线程交替占用CPU资源,而非真正的并行执行
2)多线程好处
- 充分利用CPU的资源
- 简化编程模型
- 带来良好的用户体验
4.主线程
1)Thread类
- Java提供了java.lang.Thread类支持多线程编程
2)主线程
- main()方法即为主线程入口
- 产生其他子线程的线程
- 必须最后完成执行,因为它执行各种关闭动作
public class TestMain {
//main方法自己就是一个线程,会开启栈区
public static void main(String[] args) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前的线程名
System.out.println(t.getName());
//可以自行赋值线程名
t.setName("自己赋值的线程名");
System.out.println(t.getName());
//或者把线程名写进参数里
Thread th=new Thread("子线程1");
System.out.println(th.getName());
}
}
效果图
main
自己赋值的线程名
子线程1
二.线程的创建和启动
1.继承Thread类创建线程
1)步骤
- 定义MyThread类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
-注: - 多个线程交替执行,不是真正的“并行”
- 线程每次执行时长由分配的CPU时间片长度决定
- 直接调用run方法只有主线程main一条执行路径
2)代码块演示
public class Test extends Thread{
@Override
public void run() {//线程工作的主体
Thread t=Thread.currentThread();
for (int i = 1; i <=3 ; i++) {
System.out.println(t.getName() + ":" + "第"+i+"次");
}
}
public static void main(String[] args) {
Test t=new Test();
Test t1=new Test();
t.setName("线程1");
t1.setName("线程2");
t.start();
t1.start();
}
}
效果图
线程2:第1次
线程1:第1次
线程2:第2次
线程1:第2次
线程2:第3次
线程1:第3次
2.实现Runnable接口创建线程
1)步骤
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
2)代码块演示
public class Test implements Runnable{
@Override
public void run() {//线程工作的主体
Thread t=Thread.currentThread();
for (int i = 1; i <=3 ; i++) {
System.out.println(t.getName() + ":" + "第"+i+"次");
}
}
public static void main(String[] args) {
Test t=new Test();
Thread th=new Thread(t,"线程1");
Thread th1=new Thread(t,"线程2");
th.start();
th1.start();
}
}
效果图
线程2:第1次
线程1:第1次
线程2:第2次
线程1:第2次
线程2:第3次
线程1:第3次
3.Callable接口
Callable:能够获取线程执行结果
1)步骤
必须重写call方法
2)代码块演示
public class TestCall implements Callable<Object> {
//实现callable接口,可以泛型
public int ticket=10;
@Override
public Object call() {//用call方法实现返回值
return ticket;
}
public static void main(String[] args) throws Exception{
// TestCall t=new TestCall();
// try {
// Object call = t.call();
// } catch (Exception e) {
// e.printStackTrace();
// }这种不能直接实现多线程
TestCall t=new TestCall();
FutureTask task=new FutureTask(t);
//用FutureTask吧callable实现类装进来
Thread tt=new Thread(task);
//再把task放入线程对象中
tt.start();
//线程运行完之后获取返回值
Object o = task.get();
//get来接收返回值
System.out.println(o);
}
}
效果图
10
4.比较创建线程的方式
5.练习
public class Exercise implements Runnable{
public void run(){
Thread t = Thread.currentThread();
for (int i = 0; i <20 ; i++) {
System.out.println(1+i+"你好,来自线程"+t.getName());
}
}
public static void main(String[] args) {
Exercise e=new Exercise();
Thread t=new Thread(e);
Thread t1=new Thread(e);
t.start();
t1.start();
}
}
三.线程的状态
四.线程调度
线程调度指按照特定机制为多个线程分配CPU的使用权
1.线程优先级
- 线程优先级由1~10表示,1最低,默认优先级为5
- 优先级高的线程获得CPU资源的概率较大
public class Exercise extends Thread{
public void run(){
Thread t = Thread.currentThread();
for (int i = 0; i <3 ; i++) {
System.out.println(1+i+"你好,来自线程"+t.getName());
}
}
public static void main(String[] args) {
Exercise e=new Exercise();
Exercise e1=new Exercise();
e.setPriority(10);//一种用数字表达优先级
e1.setPriority(MIN_PRIORITY);//一种用字母表达优先级
e.start();
e1.start();
}
}
效果图
1你好,来自线程Thread-0
1你好,来自线程Thread-1
2你好,来自线程Thread-0
2你好,来自线程Thread-1
3你好,来自线程Thread-0
3你好,来自线程Thread-1
2.线程休眠
- 让线程暂时睡眠指定时长,线程进入阻塞状态
- 睡眠时间过后线程会再进入可运行状态
- millis为休眠时长,以毫秒为单位
- 调用sleep()方法需处理InterruptedException异常
public class Test extends Thread{
@Override
public void run() {//线程工作的主体
Thread t=Thread.currentThread();
for (int i = 1; i <=3 ; i++) {
System.out.println(t.getName() + ":" + "第"+i+"次");
try {
Thread.sleep(2000);//每次打印完一句延迟2秒再次打印
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test t=new Test();
t.start();
}
}
效果图
Thread-0:第1次
Thread-0:第2次
Thread-0:第3次
3.线程的强制运行
- 使当前线程暂停执行,等待其他线程结束后再继续执行本线程
- millis:以毫秒为单位的等待时长
- nanos:要等待的附加纳秒时长
- 需处理InterruptedException异常
public class Test extends Thread{
@Override
public void run() {//线程工作的主体
Thread t=Thread.currentThread();
for (int i = 1; i <=3 ; i++) {
System.out.println(t.getName() + ":" + "第"+i+"次");
}
}
public static void main(String[] args) throws InterruptedException {
Test t=new Test();
t.start();
for (int i = 1; i <4 ; i++) {
System.out.println(Thread.currentThread().getName()+"第"+i+"次");
if (i==2) t.join(1);
//如果某个线程里有join,在join时处于阻塞状态,
// 一旦等待参数里的毫秒数超时则该线程直接退出阻塞状态,又继续运行
//或者说join里的线程在join前一直是阻塞状态,
// 则等待参数里的毫秒数超时则不继续等待,
// 被join的线程继续运行
}
}
}
效果图
main第1次
Thread-0:第1次
main第2次
Thread-0:第2次
Thread-0:第3次
main第3次
4.线程的礼让
- 暂停当前线程,允许其他具有相同优先级的线程获得运行机会
- 该线程处于就绪状态,不转为阻塞状态
public class Test extends Thread{
@Override
public void run() {//线程工作的主体
Thread t=Thread.currentThread();
for (int i = 1; i <=3 ; i++) {
System.out.println(t.getName() + ":" + "第"+i+"次");
}
}
public static void main(String[] args) throws InterruptedException {
Test t=new Test();
t.start();
for (int i = 1; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"第"+i+"次");
if (i%2==0) Thread.yield();
}
}
}
效果图
main第1次
Thread-0:第1次
main第2次
Thread-0:第2次
main第3次
Thread-0:第3次
main第4次
练习
1.模拟多人爬山
- 每个线程代表一个人
- 可设置每人爬山速度
- 每爬完100米显示信息
- 爬到终点时给出相应提示
public class ClimbThread implements Runnable {
private int time;//爬100米需要的时间
private int num=0;//爬多少个100米
public ClimbThread(int time, int kilo) {
this.time = time;
this.num = kilo/100;
}
public int getTime() { return time; }
public void setTime(int time) { this.time = time; }
public int getNum() { return num; }
public void setNum(int num) { this.num = num; }
public void run(){
Thread t = Thread.currentThread();
while (num>0){
try {
t.sleep(this.time);//线程休眠的时间就是爬100米所需要的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getName()+"已经爬完100米!");
num--;
}
System.out.println(t.getName()+"到达终点!");
}
public static void main(String[] args) throws Exception{
System.out.println("************开始爬山************");
ClimbThread m1=new ClimbThread(1000,500);
Thread t1=new Thread(m1,"年轻人");
ClimbThread m2=new ClimbThread(3000,500);
Thread t2=new Thread(m2,"老年人");
t1.start();
t2.start();
}
}
效果图
************开始爬山************
年轻人已经爬完100米!
年轻人已经爬完100米!
老年人已经爬完100米!
年轻人已经爬完100米!
年轻人已经爬完100米!
年轻人已经爬完100米!
年轻人到达终点!
老年人已经爬完100米!
老年人已经爬完100米!
老年人已经爬完100米!
老年人已经爬完100米!
老年人到达终点!
2.线程的优先级
- 显示主线程、子线程默认优先级
- 将主线程设置为最高优先级、子线程设置为最低优先级并显示
public class Demo1 extends Thread{
@Override
public void run() { }
public static void main(String[] args) {
System.out.println("********显示默认优先级*********");
System.out.println("主线程名:"+Thread.currentThread().getName()+",优先级:"+Thread.currentThread().getPriority());
Demo1 d=new Demo1();
System.out.println("子线程名:"+d.getName()+",优先级:"+d.getPriority());
System.out.println("********修改默认优先级后*********");
Thread.currentThread().setPriority(10);
d.setPriority(1);
System.out.println("主线程名:"+Thread.currentThread().getName()+",优先级:"+Thread.currentThread().getPriority());
System.out.println("子线程名:"+d.getName()+",优先级:"+d.getPriority());
}
}
效果图
********显示默认优先级*********
主线程名:main,优先级:5
子线程名:Thread-0,优先级:5
********修改默认优先级后*********
主线程名:main,优先级:10
子线程名:Thread-0,优先级:1
3.模拟叫号看病
- 某科室一天需看普通号50个,特需号10个
- 特需号看病时间是普通号的2倍
- 开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高
- 当普通号叫完第10号时,要求先看完全部特需号,再看普通号
- 使用多线程模拟这一过程
public class Demo2 extends Thread {
@Override
public void run() {
Thread t=new Thread("特需号");
t.setPriority(7);
for (int i = 1; i <=10; i++) {
System.out.println(t.getName()+":"+i+"号病人在看病!");
try {
t.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Demo2 d1=new Demo2();
d1.start();
Thread t=new Thread("普通号");
for (int i =1; i <=50; i++) {
if (i==11){
try {
d1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t.setPriority(3);
System.out.println(t.getName()+":"+i+"号病人在看病!");
try {
t.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
效果图
普通号:1号病人在看病!
特需号:1号病人在看病!
普通号:2号病人在看病!
特需号:2号病人在看病!
普通号:3号病人在看病!
普通号:4号病人在看病!
特需号:3号病人在看病!
普通号:5号病人在看病!
普通号:6号病人在看病!
特需号:4号病人在看病!
普通号:7号病人在看病!
特需号:5号病人在看病!
普通号:8号病人在看病!
普通号:9号病人在看病!
特需号:6号病人在看病!
普通号:10号病人在看病!
特需号:7号病人在看病!
特需号:8号病人在看病!
特需号:9号病人在看病!
特需号:10号病人在看病!
普通号:11号病人在看病!
普通号:12号病人在看病!
普通号:13号病人在看病!
普通号:14号病人在看病!
普通号:15号病人在看病!
普通号:16号病人在看病!
普通号:17号病人在看病!
普通号:18号病人在看病!
普通号:19号病人在看病!
普通号:20号病人在看病!
普通号:21号病人在看病!
普通号:22号病人在看病!
普通号:23号病人在看病!
普通号:24号病人在看病!
普通号:25号病人在看病!
普通号:26号病人在看病!
普通号:27号病人在看病!
普通号:28号病人在看病!
普通号:29号病人在看病!
普通号:30号病人在看病!
普通号:31号病人在看病!
普通号:32号病人在看病!
普通号:33号病人在看病!
普通号:34号病人在看病!
普通号:35号病人在看病!
普通号:36号病人在看病!
普通号:37号病人在看病!
普通号:38号病人在看病!
普通号:39号病人在看病!
普通号:40号病人在看病!
普通号:41号病人在看病!
普通号:42号病人在看病!
普通号:43号病人在看病!
普通号:44号病人在看病!
普通号:45号病人在看病!
普通号:46号病人在看病!
普通号:47号病人在看病!
普通号:48号病人在看病!
普通号:49号病人在看病!
普通号:50号病人在看病!
五.线程的同步方法
1.同步方法
使用synchronized修饰的方法控制对类成员变量的访问
或者
案例(卖票)
public class Demo3 extends Thread {
public int left=5;
public int used=0;
@Override
public void run(){
while (true){
buy();
if (left<=0)break;
}
}
public synchronized void buy(){
if (left<=0)return;
left--;
used++;
System.out.println("恭喜"+Thread.currentThread().getName());
System.out.println("当前已卖:"+used+",余票为:"+left);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo3 d=new Demo3();
Thread t1=new Thread(d,"王");
Thread t2=new Thread(d,"张");
Thread t3=new Thread(d,"李");
t1.start();
t2.start();
t3.start();
}
}
效果图
恭喜王
当前已卖:1,余票为:4
恭喜李
当前已卖:2,余票为:3
恭喜李
当前已卖:3,余票为:2
恭喜李
当前已卖:4,余票为:1
恭喜张
当前已卖:5,余票为:0
2.同步代码块
使用synchronized关键字修饰的代码块
syncObject为需同步的对象,通常为this
效果与同步方法相同
1)案例
public class Demo3 extends Thread {
public int left=5;
public int used=0;
@Override
public void run(){
while (true){
if (left<=0)break;
synchronized (this){
if (left<=0)return;
left--;
used++;
System.out.println("恭喜"+Thread.currentThread().getName());
System.out.println("当前已卖:"+used+",余票为:"+left);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public synchronized void buy(){
if (left<=0)return;
left--;
used++;
System.out.println("恭喜"+Thread.currentThread().getName());
System.out.println("当前已卖:"+used+",余票为:"+left);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo3 d=new Demo3();
Thread t1=new Thread(d,"王");
Thread t2=new Thread(d,"张");
Thread t3=new Thread(d,"李");
t1.start();
t2.start();
t3.start();
}
}
效果图
恭喜王
当前已卖:1,余票为:4
恭喜李
当前已卖:2,余票为:3
恭喜张
当前已卖:3,余票为:2
恭喜李
当前已卖:4,余票为:1
恭喜李
当前已卖:5,余票为:0
2)多个并发线程访问同一资源的同步代码块时
- 同一时刻只能有一个线程进入synchronized(this)同步代码块
- 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
- 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
3)同步代码块的优缺点
**优点:**解决了多线程的数据安全问题
**缺点:**当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
六.线程安全的类型
1.线程安全的类型
查看ArrayList类的add()方法定义
ArrayList类的add()方法为非同步方法
当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题
2.常见类型对比
Hashtable && HashMap
- Hashtable
- 继承关系
实现了Map接口,Hashtable继承Dictionary类 - 线程安全,效率较低
- 键和值都不允许为null
- HashMap
- 继承关系
实现了Map接口,继承AbstractMap类 - 非线程安全,效率较高
- 键和值都允许为null
StringBuffer && StringBuilder
前者线程安全,后者非线程安全
3.练习
1)模拟接力赛跑
- 多人参加1000米接力跑
- 每人跑100米,换下个选手
- 每跑10米显示信息
public class Demo3 implements Runnable{
public int kilo=1000;
@Override
public void run() {
while (true){
if (kilo<=0) break;
go();
}
}
public synchronized void go(){
System.out.println(Thread.currentThread().getName()+"拿到接力棒!");
for (int i = 10; i <=100; i+=10) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
}
kilo-=100;
}
public static void main(String[] args) {
Demo3 d=new Demo3();
Thread t1=new Thread(d,"1号选手");
Thread t2=new Thread(d,"2号选手");
Thread t3=new Thread(d,"3号选手");
Thread t4=new Thread(d,"4号选手");
Thread t5=new Thread(d,"5号选手");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
效果图
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
1号选手拿到接力棒!
1号选手跑了10米
1号选手跑了20米
1号选手跑了30米
1号选手跑了40米
1号选手跑了50米
1号选手跑了60米
1号选手跑了70米
1号选手跑了80米
1号选手跑了90米
1号选手跑了100米
5号选手拿到接力棒!
5号选手跑了10米
5号选手跑了20米
5号选手跑了30米
5号选手跑了40米
5号选手跑了50米
5号选手跑了60米
5号选手跑了70米
5号选手跑了80米
5号选手跑了90米
5号选手跑了100米
4号选手拿到接力棒!
4号选手跑了10米
4号选手跑了20米
4号选手跑了30米
4号选手跑了40米
4号选手跑了50米
4号选手跑了60米
4号选手跑了70米
4号选手跑了80米
4号选手跑了90米
4号选手跑了100米
2号选手拿到接力棒!
2号选手跑了10米
2号选手跑了20米
2号选手跑了30米
2号选手跑了40米
2号选手跑了50米
2号选手跑了60米
2号选手跑了70米
2号选手跑了80米
2号选手跑了90米
2号选手跑了100米
3号选手拿到接力棒!
3号选手跑了10米
3号选手跑了20米
3号选手跑了30米
3号选手跑了40米
3号选手跑了50米
3号选手跑了60米
3号选手跑了70米
3号选手跑了80米
3号选手跑了90米
3号选手跑了100米
2)网络购票
- “桃跑跑”、“张票票”、“黄牛党”共同抢10张票
- 限“黄牛党”只能抢一张票
public class Demo4 extends Thread {
public int left=10;
public int used=0;
@Override
public void run(){
while (true){
if (left<=0)break;
buy();
if (Thread.currentThread().getName().equals("黄牛党"))return;
}
}
public synchronized void buy(){
if (left<=0)return;
left--;
used++;
System.out.println(Thread.currentThread().getName()+"抢到第"+used+"票,剩余"+left+"张票!");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo4 d=new Demo4();
Thread t1=new Thread(d,"桃跑跑");
Thread t2=new Thread(d,"张票票");
Thread t3=new Thread(d,"黄牛党");
t1.start();
t2.start();
t3.start();
}
}
效果图
桃跑跑抢到第1票,剩余9张票!
张票票抢到第2票,剩余8张票!
桃跑跑抢到第3票,剩余7张票!
黄牛党抢到第4票,剩余6张票!
桃跑跑抢到第5票,剩余5张票!
张票票抢到第6票,剩余4张票!
桃跑跑抢到第7票,剩余3张票!
张票票抢到第8票,剩余2张票!
桃跑跑抢到第9票,剩余1张票!
张票票抢到第10票,剩余0张票!
七.concurrent并发包
java.util.concurrent 提供了一系列方便并发编程的类
- 阻塞队列BlockingQueue
- 延迟队列DelayQueue
- 闭锁CountDownLatch
- 线程池ExecutorService
……