多线程
并发与并行
并发:两个或多个事件在同一个时间段内发生
并行:两个或多个事件在同一个时刻发生(同时)
线程与进程
进程:指一个内存中运行的应用程序(进入到内存中)
线程:是进程中的一个执行单元
多线程好处:效率高 各个单元互不影响
线程调度
分时调度:所有线程轮流使用CPU,平均分配每个线程占用CPU的时间
抢占式调度:优先让优先级高的线程使用CPU,如果优先级相同,随机选择一个,Java使用的是抢占式调度
主线程
执行主(main)方法的线程
单线程程序:只有一个线程,从main方法开始,从上往下执行
JVM执行main方法,main方法进入栈内存
JVM会找操作系统开辟一条main方法路径通向CPU(叫做主线程)
CPU通过这个路径执行main方法
Thread 类
创建Thread类的子类
在子类中重写Thread类中run方法,设置线程任务
创建子类对象
调用start方法,开启线程,执行run方法
void start() 使该线程开始执行 Java虚拟机调用run方法
结果 main线程与该run线程并发运行
且多次启动一个线程是非法的。特别是线程结束后不能重新启动
public class Thread1 extends Thread{
@Override
void run(){
//覆写run的内容
}
public static void main(String []args){
Thread1 mt = new Thread1();
mt.start(); // mt的run加线程入
//main 线程内容
}
}
获取线程名称
Thread 中的 String getName(); 返回该线程的名称
static Thread currentThread(); 返回当前正在执行的线程对象的引用
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name){
super(name);
}
public static void main(String []args){
MyThread mt = new MyThread();
mt.setName("new name");
mt.start();
MyThread("another name").start();
}
@Override
public void run(){
String name = getName();
System.out.println("name");
Thread t = Thread.currentThread();
name = t.getName();
System.out.println("name");
}
}
线程暂停
public static void sleep(long millis) //使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
Runnable接口
该接口只含一个run方法
创建一个Runnable接口的实现类
实现类必须定义一个称为run的无参数方法
创建一个实现类对象
创建Thread类对象,传递实现类对象
调用start() 启动线程
public class MyThread implements Runnable{
public static void main(String []args){
MyThread mt = new MyThread();
Thread t = new Thread(mt);
t.start();
}
@Override
public void run(){
// run的重写方法
}
}
避免了单继承的局限性(一个类只能继承一个类)
增强了程序的扩展性 降低了程序的耦合性
实现Runnable接口的方式 把设置线程任务和管理线程分开
匿名内部类方式实现线程的创建
匿名内部类产物:子类/实现类对象
new 父类/接口(){
//重复父类/接口的方法
}
public class MyThread extends Thread{
public static void main(String []args){
new Thread(){
public void run(){
//覆写内容
}
}.start();
//
Runnable r = new Runnable(){
public void run(){
//覆写内容
}
}
new Thread(r).start();
//或者
new Thread(new Runnable(){
public void run(){
//覆写内容
}
}).start();
}
}
线程安全与线程同步
同步代码块
同步中的线程 没有执行完不会归还锁对象
synchronized(锁对象){
//访问共享数据的代码
//保证多个线程使用的锁对象是同一个
//把同步代码块锁住 只让一个线程在同步代码块中执行
}
public class RunnabkeImpl implements Runnable{
private int ticket=100;
Object obj = new Object();
@Override
public void run(){
while(true){
synchronized(obj){
if(ticket>0){
System.out.println("ticket");
ticket--;
}
}
}
}
}
同步方法
修饰符 synchronized 返回值类型 方法名(参数列表){
//方法内容
}
public class RunnabkeImpl implements Runnable{
private int ticket=100;
Object obj = new Object();
@Override
public synchronized void run(){
while(true){
if(ticket>0){
System.out.println("ticket");
ticket--;
}
}
}
}
静态的同步方法 锁对象是本类的class属性–>class 文件对象(反射)
Lock锁
lock接口中的方法
void lock()
void unlock()
在成员位置创建一个ReentrantLock对象
在可能会出现安全问题的代码前调用lock方法获取锁
执行代码后 调用unlock 解锁
public class RunnabkeImpl implements Runnable{
private int ticket=100;
Object obj = new Object();
Lock lock = new ReentrantLock();
@Override
public void run(){
while(true){
lock.lock();// 加锁
if(ticket>0){
System.out.println("ticket");
ticket--;
}
lock.unlock();//解锁
}
}
}
线程状态
线程状态转换图
线程等待
-
Timed Waiting 计时等待
限时暂停一个线程 如使用sleep() 语句 进入到一个计时等待的状态 注意: 进入TIMED_WAITING 状态 一般调用sleep方法。单线程也可以调用 为了让其他线程有机会执行,将Thread.sleep() 的调用放线程run() 之内 sleep() 与 锁 无关,线程睡眠到期自动苏醒 并返回刀Runnable状态 sleep() 指定的时间是线程不会运行的最短时间,睡眠结束后线程不一定马上开始
-
block 锁阻塞状态
-
一个正在阻塞等待一个监视器锁(锁对象)的线程处于的状态 如线程A与线程B使用同一锁,如果线程A获取到锁,线程A进入Runnable状态 而线程B进入Blocked锁阻塞状态
-
waiting 无限等待
一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态 这其实并不是一个线程操作,而是多个线程间的通信 多个线程会争取锁,同时相互之间又存在协作关系 像争夺唯一一台电脑时,第一个大佬把题AC了(调用wait() 进入Waiting状态) 放出电脑(失去同步锁) 此时notify() 另外两个大佬 告诉他俩可以抢电脑(同步锁)AC题(run),然后其中一个大佬就抢到了(进入Runnable) 另一个大佬想出题却没有抢到电脑(锁对象) 就只能自闭(Blocked)等到上面大佬写完(释放锁对象)
wait方法可以带参数,差不多相当于sleep的作用(计时等待)在等待中可以notify退出休眠
不带参数就只能等notify(无限等待)
等待与唤醒机制(线程间通信)
多个线程的竞争机制
wait()
//线程不再活动,不再参与调度,进入wait set
//不会浪费CPU资源 也不会竞争锁
//等到别的线程执行notify时才能从wait set中释放
notify()
//选取所通知对象的wait set中的一个线程释放
notifyall()
//释放所通知对象的wait set 上的全部线程
调用wait与notify的细节
- wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
- wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
- wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法
下面搞一段从网上弄来的代码
包子类
public class BaoZi {
String pier ;
String xianer ;
boolean flag = false ;//包子资源 是否存在 包子资源状态
}
食客类
public class ChiHuo extends Thread {
private BaoZi bz;
public ChiHuo(String name, BaoZi bz) {
super(name);
this.bz = bz;
}
@Override
public void run() {
while (true) {
synchronized (bz) {
if (bz.flag == false) {//没包子
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃" + bz.pier + bz.xianer + "包子");
bz.flag = false;
bz.notify();
}
}
}
}
包子铺
public class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(String name, BaoZi bz) {
super(name);
this.bz = bz;
}
@Override
public void run() {
int count = 0; //造包子
while (true) { //同步
synchronized (bz) {
if (bz.flag == true) {//包子资源 存在
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}// 没有包子 造包子
System.out.println("包子铺开始做包子");
if (count % 2 == 0) {
bz.pier = "冰皮";
bz.xianer = "五仁";
} else {
bz.pier = "薄皮";
bz.xianer = "牛肉大葱";
}
count++;
bz.flag = true;
System.out.println("包子造好了:" + bz.pier + bz.xianer);
System.out.println("吃货来吃吧"); //唤醒等待线程 (吃货)
bz.notify();
}
}
}
}
测试类
public class Demo {
public static void main(String[] args) { //等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货", bz);
BaoZiPu bzp = new BaoZiPu("包子铺", bz);
ch.start();
bzp.start();
}
}