一、进程和线程
总结:
进程:
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多
个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创
建、运行到消亡的过程。
线程:
进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,
可以理解为一个进程便相当
于一个单 CPU 操作系统,而线程便是这个系统中运行的多个任务。
二、 多线程程序实现
01: 第一种方式:继承Thread类实现多线程
总结:
1, 继承Thread类
2, 重写run方法
3, 将要执行的代码写在run方法中
4, 创建线程对象
5, 调用start方法开启线程
注意事项:
调用run方法并不是启动线程,跟普通的对象调用方法是一样的
线程要想启动,必须调用 start() 方法,启动线程
线程并不能自己启动, 必须由一个程序来进行启动
案例代码
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
// my1.run();
// my2.run();
//void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
my1.start();
my2.start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
三、获取和设置线程名称
总结:
A:获取线程名称的方法:
public String getName() : 获取当前执行线程的名称
是在继承了Thread类中才能使用getName()
如何获取主线程(main)的名称?
Thread.currentThread().getName()
B:设置线程名称的方法
* public Thread(String name) : 创建线程并指定线程的名称
* public void setName(String name) : 设置线程的名称
* Thread(Runnable target, String name) :
案例代码:
public class MyThreadDemo {
public static void main(String[] args) {
// MyThread my1 = new MyThread();
// MyThread my2 = new MyThread();
//
// //void setName(String name):将此线程的名称更改为等于参数 name
// my1.setName("高铁");
// my2.setName("飞机");
//Thread(String name)
// MyThread my1 = new MyThread("高铁");
// MyThread my2 = new MyThread("飞机");
//
// my1.start();
// my2.start();
//static Thread currentThread() 返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName());
}
}
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);
}
}
}
四、设置线程优先级
* final int getPriority() 返回此线程的优先级
* final void setPriority(int newPriority)更改此线程的优先级线程默认优先级是5;
线程优先级的范围是:1-10
* 问题1: 线程默认的级别是?
5
* 问题2: 线程级别的范围是?
1 - 10
线程调度:
* 分时调度
* 抢占式调度(java中默认的方式)
案例代码:
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
tp1.setName("高铁");
tp2.setName("飞机");
tp3.setName("汽车");
//public final int getPriority():返回此线程的优先级
// System.out.println(tp1.getPriority()); //5
// System.out.println(tp2.getPriority()); //5
// System.out.println(tp3.getPriority()); //5
//public final void setPriority(int newPriority):更改此线程的优先级
// tp1.setPriority(10000); //IllegalArgumentException
// System.out.println(Thread.MAX_PRIORITY); //10
// System.out.println(Thread.MIN_PRIORITY); //1
// System.out.println(Thread.NORM_PRIORITY); //5
//设置正确的优先级
tp1.setPriority(5);
tp2.setPriority(10);
tp3.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
}
public class ThreadPriority extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
线程控制
* static void sleep(long millis)(重点掌握):使当前正在执行的线程停留(暂停执行)指定的毫秒数(在run方法中)
* void join():等待这个线程死亡
* void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
五、实现Runnable接口
实现步骤
* 创建 Runnabel 接口的实现类
* 重写 run方法
* 创建 Runnabel 接口的实现类的对象
* 创建 Thread 对象,将 Runnabel 接口的实现类的对象进行传递
* 使用 Thread 对象 中的 start方法,开启线程
使用Runnable接口的好处:
* 避免了java中单一继承局限性
* 可以方便的进行资源共享
* 线程Thread 跟 线程要执行的任务run方法有效的分离了,但是要想执行run方法的代码
必须要创建Thread对象,并启动线程
案例代码:
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
//Thread(Runnable target, String name)
Thread t1 = new Thread(my,"高铁");
Thread t2 = new Thread(my,"飞机");
//启动线程
t1.start();
t2.start();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
六、线程同步
多线程卖票
* 案例需求:
* 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
* 实现步骤:
* 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
* 在SellTicket类中重写run()方法实现卖票,代码步骤如下
* 判断票数大于0,就卖票,并告知是哪个窗口卖的
* 卖了票之后,总票数要减1
* 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
* 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
* 创建SellTicket类的对象
* 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
* 启动线程
案例代码:
public class SellTicketDemo {
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();
}
}
//定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
public class SellTicket implements Runnable {
private int tickets = 100;
//在SellTicket类中重写run()方法实现卖票,代码步骤如下
@Override
public void run() {
// A:判断票数大于0,就卖票,并告知是哪个窗口卖的
// B:卖了票之后,总票数要减1
// C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
1.卖票案例的问题
总结:
* 卖票所出现的问题有那些?
* 卖重复的票
* 有负数的票
* 有0号票
* 问题产生的原因?
* 多线程随机的特点,导致卖票的时候出现了以上三个问题
- 解决线程安全问题
* 同步机制的介绍
* 1.什么情况下需要同步
* 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
* 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
* 2.同步技术分类
* 同步代码块
* 同步方法
* Lock锁
- 使用同步代码块解决数据安全问题
总结:
* 同步技术的原理
使用 synchronized 同步块将共享数据进行锁起来,多个线程在执行的时候,会抢夺CPU的执行权
当某一个线程抢到执行权时,会先判断这个锁是否存在,如果存在就获取锁,这个线程就带着这个锁执行
同步代码块中的代码,直到执行完成之后,才释放锁. 没有锁的线程执行在通过代码块外等.
public class SellTicket implements Runnable {
private int tickets = 100;
//在SellTicket类中重写run()方法实现卖票,代码步骤如下
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
// tickets--;
}
}
}
}
}
七、使用同步方法解决线程安全问题.
* 非静态同步方法
总结:
* 同步方法:
把多个线程操作共享数据的代码,放到一个方法中
* 格式:
修饰符 synchronized 返回值类型 方法名(参数列表){}
* 注意:
非静态同步方法的锁对象是谁?
this
* 静态同步方法
* 格式:
修饰符 static synchronized 返回值类型 方法名(参数列表){}
* 注意:
* 静态同步方法的锁对象是谁?
Class锁
当前类名.class
选择一个锁的对象的时候,优先选择当前类的字节码对象这个锁
- 线程同步安全的类
总结:
- 使用Lock锁解决线程安全问题
* Lock概述 :
Lock锁中通过lock和unlock两个方法, 可以解决线程的安全问题, 因为是调用方法解决, 所以体现的更为面向对象
* 方法摘要
public void lock() : 加同步锁。
public void unlock() :释放同步锁
补充: Lock锁是jdk1.5版本之后出现的.
* 案例代码:
##04: 生产者和消费者
- 等待唤醒机制概述
* 铺垫 : 实现线程间的通讯, 使用的就是等待唤醒机制
* 问题 : 等待唤醒机制使用的是哪两个方法
总结:
* 等待唤醒机制使用的是哪两个方法
1. 等待 -> wait : 让当前线程在此进入无限等待状态, 需要另一条线程对其进行唤醒.
2. 唤醒 -> notify : 随机唤醒单个线程.
notifyAll() : 唤醒所有等待的线程
- 等待唤醒机制需求分析(生产者和消费者)