多线程
进程:在内存中正在执行的程序。一个应用程序如果想要被执行必须跑在内存中。
线程:是进程的执行单元,用来负责进程中代码的执行。进程中可以一条线程,也可以有多条线程。一个进程至少需要一个线程。
Java中线程使用抢占式调度:县城具有优先级,线程高的抢到的概率大,如果线程优先级相同,那么会随机选择一个线程执行。
CPU资源在多个线程之间进行高速的切换,对于人类感觉到多个线程在同时进行操作。但是在同一 时刻,只有一条线程被执行。
当线程和计算机硬件进行交互时,会暂时空置CPU。多条线程就能够提高CPU的利用率。假设一条线程CPU空闲时间是40%,那么利用率是60%,那么两条线程空闲时间是 40% * 40%,利用 率是1 - 40% * 40% = 84%
多线程并没有提高运行速度,但是提高了CPU的利用率
主线程:程序在JVM启动时,会自动分配一条线程在执行main方法,这条线程称之为主线程。
创建方式一
- 定义一个类继承Thread。
- 重写run方法。
- 创建子类对象,就是创建线程对象。
- 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。
public class MyThread extends Thread{
// 重写run方法
@Override
public void run() {
// TODO Auto-generated method stub
// 在run方法中填写需要被其他线程执行的代码
for (int i = 0;i < 100;i++){
System.out.println("i = " + i);
}
}
}
创建方式二
- 定义类实现Runnable接口。
- 覆盖接口中的run方法。。
- 创建Thread类的对象
- 将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
- 调用Thread类的start方法开启线程。
public class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0;i < 100;i++){
System.out.println("子线程的i = " + i);
}
}
}
public static void main(String[] args) {
// 创建Runnable的实现类对象
MyRunnable my = new MyRunnable();
// 创建对象
Thread t = new Thread(my);
// 开启线程并且调用run方法
t.start();
for (int i = 0;i < 100;i++){
System.out.println("主线程的i = " + i);
}
}
售票案例
public class Practice01 {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread thread1 = new Thread(st, "窗口一");
Thread thread2 = new Thread(st, "窗口二");
Thread thread3 = new Thread(st, "窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class SellTicket implements Runnable {
int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖票" + ticket);
ticket--;
}
}
}
出现重票,0票,-1票问题,存在线程安全问题。
线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
这里明显出了问题,就是线程安全隐患。
解决思路:保证核心代码同一时刻只能有一条线程在执行。
方式一:同步代码块
- 格式
synchronized (锁对象) {
可能会产生线程安全问题的代码
} - 作用:能够保证在代码块中的代码同一时刻最多只有一条线程。
- 同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
- 方法区的内容是被线程共享的,所以"abc" Math.class 都可以当做同步代码块的锁资源
- 同步代码块会影响多线程的效率,所以只加核心可能出现隐患的代码。
// 模拟票数
public class Ticket implements Runnable{
int ticket = 100;
// 当做同步代码块的锁资源
Object obj = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
while (true){
// 让当前线程休眠 单位是毫秒
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// // 同步代码块
synchronized (obj) {
// 说明票卖光了
if (ticket <= 0){
break;
}
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket--);
}
}
}
}
方式二:同步方法
同步方法:被synchronized 修饰的方法,可以保证方法中同一时刻只能有一条线程在执行。
同步方法的锁资源是this
方式三:静态同步方法
静态同步方法:被synchronized和static修饰的方法,可以保证方法中同一时刻只能有一条线程在 执行。
静态同步方法的锁资源是 类名.class
// 模拟票数
public class Ticket implements Runnable{
int ticket = 100;
// 当做同步代码块的锁资源
Object obj = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
// synchronized (obj) {
while (true){
// 让当前线程休眠 单位是毫秒
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// // 同步代码块
synchronized (obj) {
// 说明票卖光了
if (ticket <= 0){
break;
}
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket--);
}
// method();
}
// }
}
// 同步方法
// 可以保证同步方法中的代码同一时刻只能有一条线程在执行
// 同步方法的锁资源就是this
public synchronized void method(){
// 说明票卖光了
if (ticket <= 0){
return;
}
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket--);
}
// 静态同步方法的锁资源是类名.class
// public static synchronized void method1(){
// // 说明票卖光了
// if (ticket <= 0){
// return;
// }
//
// System.out.println(Thread.currentThread().getName() + "正在卖" + ticket--);
// }
}
同步:只允许一个线程执行。
异步: 允许多条线程同时执行。
同步一定是线程安全的。
Hashtable是同步线程安全的
HashMap是异步线程不安全的
ConcurrentHashMap异步线程安全的
死锁
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
// 全公司共享的仪器
static Printer p = new Printer();
static Scan s = new Scan();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (p) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 先使用打印机
p.print();
synchronized (s) {
// 使用扫描仪
s.scan();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (s) {
s.scan();
// 打印内容
synchronized (p) {
p.print();
}
}
}
}).start();
}
}
// 打印机
class Printer{
public void print(){
System.out.println("打印机正在吱吱吱的打印东西~");
}
}
// 扫描仪
class Scan{
public void scan(){
System.out.println("扫描仪正在扫描文件~~");
}
}
等待唤醒机制
案例:学生提问
package com.kaer.threaddemo;
public class Practice03 {
public static void main(String[] args) {
Student s = new Student();
s.setName("张三");
s.setGender("男");
new Thread(new Ask(s)).start();
new Thread(new Change(s)).start();
}
}
class Student{
private String name;
private String gender;
boolean flag = true;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
class Ask implements Runnable{
private Student s;
public Ask(Student s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (s){
if (!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("我是" + s.getName() + "," + s.getGender() + "生");
s.flag = false;
s.notify();
}
}
}
}
class Change implements Runnable{
Student s;
public Change(Student s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (s){
if (s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if ("张三".equals(s.getName())){
s.setName("李四");
s.setGender("女");
}else{
s.setName("张三");
s.setGender("男");
}
s.flag = true;
s.notify();
}
}
}
}
生产者消费者模型
package com.kaer.threaddemo;
/*
生产消费模式
一个线程作为消费者,一个线程作为生产者。生产者每生产一次,消费者就消费一次。
生产者每次生产的商品数量以及消费者每次消费的数量用随机数产生即可。
每一次的生产的商品数量和上一次剩余的商品数量之和不能超过1000。
*/
public class Practice02 {
public static void main(String[] args) {
Product p = new Product();
new Thread(new Productor(p)).start();
new Thread(new Productor(p)).start();
new Thread(new Customer(p)).start();
new Thread(new Customer(p)).start();
}
}
class Product {
private int count;
public boolean flag = true;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
//生产
class Productor implements Runnable {
private Product p;
public Productor(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
while (!p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//计算本次所能生产的最大数量
int max = 1000 - p.getCount();
//计算本次生产的实际数量
int count = (int) (Math.random() * (max + 1));
//计算本次所能提供的商品数量
p.setCount(p.getCount() + count);
System.out.println("生产的数量为:" + count + ",提供的数量为:" + p.getCount());
p.flag = false;
p.notifyAll();
}
}
}
}
//消费
class Customer implements Runnable {
private Product p;
public Customer(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
while (p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//计算本次消费的商品数量
int count = (int) (Math.random() * (p.getCount() + 1));
//计算本次剩余的商品数量
p.setCount(p.getCount() - count);
System.out.println("消费数量为:" + count + ",剩余数量为:" + p.getCount());
p.flag = true;
p.notifyAll();
}
}
}
}