线程举例:
String家族:
StringBuilder线程非同步,不安全,但是效率高。
StringBuffer线程同步,安全性高
复习总结:
1.进程和线程:操作系统中运行的每一个任务对应一个进程,当一个程序进入内存运行时,即变成了一个进程。进程是操作系统进行组员分配和调度的一个独立单元。
线程是进程的执行单元。
2.线程的状态:新生态;就绪态;运行态;阻塞态;死亡态
3.start和run方法:让线程开始新的任务的时候,我们调用start方法,让cpu自己执行run方法,只需要调用start方法告诉cpu自己已经准备好了。
4.创建线程的两种方法:
继承Thread类。重写run方法
实现Runnable接口创建线程
5.sleep()方法:使当前线程让出cpu,留出一定时间给其他线程执行的机会。
注意调用sleep方法时,要处理异常
6.设置线程的优先级setPriority:
设置优先级只是说让这个线程可以抢到cpu时间片的概率增大,但是不意味着一定可以抢到cpu时间片,值是[0,10],默认是5
7.线程礼让:指当前运行状态的线程释放自己的cpu资源,由运行状态回到就绪状态,然后和另一个线程重新开始一起抢夺cpu时间片,谁抢到还不一定
8.临界资源抢夺问题:多个线程共享的资源成为临界资源。
多线程同时去访问临界资源的时候,可能会出现问题。
9.为了解决临界资源问题,出现了锁。当一个线程在访问临界资源的时候,上一把锁,其他线程先等待,前一个线程释放锁以后,其他线程才可以操作临界资源。
锁synchronized有两种方法:
同步代码块:在代码块上增加synchronized 修饰符
同步方法:类方法增加synchronized 修饰符
10.死锁
11.死锁解决办法使用wait方法,唤醒wait使用notify和notifyAll方法
一. 线程的基础
1.几个重要的概念:
程序:可以理解为是一组静态的代码
进程:正在进行的程序,静态的代码 运行起来
线程:正在执行程序中的小单元
线程是进程中的小单元。
2.举个例子理解一下:
聚餐
聚餐之前, 班级大扫除。
扫除需要分配任务,任务写在纸上,列一个清单。这是一个静态的。
一声令下,开始扫除,班级扫除这件事就是-----进程。
每一个同学都做自己的事情,并发执行,互相不影响。
3.如何在Java中创建线程: 让线程执行,多线程
掌握每一个线程的几种不同状态,及状态之间如何切换:
如何实现线程
自定义一个类
继承Thread 实现Runnable
重写run方法
调用start方法 让线程进入就绪状态 需要注意的是start方法Thread类中
4.实现线程的过程
1.自己描述一个类
2.继承父类Thread
3.重写run方法
4.new一个线程对象 调用start()方法 让线程进入就绪状态
实现一个跑步的小例子
多个人同时跑步:苏炳添;博尔特 ;加特林
//一个继承线程的类
public class RunningMan extends Thread {
private String name;
//构造方法,两个,想用哪个用哪个。
public RunningMan(){}
public RunningMan(String name){
this.name=name;
}
//重写run方法
public void run(){
for(int i=1;i<=100;i++){
System.out.println(this.name+"跑到第"+i+"米啦");
}
}
}
主方法测试一下:
public class TestMain {
public static void main(String[] args){
//1.创建一个线程对象
RunningMan r1 = new RunningMan("苏炳添");
RunningMan r2 = new RunningMan("博尔特");
RunningMan r3 = new RunningMan("加特林");
//2.调用start方法 让线程进入就绪状态 按顺序逐个执行
r1.start();//从Thread类中继承过来的方法
r2.start();
r3.start();
//如果掉run,那就是单线程了,按照顺序执行了。
//我们调用stat是告诉线程,我们准备好了,让cpu自己run。
}
}
实现过程很简单,但是有没有什么问题?
Java中 继承,单继承
extends
Person extends Animal
我想让人去多线程,但是发现java是单继承,人已经继承了动物,就不能再继承线程了,那咋么去多线程呢?所以有第二种,多线程。
5.实现线程的另一种方法过程
1.自己描述一个类
2.实现一个父接口Runnable
3.重写run方法
4.new一个线程对象 需要创建Thread将自己的对象包起来 然后调用start()
public class RunningMan implements Runnable {
private String name;
public RunningMan(){}
public RunningMan(String name){
this.name=name;
}
//重写run方法
public void run(){
for(int i=1;i<=100;i++){
System.out.println(this.name+"跑到第"+i+"米啦");
}
}
}
主方法
public class TestMain {
public static void main(String[] args){
//1.创建一个线程对象
RunningMan r1 = new RunningMan("苏炳添");
RunningMan r2 = new RunningMan("博尔特");
RunningMan r3 = new RunningMan("加特林");
//2.调用start方法 让线程进入就绪状态 按顺序逐个执行
//我们必须调用start,但是不是线程没有start,所以我们构建一个线程对象。
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
Thread t3 = new Thread(r3);
t1.start();
t2.start();
t3.start();
}
}
二. 线程之生产消费者模型
1.线程并发访问时,可能会产生线程安全问题
生产消费者模型。生产者,生产产品存入仓库,消费者消费产品,去仓库中取走产品。需要三个类,仓库,消费者以及生产者。
想验证一下,因为消费者是多线程访问仓库,是否会发生抢夺资源的现象发生。
仓库类:
public class Warehouse {
//仓库里面的集合 存放元素
private ArrayList<String> list = new ArrayList<>();
//向集合内添加元素的方法
public void add(){
if(list.size()<20) {
list.add("a"); //仓库元素够20个,就不再往里面存元素了。
}else{
return;//让方法执行到这里就结束方法
}
}
//从集合内获取元素的方法
public void get(){
if(list.size()>0) {
list.remove(0); //拿走元素
}else{
return;//集合越界的问题
}
}
}
生产者类:因为生产者是多线程生产的,所以要继承Thread。
public class Producer extends Thread {
//为了保证生产者 和消费者使用同一个仓库对象 添加一个属性
private Warehouse house;
public Producer(Warehouse house){
this.house=house;
}
//生产者的run方法 一直向仓库内添加元素
public void run(){
//方法重写,要求名字和参数列表必须一致,所以不能传参,所以我们可以考虑将house放到属性里。
while(true){ //死循环,一直往里放
house.add();
System.out.println("生产者存入了一件货物");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者:
public class Consumer extends Thread{
//为了保证生产者 和消费者使用同一个仓库对象 添加一个属性
private Warehouse house;
public Consumer(Warehouse house){
this.house=house;
}
//消费者的方法 一直从仓库内获取元素
public void run(){
while(true){
house.get();
System.out.println("消费者拿走了一件货物");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
多线程并发很有可能两个都来拿元素,很有可能发生资源抢劫。
右下角这种方式可能会产生线程安全问题,只剩下一个库存了,两个消费者恰好判断出仓库有文件,又同时一起取了文件,但是只有一个文件,所以会出现异常。也就是线程不安全。
写一个主方法测试一下:
public class TestMain {
public static void main(String[] args){
Warehouse house = new Warehouse();//里面有一个ArrayList线程非安全
Producer p = new Producer(house);
Consumer c1 = new Consumer(house);
Consumer c2 = new Consumer(house);
//以上三个线程
p.start();
c1.start();
c2.start();
}
}
运行时,会发生异常。说明刚才的分析没有错误。多线程并发,会发生安全问题。
通过这个模型 成功的演示出了 线程安全的问题
两个消费者 同时访问同一个仓库对象 仓库内只有一个元素的时候
两个消费者并发访问 会有可能产生抢夺资源的问题
2.自己解决一下线程安全的问题
- 让仓库对象被线程访问的时候 仓库对象被锁定;
- 仓库对象只能被一个线程访问 其他的线程处于等待状态。
- 使用特征修饰符synchronized,也称之为线程安全锁 , 同步 , 一个时间点只有一个线程访问。
- 两种形式写法:
1.将synchronized关键字 放在方法的结构上
public synchronized void get(){}
锁定的是调用方法时的那个对象
2.将synchronized关键字,放在方法(构造方法 块)的内部
public void get(){
好多代码
synchronized(对象,写this就行了){
好多代码 //只有这句比较重要,只有执行这块代码的时候才锁定对象。
}
好多代码
}
所以修改仓库类:
public class Warehouse {
//单例设计模式
//仓库里面的集合 存放元素
private ArrayList<String> list = new ArrayList<>();
//向集合内添加元素的方法
public synchronized void add(){
if(list.size()<20) {
list.add("a");
}else{
//return;//让方法执行到这里就结束方法
}
}
//从集合内获取元素的方法
public synchronized void get(){
if(list.size()>0) {
list.remove(0);//集合越界的问题
}else{
//return;
try {
this.notifyAll();
this.wait();//仓库对象调用wait 不是仓库对象等待 访问仓库的消费者线程进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.我们觉得return不是很好
对于生产者,生产发现超过20,不是不在生产了,而是等一会,一会在继续生产,所以直接return不好。
对于消费者,发现数量为0 的时候,也不是不再取了,而是等一会,一会再取,所以return不好。
- 应该让线程的不同状态来回切换
执行 等待 执行 等待 -
wait(); Object类中的方法
对象.wait(); 不是当前的这个对象wait
是 访问当前这个对象的线程wait
将return换成如下的代码:
try {
this.wait();//仓库调用wait 不是仓库对象等待 访问仓库的生产者线程进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
但是单纯这样,会产生一个类似假死状态
所有的线程都进入等待状态 没有线程做事。所以可以使用下面的notify方法。
- notify和notifyAll Object类中的方法
代码为:
try {
this.notifyAll();
this.wait();//仓库调用wait 不是仓库对象等待 访问仓库的生产者线程进入等待状态。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
但是唤醒是将消费者和生产者都唤醒了,生产者和消费者哪一个先开始,我们还是不确定,我们是希望生产者先开始,但是这不是我们能决定的。所以考虑设置优先级,如下:
- p.setPriority(10); p.getPriority();
public class TestMain {
public static void main(String[] args){
Warehouse house = new Warehouse();//里面有一个ArrayList线程非安全
Producer p = new Producer(house);
//设置线程的优先级别1-10
p.setPriority(10); //给生产者级别高些,优先抢资源。
Consumer c1 = new Consumer(house);
Consumer c2 = new Consumer(house);
p.start();
c1.start();
c2.start();
}
}
笔试题
- 程序 进程 线程 概念的区别
- 线程的创建方式
- 线程的几种状态 如何切换
- sleep方法 ; wait方法的区别:
1.类: Thread类 ;Object类
2.调用: 静态 类名. ; 对象.
3.理解: 哪个位置调用 哪个线程等待 ;对象调用方法 访问对象的其他线程等待
4.唤醒:不需要别人 ;需要其他对象调用notify唤醒
5.锁:不会释放锁 ;等待后会释放锁
三、join方法&死锁&Timer
1.join方法:
设计一个模型
1.有两个线程 One Two two加入到one里面
2.设计模型时候 two线程在one的run里面创建 保证两个有先后顺序
3.two.join(); 无参数0 有参数2000
A跑几步,B跑几步,然后遇到独木桥,然后让B先过独木桥。
但是,我们以前只能让线程进入就绪状态,不能控制线程的顺序,现在,我们想让A加入到B线程中,怎么做呢?
线程一:
public class ThreadOne extends Thread {
public void run(){
System.out.println("thread-one start");
ThreadTwo two = new ThreadTwo();
two.start();//一线程运行的时候,二线程才创建
//所以一线程应该执行的比二线程早,控制了顺序。
try {
two.join();//线程2加入线程1里面
//如果填一个时间,那么一线程就等这么长时间,就执行了,就不管二线程是否执行完毕了
// two.join(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}//一线程等待二线程,二线程休息了5000ms
System.out.println("thread-one end");
}
}
线程二:
public class ThreadTwo extends Thread {
public void run(){
System.out.println("thread-two start");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-two end");
}
}
所以主方法
public class TestMain {
public static void main(String[] args){
ThreadOne one = new ThreadOne();
one.start();
}
}
结果:
thread-one start
thread-two start
thread-two end
thread-one end
在two执行的过程中,one等待的过程中,three将two对象锁定,那么one没能推two,one会怎样呢?会不会一直在那一直等待呢?
Three:
public class ThreadThree extends Thread{
private ThreadTwo two;
public ThreadThree(ThreadTwo two){
this.two=two;
}
public void run(){
//在two执行的过程中 one等待的过程中 three将two对象锁定
System.out.println("thread-three start");
synchronized(two){
System.out.println("two is locked");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("two is free");
}
System.out.println("thread-three end");
}
}
整体过程,one线程先执行,one线程执行的时候建立了two线程,然后遇到独木桥,one让two先执行,two.join(2000),one在这数数,2000ms。two执行需要5000ms,two还没有执行完毕,one的2000ms已经数完了,在one想要推two的时候,发现two被three拿走了锁定了,tow被three老鹰叼走了。所以one没能推two,one就在这一直等着,等到two回来,然后推two一把,才能继续执行。
线程二:
public class ThreadTwo extends Thread {
public void run(){
System.out.println("thread-two start");
ThreadThree three = new ThreadThree(this);
three.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-two end");
}
}
结果:
one 启动
two 启动
three 启动
two就join啦
2000之后 one想要将two从自己的线程内剔除
发现 two对象不在自己的手里 因为对象被three线程锁定啦 10000
one只能等待 threee将two对象释放后 才能踢掉。
结果:
thread-one start
thread-two start
thread-three start
thread-two locked
thread-two end two虽然被叼走了,但是实际上还在执行,所以他先执行完毕了因为只需要5000ms就能执行完毕,二别锁定10000ms,所以two可以执行完毕
two is free
thread-three end
thread-one end
synchronized锁 非常的厉害,
一旦对象被锁定 不释放的情况下 其他的对象都需要等待,
有可能会产生一个死锁的效果。
2.死锁
一个小箱子,有俩个元素。两个人都想去拿东西,而且拿两个。可能一人拿了一个,想等着对方释放,但是对方又不释放,产生的现象称之为死锁。
死锁的效果
模拟一个模型 演示死锁。哲学家就餐的问题,上图左侧的桌子。有可能产生死锁,但是也有可能有的人快,就拿到了一双。
筷子只有属性:
public class Chopstick {
private int num;
public Chopstick(int num){
this.num=num;
}
public int getNum(){
return this.num;
}
}
哲学家:
public class Philosopher extends Thread{
private String pname;//哲学家的名字
private Chopstick left;
private Chopstick right;
//private long time;
public Philosopher(String pname,Chopstick left,Chopstick right,long time){
this.pname = pname;
this.left = left;
this.right = right;
}
public void run(){
synchronized (left) {
System.out.println(this.pname+"拿起了左手边的"+this.left.getNum()+"筷子");
synchronized (right){
System.out.println(this.pname+"拿起了右手边的"+this.right.getNum()+"筷子");
System.out.println(this.pname+"开始狼吞虎咽的吃起来啦");
}
}
}
}
主方法:
public class TestMain {
public static void main(String[] args){
Chopstick c1 = new Chopstick(1);
Chopstick c2 = new Chopstick(2);
Chopstick c3 = new Chopstick(3);
Chopstick c4 = new Chopstick(4);
Philosopher p1 = new Philosopher("哲学家a",c2,c1);
Philosopher p2 = new Philosopher("哲学家b",c3,c2);
Philosopher p3 = new Philosopher("哲学家c",c4,c3);
Philosopher p4 = new Philosopher("哲学家d",c1,c4);
p1.start();
p2.start();
p3.start();
p4.start();
}
}
//有可能死锁,一人拿到了一支筷子,但是也有可能不产生死锁问题,有人拿的快,吃到了饭。
想要解决死锁的问题:
1.礼让---->产生时间差
2.不要产生对象公用的问题
哲学家类:
public class Philosopher extends Thread{
private String pname;//哲学家的名字
private Chopstick left;
private Chopstick right;
private long time; //上来先睡一会儿
public Philosopher(String pname,Chopstick left,Chopstick right,long time){
this.pname = pname;
this.left = left;
this.right = right;
this.time = time;
}
public void run(){
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (left) {
System.out.println(this.pname+"拿起了左手边的"+this.left.getNum()+"筷子");
synchronized (right){
System.out.println(this.pname+"拿起了右手边的"+this.right.getNum()+"筷子");
System.out.println(this.pname+"开始狼吞虎咽的吃起来啦");
}
}
}
}
主方法:
public class TestMain {
public static void main(String[] args){
Chopstick c1 = new Chopstick(1);
Chopstick c2 = new Chopstick(2);
Chopstick c3 = new Chopstick(3);
Chopstick c4 = new Chopstick(4);
Philosopher p1 = new Philosopher("哲学家a",c2,c1,0);
Philosopher p2 = new Philosopher("哲学家b",c3,c2,3000);
Philosopher p3 = new Philosopher("哲学家c",c4,c3,0);
Philosopher p4 = new Philosopher("哲学家d",c1,c4,3000);
//对面的人可以同时吃,所以可以设置睡觉时间相同。
p1.start();
p2.start();
p3.start();
p4.start();
}
}