多线程
一、 实现多线程
1. 进程和线程
进程概述:正在运行的程序
- 系统进行资源调度和分配的独立单位
- 每个进程有自己独立的内存空间和系统资源
线程概述:是进程中单个顺序控制流,是一条执行路径
单(多)线程:一个进程有单个(多个)执行路径
2. 多线程的实现方式1——Thread
方式1:继承Thread类
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
两个小问题:
- 为什么要重写run()方法?
因为run()是用来封装被线程执行的代码的 - run()和start()的区别?
run():封装线程执行的代码,直接调用的,相当于普通方法的调用
start():启动线程;然后由JVM调动此线程的run()
package file;
public class myThread extends Thread{
@Override
public void run() {
for(int i =0 ; i<100 ; i++){
System.out.println(i);
}
}
}
package file;
import java.io.IOException;
public class Demo {
public static void main(String[] args) throws IOException {
myThread my1 = new myThread();
myThread my2 = new myThread();
my1.start();
my2.start();
}
}
多线程执行的效果:
3. 设置和获取线程名称
Thread类中设置和获取线程名称的方法:
- void setName(Stringname):将此线程的名称更改为等于参数name
- String getName():返回此线程的名称
- 通过构造方法也可以设置线程名称
如何获取main()方法所在的进程?
- currentThread(): 返回当前正在执行的线程对象的引用
package file;
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(i);
}
}
}
package file;
import java.io.IOException;
public class Demo {
public static void main(String[] args) throws IOException {
myThread my1 = new myThread("高铁");//使用带参构造方法构造需要在myThread里面自己定义
myThread my2 = new myThread("飞机");
myThread my3 = new myThread();
my3.setName("大炮");
my1.start();
my2.start();
my3.start();
System.out.println(my1.getName());
System.out.println(Thread.currentThread().getName());
}
}
4. 线程调度
线程有两种调度模型:
- 分时调度模型: 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
- 抢占式调度模型: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
多线程执行的时候有随机性,因为谁抢到cpu的使用权不一定
Thread类中设置和获取线程优先级的方法:
- public final int getPriority():返回此线程的优先级
- public final void setPriority(int newPriority):更改此线程的优先级
线程默认优先级是5,优先级范围:1~10
优先级高仅仅代表线程获取CPU的时间片的概率变高。
int num = my1.getPriority();
my1.setPriority(10);//1-10,优先级递增
my2.setPriority(5);
my3.setPriority(1);
5. 线程的控制
- sleep()
package file;
public class myThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "," + i);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package file;
import java.io.IOException;
public class Demo {
public static void main(String[] args) throws IOException {
myThread my1 = new myThread();
myThread my2 = new myThread();
myThread my3 = new myThread();
my1.setName("关羽");
my2.setName("张飞");
my3.setName("刘备");
my1.start();
my2.start();
my3.start();
}
}
- join()
my1.start();
my1.join();//等待这个进程死亡之后,下面的进程才开始执行
my2.start();
my3.start();
- setDaemon()
package file;
public class Demo {
public static void main(String[] args) throws InterruptedException {
myThread my1 = new myThread();
myThread my2 = new myThread();
my1.setName("关羽");
my2.setName("张飞");
//设置主线程为刘备
Thread.currentThread().setName("刘备");
//设置守护线程
my1.setDaemon(true);
my2.setDaemon(true);
//效果是:在主线程结束后,守护线程也慢慢停止
my1.start();
my2.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
}
}
}
6. 线程的生命周期
计组有手就行
7. 多线程的实现方式2——Runnable
实现Runnable接口:
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run)方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
相比继承Thread类,实现Runnable接口的好处:
- 避免了Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
package file;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
//实现Runnable接口不能直接用get.name()方法
}
}
}
package file;
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable mr = new MyRunnable();
//创建Thread类的对象,把MyRunnable类的对象当作构造参数
Thread tr1 = new Thread(mr, "A");
Thread tr2 = new Thread(mr, "B");
//启动线程
tr1.start();
tr2.start();
}
}
二、线程的同步
1. 同步代码块(锁)——synchronized
锁多条语句操作共享数据,可以使用同步代码块实现
- 格式:
synchronized(任意对象){
多条语句操作共享数据的代码
} - synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
同步的利弊
- 好处:解决多线程的数据安全问题
- 弊端:线程很多时,因为每个线程都要判断同步上的锁,耗资源,降效率
案例:买票
存在的问题:线程随机性导致很多问题
为什么会有问题?(也是判断多线程程序是否有数据安全问题的保准)
- 是否是多线程环境
- 是否有共享数据
- 是否存在多条语句操作共享数据
如何解决?
- 让程序没有安全问题的环境(破坏上述三个)
如何实现?
- 同步代码块(锁多条语句操作共享数据)
package file;
public class MyRunnable implements Runnable {
private int ticket = 100;//100张票
private Object obj = new Object();//任意对象
@Override
public void run() {
while (true) {
//假设tr1抢到cpu执行权,往下执行,在输出是tr2抢到了执行权,但是tr2看到锁,只能等tr1全部执行完毕后才可以执行
synchronized (obj) {//上锁
if (ticket > 0) {
try {
Thread.sleep(100);//出票时间
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
}
}
}
}
}
package file;
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable mr = new MyRunnable();
//创建Thread类的对象,把MyRunnable类的对象当作构造参数
Thread tr1 = new Thread(mr, "窗口A");
Thread tr2 = new Thread(mr, "窗口B");
Thread tr3 = new Thread(mr, "窗口C");
//启动线程
tr1.start();
tr2.start();
tr3.start();
}
}
结果一下一下跳出来(Thread.sleep()的作用)
2. 同步方法
同步方法:就是把synchronized关键字加在方法上
- 格式:
修饰符synchronized返回类型 方法名 - 同步方法的锁的对象是
this - 同步静态方法:把synchronized加在静态方法上
static synchronized 方法名 - 同步静态方法的锁的对象是
类名.class
本类方法锁:
package file;
public class MyRunnable implements Runnable {
private int ticket = 100;//100张票
private Object obj = new Object();
private int x = 0;
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
synchronized (this) {//this
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
}
}
} else {
sellTicket();
x++;
}
}
}
private synchronized void sellTicket() {
if (ticket > 0) {
try {
Thread.sleep(10);//
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
}
}
}
同步静态方法锁: (this的地方改,还有相关的static)
package file;
public class MyRunnable implements Runnable {
private static int ticket = 100;//100张票
private Object obj = new Object();
private int x = 0;
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
synchronized (MyRunnable.class) {//this
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
}
}
} else {
sellTicket();
x++;
}
}
}
private static synchronized void sellTicket() {
if (ticket > 0) {
try {
Thread.sleep(10);//
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
}
}
}
说实话没听懂
3. Lock锁(接口)
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获得锁和释放锁的方法:
- void lock():获得锁
- void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
package file;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyRunnable implements Runnable {
private static int ticket = 100;//100张票
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();//获得锁
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
}
} finally {
lock.unlock();//释放锁
//try finally 的作用,,防止try里面出现问题,最后释放不了锁
}
}
}
}
三、 生产者和消费者
1. 模式描述
包含两类线程:生产数据和消费数据.
存在等待和唤醒
2. 案例: 奶箱,送奶工,用户
package file;
public class Box {
//等一成员变量,表示第几瓶奶
private int milk;
private boolean statue = false; //奶箱状态,没奶
//存牛奶
public synchronized void put(int milk) {
//如果有牛奶,等待消费
if (statue) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} //没有牛奶,就生产
this.milk = milk;
System.out.println("送牛奶将" + this.milk + "瓶牛奶放入奶箱");
statue = true;
notifyAll();
}
//取牛奶
public synchronized void get() {
//没牛奶,生产奶
if (!statue) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("用户拿第" + this.milk + "瓶牛奶");
statue = false;
notifyAll();
}
}
package file;
public class Consumer implements Runnable {
private Box b;
public Consumer(Box b) {
this.b = b;
}
@Override
public void run() {
while (true) {
b.get();
}
}
}
package file;
public class Producter implements Runnable {
private Box b;
public Producter(Box b) {
this.b = b;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
b.put(i);
}
}
}
package file;
public class BoxDemo {
public static void main(String[] args){
//创建奶箱对象,共享数据
Box b = new Box();
//创建生产者和消费者对象,把奶箱对象作为构造参数传递,因为这个类要调用存入牛奶和取出牛奶的数据
Consumer consumer = new Consumer(b);
Producter producter = new Producter(b);
//创建两个进程,存奶和取奶
Thread t1 = new Thread(consumer);
Thread t2 = new Thread(producter);
//启动线程
t1.start();
t2.start();
}
}
很难理解但是,仔细理解