程序:可以理解为一组静态的代码
进程:正在进行的程序
线程:正则执行线程的小单元
多线程
线程允许我们并发执行多个代码片段。
创建线程有两种方式,
方式一:
定义一个类继承Thread并重写run方法。run方法用来定义需要线程并发执行的任务代码
第二种创建线程的方式:实现Runnable接口单独定义线程任务
第一种创建线程的方式
优点:创建简单,当临时需要执行某个任务时使用这种方式创建更直接
缺点:由于java是单继承的,这会导致我们继承了线程以后就不能再继承其他类去复用方法,实际开发非常不便
定义线程的同时重写run方法,将任务也一同定义出来这会导致线程与任务有一个必然耦合关系,不利于线程的重用。
注意,启动线程要调用start,而不是直接调用run方法。
start方法调用完毕后,线程纳入线程调度器。当该线程第一次获得CPU时间开始运行时,其run方法
会自动被调用。
线程纳入线程调度器后,只能被动的等待分配CPU的时间片,得到时间片后,CPU便会运行该线程的任务代码,时间片用完后,CPU离开,此时线程调度会再分配时间片给某个线程使其运行。
线程调度器分配时间片的概率是一样的,但是所有并发运行的线程不保证"一人一次"这样均匀的分配
时间片。
匿名内部类实现线程
Thread t1 = new Thread() {
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("Thread"+i);
}
};
};
Runnable r1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("Runnable"+i);
}
}
};
t1.start();
new Thread(r1).start();
生产消费者模型
仓库类
public class Cangku {
private ArrayList<Integer> list = new ArrayList<Integer>();
public void add() {
if (list.size() < 20) {
list.add(1);
System.out.println("++++");
}else {
return;
}
}
public void remove() {
if (list.size() > 0) {
list.remove(0);
System.out.println("------");
} else {
return;
}
}
}
生产者
public class Sheng extends Thread{
public Sheng(Cangku cangku) {
super();
this.cangku = cangku;
}
private Cangku cangku;
@Override
public void run() {
while(true) {
cangku.add();
}
}
}
消费者
public class Xiao extends Thread{
public Xiao(Cangku cangku) {
super();
this.cangku = cangku;
}
private Cangku cangku;
@Override
public void run() {
while(true) {
cangku.remove();
}
}
}
main
public static void main(String[] args) {
Cangku cangku = new Cangku();
Sheng sheng = new Sheng(cangku);
sheng.start();
Xiao xiao = new Xiao(cangku);
xiao.start();
Xiao xiao1 = new Xiao(cangku);
xiao1.start();
}
产生问题
两个消费者同时访问一个仓库对象,仓库内只有一个元素时,两个消费者并发访问,有可能产生抢夺资源的问题
解决
让仓库对象被线程访问时,仓库对象被锁定
让仓库对象只能被一个对象访问,其他线程处于等待状态,即同一个时间点只有一个线程访问
特征修饰符:synchronized
两种写法
写在方法的结构上(锁定的时调用方法时的那个对象)
public synchronized void remove() {
写在方法/构造方法/块的内部
public void test(){
//代码一
synchronized (this){
//代码二
}
//代码三
}
最终解决,添加线程安全锁,让线程等待或唤醒
public class Cangku {
private ArrayList<Integer> list = new ArrayList<Integer>();
public void add() {
if (list.size() < 20) {
list.add(1);
System.out.println("++++");
} else {
try {
/**
* 当线程等后,要叫醒其他的线程,否则可能出现所有线程都等待的结果
*/
// 叫醒其他所有的线程
this.notifyAll();
// 仓库对象调用wait不是仓库等待,而是访问仓库的生产者线程等待
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void remove() {
if (list.size() > 0) {
list.remove(0);
System.out.println("------");
} else {
try {
// 叫醒其他所有的线程
this.notifyAll();
// 仓库对象调用wait不是仓库等待,而是访问仓库的消费者线程等待
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
注意
当没有线程安全锁时,调用 wait();方法,可能抛出异常
IllegalMonitorStateException
是因为当通知一个人等待是,线程切换了,被通知的那个人已经不是刚才的那个线程了
此时,如果有线程安全锁,就不会产生问题了
总结
利用了线程安全锁,锁定的是对象
利用方法控制线程状态来回切换
笔试题
slepp方法是Thread类下的,方法是静态的,类名.调用,在哪个位置调用,哪个位置等待,自己就可以醒,不会释放锁
wait方法是Object下的,通过对象.调用,是让访问对象的其他线程等待。需要其他对象调用notify唤醒,等待后会释放锁
join方法
线程的API
线程提供了一个静态方法:
static Thread currentThread()
这个方法会将运行这个方法的线程返回。
一个重要的API:ThreadLocal会使用到它。
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
Thread t1 = Thread.currentThread();
//Thread[线程名字,线程优先级,线程组]
//Thread[main,5,main]
System.out.println(t1);
new Thread() {
@Override
public void run() {
//Thread[Thread-0,5,main]
System.out.println(Thread.currentThread());
}
}.start();
// 获取主线程
Thread main = Thread.currentThread();
// 获取线程的唯一标识
long id = main.getId();
System.out.println("id:" + id);
// 获取线程名字
String name = main.getName();
System.out.println(name);
// 获取线程的优先级,默认情况线程的优先级都是5
int priority = main.getPriority();
System.out.println(priority);
// 线程是否处于活动状态
boolean isAlive = main.isAlive();
// 线程是否为守护线程
boolean isDaemon = main.isDaemon();
// 线程是否被中断
boolean isInterrupted = main.isInterrupted();
System.out.println("isAlive:" + isAlive);
System.out.println("isDaemon:" + isDaemon);
System.out.println("isInterrupted:" + isInterrupted);
线程优先级
线程启动后纳入到线程调度,线程时刻处于被动获取CPU时间片而无法主动获取。我们可以通过调整线程的优先级来最大程度的干涉线程调度分配时间片的几率。
理论上优先级越高的线程获取CPU时间片的次数越多。
线程优先级有10个等级,分别用整数1-10表示。其中1位最低优先级,10为最高优先级,5为默认值。
调用线程的方法:setPriority()方法来设置优先级。
/**
* 线程提供的方法:
* static void sleep(long ms)
* 这是一个静态方法,当一个线程执行了这个方法后就会进入
* 阻塞状态,并阻塞指定的毫秒。当超时后,线程会自动回到
* RUNNABLE状态等待再次获取时间片并发运行。
*/
/**
* sleep方法要求处理中断异常:InterruptedException
* 当一个线程调用sleep方法处于阻塞状态的过程中,这个线程
* 的中断方法interrupt被调用时,则sleep方法会抛出中断异常
* 此时该线程的睡眠阻塞被打断。
* @author Administrator
*/
处理中断异常:InterruptedException
public static void main(String[] args) {
Thread lin = new Thread() {
public void run() {
System.out.println("林:刚美完容,睡一会...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了相了!");
}
System.out.println("林:醒了!");
}
};
Thread huang = new Thread() {
public void run() {
System.out.println("黄:开始砸墙!");
for(int i=0;i<5;i++) {
System.out.println("黄:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("咣当!");
System.out.println("黄:搞定!");
lin.interrupt();//中断lin线程的睡眠阻塞
}
};
lin.start();
huang.start();
}
/**
* 守护线程
* 设置守护线程必须在线程启动之前设置
* 守护线程又称为后台线程,默认创建出来的线程都是普通线程。
* 守护线程需要通过调用线程方法:setDaemon(boolean on)来
* 进行设置。
*
* 守护线程使用上与普通线程没有区别,但是在结束时机上有一个
* 不同之处:进程结束时,所有正在运行的守护线程都会被强制停
* 止。而进程结束:当一个进程中所有的普通线程结束时进程结束
*
* GC就是跑在守护线程上的。
* 我们也可以将某些会一致运行的任务,并且当主要业务执行完毕
* 后可以跟着一同结束的就都放在守护线程上运行即可。
*
* @author Administrator
*
*/
守护线程
public class DaemonThreadDemo {
public static void main(String[] args) {
Thread rose = new Thread() {
public void run() {
for(int i=0;i<5;i++) {
System.out.println("rose:let me go!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("rose:啊啊啊啊AAAAAaaaaa....");
System.out.println("噗通!");
}
};
Thread jack = new Thread() {
public void run() {
while(true) {
System.out.println("jack:you jump!i jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
};
rose.start();
//线程启动前进行设置
jack.setDaemon(true);//设置为守护线程
jack.start();
//此时如果是死循环,主线程没有死,则守护线程不能结束
//如果不添加死循环,rose停止了则jack也会停止
while(true) {
}
}
}
join:让线程完成排队
public class A extends Thread {
@Override
public void run() {
System.out.println("线程a开始执行");
//在线程a里创建b线程,
B b = new B();
b.start();
try {
//如果不传递参数,表示a线程会一直等待B线程执行完
//如果传递参数,表示a线程等待b线程的最多时间,如果在时间内b线程结束了,则a继续执行
//如果时间内b线程没有结束,则a直接执行
//这里如果c锁定了b,则a会等待c释放b然后a自己才能执行完
b.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程a结束执行");
}
}
public class B extends Thread{
@Override
public void run() {
System.out.println("线程b开始执行");
new C(this).start();;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程b结束执行");
}
}
public class C extends Thread {
private B b;
public C(B b) {
this.b = b;
}
@Override
public void run() {
System.out.println("线程c开始执行");
//锁定b五秒
synchronized (b) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程c结束执行");
}
}
public static void main(String[] args) {
new A().start();
}
public class ThreadTest extends Thread {
static boolean b = false;
public static void main(String[] args) {
/**
* 线程提供了一个方法:
* void join()
* 该方法允许执行这个方法的线程在该方法所属线程后等待,
* 直到该方法所属线程结束后方可继续运行,否则会一致处于
* 阻塞状态。
* 所以join可以协调线程之间的同步运行。
* 同步运行:执行有先后顺序
* 异步运行:执行没有先后顺序,多线程就是异步运行的。
* @author Administrator
*
*/
/*
* JDK8之前有一个强制要求(JDK8不强制要求,但是
* 若存在使用不当情况,编译器仍然会编译失败)。
* 当一个方法的局部内部类中引用了这个方法的其他
* 局部变量时,要求这个变量必须声明为final的。
* 这源自JVM的内存分配问题。
*/
final Thread t1 = new Thread() {
public void run() {
for(int i=1;i<=10;i++) {
System.out.println("图片下载了"+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
b = true;
}
};
Thread t2 = new Thread() {
public void run() {
//解决:此时应该卡住t2线程,等待t1线程执行完
//想把谁卡住,就让谁调join方法,等着哪个线程就调哪个线程的join
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(b) {
System.out.println("图片显示成功");
}else {
System.out.println("图片显示失败");
throw new RuntimeException("图片显示失败");
}
}
};
t1.start();
t2.start();
}
}
哲学家就餐问题
public class Kuaizi extends Thread {
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public Kuaizi(int num) {
super();
this.num = num;
}
}
public class Zhexuejia extends Thread{
public Zhexuejia(String name, Kuaizi zuo, Kuaizi you) {
super();
this.name = name;
this.zuo = zuo;
this.you = you;
}
private String name;
private Kuaizi zuo;
private Kuaizi you;
@Override
public void run() {
synchronized (zuo) {
System.out.println(name+"获得左手的筷子");
synchronized (you) {
System.out.println(name+"获得右手的筷子");
System.out.println(name+"......");
}
}
}
}
public static void main(String[] args) {
Kuaizi kuaizi1 = new Kuaizi(1);
Kuaizi kuaizi2 = new Kuaizi(2);
Kuaizi kuaizi3 = new Kuaizi(3);
Kuaizi kuaizi4 = new Kuaizi(4);
Zhexuejia zhexuejia1 = new Zhexuejia("张1三",kuaizi1,kuaizi2);
Zhexuejia zhexuejia2 = new Zhexuejia("张2三",kuaizi2,kuaizi3);
Zhexuejia zhexuejia3 = new Zhexuejia("张3三",kuaizi3,kuaizi4);
Zhexuejia zhexuejia4 = new Zhexuejia("张4三",kuaizi4,kuaizi1);
zhexuejia1.start();
zhexuejia2.start();
zhexuejia3.start();
zhexuejia4.start();
}
可能会产生死锁的问题
解决,增加排队的机制
public Zhexuejia(String name, Kuaizi zuo, Kuaizi you,long time) {
super();
this.name = name;
this.zuo = zuo;
this.you = you;
this.time = time;
}
private String name;
private Kuaizi zuo;
private Kuaizi you;
private long time;
@Override
public void run() {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (zuo) {
System.out.println(name+"获得左手的筷子");
synchronized (you) {
System.out.println(name+"获得右手的筷子");
System.out.println(name+"......");
}
}
}
同时创建Zhexuejia对象需要在传递一个时间参数
并发安全问题的介绍
同步锁
/**
* 多线程并发安全问题
* 当多个线程并发操作同一个资源(临界资源)时,由于线程切换
* 实际不确定,导致执行此操作的顺序混乱,会出现执行过程
* 与结果未按照该程序预定方式进行导致一系列后果,严重时可
* 能导致系统瘫痪。
* @author Administrator
*
*/
产生了抢资源的问题
public class ThreadTest extends Thread {
public static void main(String[] args) {
final Table table = new Table();
Thread t1 = new Thread() {
public void run() {
while(true) {
int bean = table.getBean();
Thread.yield();//模拟CPU时间片用完导致线程切换
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 = new Thread() {
public void run() {
while(true) {
int bean = table.getBean();
Thread.yield();//模拟CPU时间片用完导致线程切换
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table {
int beans = 20;
解决方式一
/**
* 当一个方法使用关键字synchronized修饰后,这个方法
* 称为"同步方法",即:多个线程不能同时进到方法内部执
* 行。
* 将多个线程并发执行操作临界资源改为同步执行就可以
* 有效的解决多线程的并发安全问题。
* 直接在方法上使用synchronized,那么同步监视器对象就是"this",即:该方法所属对象
* @return
*/
public synchronized int getBean() {
if(beans==0) {
throw new RuntimeException("没有豆子了!");
}
Thread.yield();//模拟CPU时间片用完导致线程切换
return beans--;
}
}
解决方式二
/**
* 同步块:
* synchronized(同步监视器对象){
* 需要同步运行的代码片段
* }
*
* 同步块可以更准确的控制需要同步运行的代码片段,有效的
* 缩小同步范围可以保证并发安全的前提下尽可能的提高并发
* 的效率。
*
* 同步块要求指定一个同步监视器对象,即:上锁
* 的对象。这个对象可以是java中任何对象,但是
* 必须保证多个需要同步运行此代码片段的线程看到
* 的该对象是"同一个"。
*/
// public synchronized void buy() {
public void buy() {
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在挑衣服...");
Thread.sleep(5000);
/*
* 同步块要求指定一个同步监视器对象,即:上锁
* 的对象。这个对象可以是java中任何对象,但是
* 必须保证多个需要同步运行此代码片段的线程看到
* 的该对象是"同一个"。
*/
synchronized (this) {
System.out.println(t.getName()+":正在试衣服...");
Thread.sleep(5000);
}
System.out.println(t.getName()+":结账离开");
} catch (Exception e) {
}
}
/**
* 静态方法若使用synchronized修饰,那么这个方法一定
* 具有同步效果。
* 静态方法上使用的同步监视器对象为这个类的"类对象",
* 每个java定义的类都只有唯一的一个类对象(Class类型的的实
* 例),而类对象后面的反射知识中会介绍。
*
* 静态方法中若使用同步监视器对象,也可以使用
* 当前类的类对象,获取当前类的类对象可以直接
* 通过:类名.class得到。如下:
*/
互斥锁
/**
* 互斥锁
* 当使用synchronized锁定多个代码片段,并且他们指定的
* 同步监视器对象是同一个时,那么这些代码片段之间就是互斥
* 的,多个线程不能同时在这些代码片段中运行。
*/
public class SyncDemo4 {
public static void main(String[] args) {
Boo boo = new Boo();
Thread t1 = new Thread() {
public void run() {
boo.methodA();
}
};
Thread t2 = new Thread() {
public void run() {
boo.methodB();
}
};
t1.start();
t2.start();
}
}
class Boo{
public synchronized void methodA() {
try {
Thread t = Thread.currentThread();
System.out.println(t.getName()+":正在执行A方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行A方法完毕!");
} catch (Exception e) {
}
}
public void methodB() {
synchronized(this) {
try {
Thread t = Thread.currentThread();
System.out.println(t.getName()+":正在执行B方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行B方法完毕!");
} catch (Exception e) {
}
}
}
}