本帖最后由 小石姐姐 于 2019-5-17 11:12 编辑
一、进程和线程进程:是正在运行的应用程序 是系统进行资源分配和调用的独立单位 每一个进程都有它自己的内存空间和系统资源 线程:是进程中的单个顺序控制流,是一条执行路径 单线程:一个进程如果只有一条执行路径,则称为单线程(以前所学的都是单线程) 多线程:一个进程如果有多条执行路径,则称为多线程 二、继承Thread类的方式实现多线程方法介绍 [td]方法名 | 说明 | void run() | 在线程开启后,此方法被调用执行,里面写需要执行的代码 | void start() | 使此线程开始执行(开启线程),Java虚拟机会自动调用run()方法,使线程进入就绪状态 | 实现步骤 定义一个类MyThread继承Thread类 在MyThread类中重写run()方法 创建MyThread类的对象 启动线程[Java] 纯文本查看 复制代码 //定义一个类MyThread继承Thread类
public class MyThread extends Thread{
@override
//在MyThread类中重写run()方法
public void run(){
for(int i = 0;i < 100; i++){
System.out.println(i);
}
}
}
public class Test{
public static void main(String[] args){
//创建MyThread类的对象
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
//这只是一个不同的方法调用,
//mt1.run();
//mt2.run();
//启动线程
mt1.start();
mt2.start();
}
} 两个小问题 为什么要重写run()方法? 因为run()方法时用来封装需要被线程执行的代码 run()方法和start()方法的区别? run():封装线程执行的代码,直接调用,相当于普通方法的调用 start():启动线程;然后由JVM调用此线程的run()方法 三、设置和获取线程名称方法介绍[td]方法名 | 说明 | void setName(String name) | 将此线程的名称更改为参数name | String getName() | (获取)返回此线程的名称 | Thread currentThread() | 返回对当前正在执行的线程对象的引用 | Thread() | 无参构造方法 | Thread(String name) | 有参构造方法,将参数name传入给当前线程 |
[Java] 纯文本查看 复制代码 public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name){
super(name);
}
@override
public void run(){
for(int i = 0; i < 100; i++){
System.out.println(getName() + " "+ i);
}
}
}
package thread;
//实现多线程的第一种方式:
// 继承Thread类,重写run方法,创建对象,调用start方法
public class ThreadTest1 {
public static void main(String[] args) {
//创建Thread类的子类MyThread类
/*MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();*/
//通过有参构造方法,调用父类构造方法,将参数name传入给调用该方法的线程
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
//这个run方法是当普通方法使用的
/*mt1.run();
mt2.run();*/
//setName(),设置线程名称
/*mt1.setName("姓名");
mt2.setName("居住地");*/
//getName(),没有设置线程名称,系统会默认设置一个线程名称,
// 一般写在run方法内部的输出语句,为了更加明确的看到是哪个线程在运行
/*System.out.println(mt1.getName());//Thread-0,没有设置线程名称,系统会默认设置一个线程名称
System.out.println(mt2.getName());//Thread-1*/
//获取当前正在执行的线程的名称--这是在获取main方法里的线程名称
System.out.println(Thread.currentThread().getName());
//启动线程,
mt1.start();
mt2.start();
}
}
四、线程优先级线程调度两种调度方式 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个, 优先级高的线程获取的 CPU 时间片相对多一些 Java使用的是抢占式调度模型 随机性 假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就 是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的, 还有,不是优先级越高,就一定要执行该线程,优先级越高,说明他得到时间片(CPU的使用权)的概率越大 优先级相关方法 [td]方法名 | 说明 | final int getPriority() | (获取)返回此线程的优先级 | final void setPriority(int newPriority) | 更改此线程的优先级 | static int NORM_PRIORITY | 当前线程的默认优先级(5),优先级的范围(1-10) |
[Java] 纯文本查看 复制代码 public class ThreadPriority extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
package priority;
//线程的优先级练习,优先级越高,获取CPU的使用权概率越高
public class PriorityTest {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
//获取当前线程的优先级
/*System.out.println(mt1.getPriority());//5
System.out.println(mt2.getPriority());//5*/
//设置优先级(1-10)
mt1.setPriority(10);
mt2.setPriority(3);
//获取当前线程的优先级
System.out.println(mt1.getPriority());//10
System.out.println(mt2.getPriority());//3
//获取线程的最大优先级
System.out.println(Thread.MAX_PRIORITY);
//获取线程的最小优先级
System.out.println(Thread.MIN_PRIORITY);
//获取线程的默认优先级
System.out.println(Thread.NORM_PRIORITY);
//启动线程
mt1.start();
mt2.start();
}
}
五、线程控制相关方法[td]方法名 | 说明 | static coid sleep(long millis) | 使当前正在执行的线程停留(睡眠)(暂停执行)指定的毫秒数 | void join() | 等待这个线程死亡(执行完毕) | void setDaemon(boolean on) | 参数on为true时,将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 | sleep()练习[Java] 纯文本查看 复制代码
package threadcontrol;
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package threadcontrol;
//sleep()练习
public class SleepTest {
public static void main(String[] args) {
MyThread mt1 = new MyThread("刘备");
MyThread mt2 = new MyThread("曹操");
MyThread mt3 = new MyThread("孙权");
mt1.start();
mt2.start();
mt3.start();
}
}
join()练习[Java] 纯文本查看 复制代码 package threadcontrol;
public class JoinTest {
public static void main(String[] args) {
MyThread mt1 = new MyThread("康熙");
MyThread mt2 = new MyThread("四阿哥");
MyThread mt3 = new MyThread("八阿哥");
mt1.start();
try {
mt1.join();//能使当前线程执行完毕,其它线程才能执行
} catch (InterruptedException e) {
e.printStackTrace();
}
mt2.start();
mt3.start();
}
}
setDaemon()练习[Java] 纯文本查看 复制代码 package threadcontrol;
public class SetDaemonTest {
public static void main(String[] args) {
MyThread mt1 = new MyThread("关羽");
MyThread mt2 = new MyThread("张飞");
Thread.currentThread().setName("刘备");
//setDaemon(),当进程里都是守护线程时,守护线程将会结束,不会立即停止运行
mt1.setDaemon(true);
mt2.setDaemon(true);
mt1.start();
mt2.start();
mm();
}
public static void mm(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
六、线程的生命周期
线程的五种状态:新建(实例化Thread类)——就绪(调用start方法)——运行(该线程抢夺到CPU使用权,被其他线程抢到CPU使用权,该线程会回到就绪状态)——死亡(功能执行完毕)——堵塞(sleep方法,或者其它堵塞方式,这时该线程会再次进入就绪状态) 七、实现Runnable接口的方式实现多线程Thread构造方法[td]方法名 | 说明 | Thread(Runnable target) | 传入Runnable接口的实现类对象 | Thread(Runnable target, String name) | 传入Runnable接口的实现类对象和线程名称(String) | 实现步骤
定义一个类MyRunnable实现Runnable接口
在MyRunnable类中重写run()方法
创建MyRunnable类的对象
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
启动线程
[HTML] 纯文本查看 复制代码 package runnable;
//定义一个类MyRunnable实现Runnable接口
public class MyRunnable implements Runnable {
//- 在MyRunnable类中重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package runnable;
/*
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
*/
public class Test {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable mr = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread(Runnable target)
Thread t1 = new Thread(mr);
t1.setName("飞机");
//Thread(Runnable target, String name)
Thread t2 = new Thread(mr,"地铁");
//启动线程
t1.start();
t2.start();
}
}
八、卖票[Java] 纯文本查看 复制代码 package runnable;
//定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
public class SellTicket implements Runnable {
private int ticket = 100;
/*
在SellTicket类中重写run()方法实现卖票,代码步骤如下
- 判断票数大于0,就卖票,并告知是哪个窗口卖的
- 卖了票之后,总票数要减1
- 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
*/
@Override
public void run(){
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
}
}
}
}
package runnable;
/*
- 案例需求
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
- 实现步骤
- 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
- 在SellTicket类中重写run()方法实现卖票,代码步骤如下
- 判断票数大于0,就卖票,并告知是哪个窗口卖的
- 卖了票之后,总票数要减1
- 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
- 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
- 创建SellTicket类的对象
- 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
- 启动线程
-
*/
//定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
public class SellTicketTest {
public static void main(String[] args) {
//创建SellTicket类的对象
SellTicket st = new SellTicket();
//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
Thread t1 = new Thread(st,"售票窗口1");
Thread t2 = new Thread(st,"售票窗口2");
Thread t3 = new Thread(st,"售票窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
九、卖票案例的思考卖票出现了问题
相同的票出现了多次
出现了负数的票
问题产生原因
线程执行的随机性导致的
[Java] 纯文本查看 复制代码 public class SellTicket implements Runnable {
private int tickets = 100;
@Override
public void run() {
//相同的票出现了多次
// while (true) {
// //tickets = 100;
// //t1,t2,t3
// //假设t1线程抢到CPU的执行权
// if (tickets > 0) {
// //通过sleep()方法来模拟出票时间
// try {
// Thread.sleep(100);
// //t1线程休息100毫秒
// //t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程休息100毫秒
// //t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// //假设线程按照顺序醒过来
// //t1抢到CPU的执行权,在控制台输出:窗口1正在出售第100张票
// System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
// //t2抢到CPU的执行权,在控制台输出:窗口2正在出售第100张票
// //t3抢到CPU的执行权,在控制台输出:窗口3正在出售第100张票
// tickets--;
// //如果这三个线程还是按照顺序来,这里就执行了3次--的操作,最终票就变成了97
// }
// }
//出现了负数的票
while (true) {
//tickets = 1;
//t1,t2,t3
//假设t1线程抢到CPU的执行权
if (tickets > 0) {
//通过sleep()方法来模拟出票时间
try {
Thread.sleep(100);
//t1线程休息100毫秒
//t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程休息100毫秒
//t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//假设线程按照顺序醒过来
//t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票
//假设t1继续拥有CPU的执行权,就会执行tickets--;操作,tickets = 0;
//t2抢到了CPU的执行权,在控制台输出:窗口1正在出售第0张票
//假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets = -1;
//t3抢到了CPU的执行权,在控制台输出:窗口3正在出售第-1张票
//假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets = -2;
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
十、同步代码块解决数据安全问题[Java] 纯文本查看 复制代码 public class SellTicket implements Runnable {
private int tickets = 100;
//private Object obj = new Object();
@Override
public void run() {
while (true) {
//tickets = 100;
//t1,t2,t3
//假设t1抢到了CPU的执行权
//假设t2抢到了CPU的执行权
//synchronized (obj) {
synchronized (this) {//代表把当前方法锁起来,只有第一个抢到使用权的线程使用,使用完毕后,轮到下一个线程
//t1进来后,就会把这段代码给锁起来
if (tickets > 0) {
try {
Thread.sleep(100);
//t1休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口1正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--; //tickets = 99;
}
}
//t1出来了,这段代码的锁就被释放了
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
十一、同步方法解决数据安全问题[Java] 纯文本查看 复制代码 package synchronizedtest;
public class Person {
public void test1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//非静态同步方法默认锁this--当前线程,锁不住非静态同步方法
public synchronized void test2() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void test3() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//静态同步方法默认锁类名.class--Person.class
public static synchronized void test4(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static synchronized void test5(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package synchronizedtest;
public class MyThread1 extends Thread {
private Person p;
public MyThread1(){}
public MyThread1(Person p) {
this.p = p;
}
public MyThread1(String name, Person p) {
super(name);
this.p = p;
}
public Person getP() {
return p;
}
public void setP(Person p) {
this.p = p;
}
@Override
public void run() {
// p.test1();
// p.test2();
Person.test5();
}
}
package synchronizedtest;
public class MyThread2 extends Thread {
private Person p;
public MyThread2(){}
public MyThread2(Person p) {
this.p = p;
}
public MyThread2(String name, Person p) {
super(name);
this.p = p;
}
public Person getP() {
return p;
}
public void setP(Person p) {
this.p = p;
}
@Override
public void run() {
// p.test2();
// p.test3();
Person.test4();
}
}
package synchronizedtest;
public class Test {
public static void main(String[] args) {
Person p = new Person();
MyThread1 m1 = new MyThread1("线程1",p);
MyThread1 m2 = new MyThread1("线程2",p);
m1.start();
m2.start();
}
}
十二、线程安全的类- StringBuffer--StringBuilder
- Vector--ArrayList
- Hashtable--HashMap
- Collections.synchronizedMap-List-Set,可以把非安全线程,转换为安全线程
十三、Lock锁虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化 [td]方法名 | 说明 | ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法 [td]方法名 | 说明 | void lock() | 获得锁(写在需要锁的功能的前面) | void unlock() | 释放锁(写在需要锁的功能的前面,表示把该功能给锁住了) |
[Java] 纯文本查看 复制代码 public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
} finally {
lock.unlock();
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
} 十四、生产者和消费者模式概述- 概述
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。 所谓生产者消费者问题,实际上主要是包含了两类线程: 一类是生产者线程用于生产数据 一类是消费者线程用于消费数据 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为 生产者——共享数据区域——消费者 Object类的等待和唤醒方法 [td]方法名 | 说明 | void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 | void notify() | 唤醒正在等待对象监视器的单个线程 | void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
十五、生产者和消费者案例案例需求生产者消费者案例中包含的类: 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下 ①创建奶箱对象,这是共享数据区域 ②创建消费者创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作 ③对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作 ④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递 ⑤启动线程 Box类——奶箱 [Java] 纯文本查看 复制代码 package productercustomer;
//奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
public class Box {
private int mile = 0;
private boolean state = false;//用来代表奶箱有没有牛奶
//获取牛奶
public synchronized void getMile() {
//如果没有牛奶,就等待有牛奶
if (!state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果把牛奶拿走了,就把状态改成没有牛奶
System.out.println("程序员拿了第" + this.mile + "瓶奶");
state = false;
//唤醒送奶工存储牛奶
notifyAll();
}
//存储牛奶
public synchronized void setMile(int mile) {
//如果有牛奶,就等待程序员来拿牛奶
if (state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有牛奶就添加牛奶
this.mile = mile;
System.out.println("送奶工放入第" + this.mile + "瓶奶");
//存储完牛奶后,就把状态改成有牛奶的状态
state = true;
//唤醒程序员来拿牛奶
notifyAll();
}
}
Producter类——送奶工 [Java] 纯文本查看 复制代码 package productercustomer;
//生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
public class Producter implements Runnable {
private Box b;
public Producter(Box b){
this.b = b;
}
@Override
public void run() {
//让送奶工总共送一个月的奶,一天一瓶
for (int i = 1; i <= 30; i++) {
b.setMile(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Customer类——程序员 [Java] 纯文本查看 复制代码 package productercustomer;
public class Customer implements Runnable {
private Box b;
public Customer(Box b){
this.b = b;
}
@Override
public void run() {
//让程序员那边送多少奶,我就拿多少瓶
while (true) {
b.getMile();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类 [Java] 纯文本查看 复制代码 package productercustomer;
public class BoxTest {
public static void main(String[] args) {
Box b =new Box();
Producter p = new Producter(b);
Customer c = new Customer(b);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
|