并发与并行
- 并行
- 多个事件同一时刻发生
- 并发
- 多个事件在同一时间段内发生
线程与进程
- 进程
- 系统运行程序的基本单位
- 每个进程都有一个独立的内存空间
- 是系统调度和资源分配的最小单位
- 线程
- 进程中的一个执行单元
- 一个进程可以有多个线程
- 共享进程的内存
- 是CPU调度的最小单位
创建线程和启动线程
继承Thread类
public class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}
public class Demo01 {
public static void main(String[] args) {
//创建自定义线程对象
MyThread mt = new MyThread("新的线程!");
//开启新线程
mt.start();
//在主方法中执行for循环
for (int i = 0; i < 10; i++) {
System.out.println("main线程!"+i);
}
}
}
实现Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public class Demo {
public static void main(String[] args) {
//创建自定义类对象 线程任务对象
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread t = new Thread(mr, "小强");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("旺财 " + i);
}
}
}
使用匿名内部类对象来实现线程的创建和启动
new Thread("新的线程!"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}.start();
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":" + i);
}
}
}).start();
Thread类
常用方法
- public void run() :此线程要执行的任务在此处定义代码
- public String getName() :获取当前线程名称
- public static Thread currentThread() :返回对当前正在执行的线程对象的引用
- public final void setPriority(int newPriority):改变线程的优先级(10最高、1最低、5普通)
- public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
- void join() :等待该线程终止
- void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待
示例
import java.util.Scanner;
public class TestJoin {
public static void main(String[] args) {
ChatThread t = new ChatThread();
t.start();
for (int i = 1; i < 10; i++) {
System.out.println("main:" + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当main打印到5之后,需要等join进来的线程停止后才会继续了。
if(i==5){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class ChatThread extends Thread{
public void run(){
Scanner input = new Scanner(System.in);
while(true){
System.out.println("是否结束?(Y、N)");
char confirm = input.next().charAt(0);
if(confirm == 'Y' || confirm == 'y'){
break;
}
}
input.close();
}
}
守护线程
为其他线程服务,如果所有线程都死亡,那么守护线程自动死亡
JVM的垃圾回收线程就是典型的守护线程
调用setDaemon(true)方法可将指定线程设置为守护线程。必须在线程启动之前设置,否则会报IllegalThreadStateException异常
public class TestThread { public static void main(String[] args) { MyDaemon m = new MyDaemon(); m.setDaemon(true); m.start(); for (int i = 1; i <= 100; i++) { System.out.println("main:" + i); } } } class MyDaemon extends Thread { public void run() { while (true) { System.out.println("我一直守护者你..."); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }
线程安全问题
卖票存在的问题
- 某张票被卖了两回
- 不存在的票,比如0票-1票
解决
- 同步代码块
- 同步方法
- 锁机制
同步代码块
class Ticket extends Thread{
private static int ticket = 100;
public Ticket() {
super();
}
public Ticket(String name) {
super(name);
}
/*
* 执行卖票操作
*/
@Override
public void run() {
// 每个窗口卖票的操作
// 窗口永远开启
while (true) {
synchronized (Ticket.class) {//这里不能选用this作为锁,因为这几个线程的this不是同一个
if (ticket > 0) { // 有票可以卖
// 出票操作
// 使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
System.out.println(getName() + "正在卖:" + ticket--);
}
}
}
}
}
同步方法
格式
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步方法的锁对象
- 静态方法:当前类的Class对象
- 非静态方法:this
线程间通信
等待唤醒机制
- 在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程
wait 和 notify 方法需要注意的细节
wait 和 notify 方法必须要由同一个锁对象调用
wait 和 notify 方法是属于Object方法的
wait 和 notify 方法必须要在同步代码块或者是同步函数中使用
生产者消费者问题
包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
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();
}
}
线程的生命周期
Thread 和 Runnable的区别
- 类只能是单继承,继承了Thread类就不能继承别的类了,而实现Runnable接口之后还可以继承别的类
- 线程池中只能放入实现Runnable或Callable类的线程,不能直接放入继承Thread的类
释放锁操作与死锁
会释放锁的操作
- 当前线程的同步方法、同步代码块执行结束
- 方法中遇到break,return终止了方法的继续执行
- 遇到了未处理的Error或Exception,导致线程异常结束
- 执行了wait()方法,导致线程被挂起并释放锁
不会释放锁的操作
- 程序调用Thread.sleep()、Thread.yield()
死锁
- 不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁
sleep() 和 wait() 的区别
sleep()不释放锁,wait()释放锁
sleep()指定休眠时间,wait()可以指定时间也可等待直到notify
sleep()是在Thread类中声明的静态方法,wait方法在Object类中声明