多线程编程
进程与线程
进程:一个正在运行的程序(独立运行的程序)
一个进程中可以只有一个线程 单线程程序
一个进程中可以有多个线程 多线程程序
线程:一个线程相当于一个 cpu 的执行路径(大大提升了处理效率)
一个独立的运行单元
创建一个线程,相当于 CPU 开辟了一个独立的运行执行路径
每个执行路径都是一个独立空间
创建一个线程,该线程就会拥有一个独立的栈空间
如果在同一个栈空间,不符合先入栈的后出栈这个规则
**注意:start 方法和 run 方法的区别**
start 方法调用的是一个线程
run 方法调用的是一个普通的方法
标准的单线程程序
public class Demo {
public static void main(String[] args) {
print();
printRemove();
System.out.println("程序执行完毕~");
}
public static void print() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
public static void printRemove() {
System.out.println("删除");
}
}
绝对安全,程序由上至下依次执行,但是效率不高
![这里写图片描述](https://img-blog.csdn.net/20180205204508661?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHV6b25nbmFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![这里写图片描述](https://img-blog.csdn.net/20180205204528703?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHV6b25nbmFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
多线程实现的三种方式
多线程实现过程
JVM 调用 main 方法 ---> 找操作系统(CPU) ---> 开启一个执行路径
开启了一个叫 main 执行路径, main 就是一个线程, main 是线程的名字,又叫主线程
线程默认名字:Thread-0,Thread-1.....
继承 Thread 类
Thread 方法
方法名 | 方法描述 |
---|
public String start() | 使该线程开始执行;JVM 调用该线程的 run 方法 |
public String run() | 调用该线程的 run 方法 |
public final void setName(String name) | 改变线程名称,使之与参数 name 相同 |
1.继承 Thread 类
2.重写父类的 run 方法
public class Demo {
public static void main(String[] args) {
// 创建一个线程
// 两种对线程进行命名的方法
SubThread subThread = new SubThread("线程1");
subThread.setName("线程");
/*
* 直接调用 run 方法,相当于就调用了一个普通成员方法
* subThread.run();
*/
// 获取主线程的名字
// 获取当前执行的线程对象,是一个静态方法
Thread currentThread = Thread.currentThread();
// 获取主线程名字
String name = currentThread.getName();
// 开启线程的方法
// 注意:直接调用 run 方法不能开启线程,需要调用 start 方法开启线程
subThread.start();
for (int i = 0; i < 50; i++) {
System.out.println(name + "---main 方法---" + i);
}
}
}
// Thread(String name) 该方法在创建线程的同时给线程起个名
// 构造方法不能被继承,要想使用父类的构造方法,可以在子类中写带参数的构造方法
class SubThread extends Thread {
public SubThread() {
super();
}
public SubThread(String name) {
// 调用父类的有参构造方法
super(name);
}
// 重写 run 方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
// 获取线程名的方法 getName
System.out.println(Thread.currentThread().getName() + "---run 方法---" + i);
}
}
}
**注意:在继承 Thread 时,不要对成员变量命名为 name!**
class NameThread extends Thread {
private String name;
// 构造
public NameThread() {
super();
}
public NameThread(String name,String myName) {
// 传递的是线程的名字,利用的是父类的构造方法
super(name);
// 给自己类的 name 赋值
// 建议:不要使用 name 这个名字作为成员变量名,避免不必要的麻烦
this.name = myName;
}
/*
* 父类已经有了 getName() 方法,并且用 final 修饰,不能被重写,所以报错
* public String getName() {
* return name;
* }
* public void setName(String name) {
* this.name = name;
* }
* 解决方法:不使用标准 get/set 方法,修改 get/set 方法名
*/
public String getNewName() {
return this.name;
}
public void setNewName(String newName) {
this.name = newName;
}
@Override
public void run() {
super.run();
}
}
实现 Runnable 接口
Java 中,只能单继承,当该类已经继承别的类的时候,不能继承 Thread 接口,因此使用实现 Runnable 接口来实现多线程
public class Demo {
public static void main(String[] args) {
// 创建线程
RunableImpl runableImpl = new RunableImpl();
Thread thread = new Thread(runableImpl);
// 开启线程
thread.start();
}
}
// 接口中 run 方法是一个抽象方法
class RunableImpl implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "(" + i + ")");
}
}
}
匿名内部类方式
相当于创建一个该类的子类对象
定义:
new 父类名或接口名(){
重写父类或实现接口的方法
};
这里 new 出来的就是这个类的子类对象
class Test{
public void fun(){
System.out.println("父类 fun() 方法");
}
}
// 创建 Test 的子类对象
new Test(){
// 重写父类的方法
@Override
public void fun(){
System.out.println("子类的 fun()方法");
}
}.fun();// 这里调用的是子类的 fun()方法
// 使用多态来接收子类
Test test = new Test(){
public void fun(){
System.out.println("子类的 fun()方法");
};
};
// 调用子类 fun()方法
test.fun();
interface TestInter {
public abstract void fun();
}
// 创建接口的实现类
TestInter testInter = new TestInter() {
@override
public void fun(){
System.out.println("实现类的 fun()方法");
}
};
testInter.fun();
利用匿名内部类方式,给 TreeSet 集合中的学生对象,按年龄排序
学生:姓名,年龄
/*
* 学生类
*/
public class Student {
private String name;
private int age;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "姓名:" + name + "\n 年龄:" + age;
}
}
public class Demo{
public static void main(String[] args){
TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
});
set.add(new Student("a", 80));
set.add(new Student("b", 40));
set.add(new Student("c", 60));
set.add(new Student("d", 10));
set.add(new Student("e", 20));
for (Student student : set) {
System.out.println(student);
}
}
}
使用匿名内部类方式创建线程(两种方式)
new Thread(){
@Override
public void run(){
for(int i = 0; i < 50; i++){
System.out.println(Thread.currentThread().getName() + "(" + i + ")");
}
}
}.start();
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "(" + i + ")");
}
}
};
// 放入线程对象中
Thread thread = new Thread(runnable);
thread.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "(" + i + ")");
}
}
}).start();
**接口实现创建线程的好处**
1.避免直接继承 Thread 类的局限性(避免单继承)
2.接口即插即用,减少类与类之间的联系(可以解耦)
线程的六种状态
1.新建状态(new 线程对象)
2.运行状态(调用 start()方法)
3.阻塞状态(等待 CPU 的执行资源)
4.休眠状态(调用 sleep(时间)方法)
5.等待状态(调用了 wait()方法)
6.死亡状态(run() 方法执行完毕)
![这里写图片描述](https://img-blog.csdn.net/20180206211349586?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHV6b25nbmFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
线程休眠
// 线程在 main 方法中休眠时,可以把异常抛出
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
// 线程休眠1秒 单位毫秒
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "\t(" + i);
}
}
/*
* 在子线程中,出现异常,只能 try...catch 处理
* Thread 类是 Runnable 接口的实现类
* 重写接口中 run 方法
* 该方法没有抛出异常,所以所有 Runnable 接口的实现类(包括 Thread 类)都不能再 run 方法中抛出异常
* 只能 try...catch 处理
*/
class SleepThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
// 休眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t(" + i);
}
}
}
同步锁
synchronized 关键字
两个或者两个以上的并发线程访问同一个对象的时候,就会用到同步代码块
在同一时间内,只能有一个线程访问这个对象,其他线程在同步代码块外被阻塞
同步锁(同步代码块)
锁可以是任意对象,但是要保证锁的唯一,多个线程使用的是同一把锁
synchronized(锁){
}
同步锁规则:
线程遇到锁就进同步代码块(并且携带锁)
当线程执行完代码块中的代码,把锁返还
线程没有遇到锁会在同步代码块外等待,遇到锁才能进
/*
* 卖火车票问题
*/
public class Demo {
public static void main(String[] args) {
TicketRunnable ticketRunnable = new TicketRunnable();
// 创建3个线程这些线程会执行 run 方法(线程内的任务)
Thread thread = new Thread(ticketRunnable);
Thread thread2 = new Thread(ticketRunnable);
Thread thread3 = new Thread(ticketRunnable);
// 开启三个 线程
thread.start();
thread2.start();
thread3.start();
}
}
class TicketRunnable implements Runnable{
// 声明票,同时私用票这个成员变量(保证票是共享数据,只 new 一次该类对象)
private int tickets = 50;
// 创建对象锁,保证唯一,确保多个线程,卖的是一份票,而不是每个线程卖不同份的同样份数的票
private Object object = new Object();
// 卖票方法
@Override
public void run() {
while (true) {
// 锁只要保证是对象和唯一就可以
synchronized (object) {
// 操作的共享数据的代码
if (tickets > 0) {
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
// 有票就卖
System.out.println(Thread.currentThread().getName() + "剩余票数:" + tickets);
// 减少一张
tickets--;
} else {
// 没票 结束循环
break;
}
}
// 让线程让出CPU的资源(不代表,下次执行卖票的线程不是这个线程,让出后,三个线程又开始进入同步锁,随机进入)
Thread.yield();
}
}
}
public class Demo {
public static void main(String[] args) {
TicketRunnable1 ticketRunnable1 = new TicketRunnable1();
// 创建3个线程这些线程会执行 run 方法(线程内的任务)
Thread thread = new Thread(ticketRunnable1);
Thread thread2 = new Thread(ticketRunnable1);
Thread thread3 = new Thread(ticketRunnable1);
// 开启三个 线程
thread.start();
thread2.start();
thread3.start();
}
}
class TicketRunnable1 implements Runnable{
// 声明票(保证票是共享数据,只 new 一次该类对象)
private int tickets = 50;
// 卖票方法
@Override
public void run() {
while (true) {
if (sellTickets()) {
break;
}
// 让线程让出CPU的资源
Thread.yield();
}
}
// 操作共享数据的方法
// 在方法中添加 synchronized 关键字 把方法变成同步方法
public synchronized boolean sellTickets() {
// 静态方法的同步代码的锁,可以使用本类 类名. class
//(可以使用静态方法,注意,静态方法只能调用静态方法或者静态变量
// 当改为静态时,要注意把相关的方法和成员变量也一起改为静态)
// 操作的共享数据的代码
if (tickets > 0) {
// 有票就卖
System.out.println(Thread.currentThread().getName() + "剩余票数:" + tickets);
// 减少一张
tickets--;
return false;
} else {
// 没票 结束循环
return true;
}
}
}
/*
* 公司年会 进入公司有两个门(前门和后门)
* 进门的时候 每位人都能获取一张彩票(7位数)
* 公司有100个员工
* 利用多线程模拟进门过程
* 统计每个入口入场的人数
* 每个人拿到的彩票的号码 要求7位数字 不能重复
* 打印格式:
* 编号为: 1 的员工 从后门 入场! 拿到的双色球彩票号码是: [17, 24, 29, 30, 31, 32, 07]
* 编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是: [06, 11, 14, 22, 29, 32, 15]
* .....
* 从后门 入场的员工总共: 45 位员工
* 从前门 入场的员工总共: 55 位员工
*/
public class Demo {
public static void main(String[] args) {
// 创建线程
YHRunnable yhRunnable = new YHRunnable();
Thread thread = new Thread(yhRunnable, "前门");
Thread thread1 = new Thread(yhRunnable, "后门");
thread.start();
thread1.start();
}
}
class YHRunnable implements Runnable {
// 操作的共享数据 100人
private int pNum = 100;
// 记录从前门入门的人数
private int qNum = 0;
// 记录从后门入门的人数
private int hNum = 0;
@Override
public void run() {
// 进门
while (true) {
synchronized (this) {
// 结束循环条件
if (pNum <= 0) {
break;
}
// 获取进入的线程的名字
String name = Thread.currentThread().getName();
int nowNum= 100 - pNum + 1;
// 前门进
if (name.equals("前门")) {
if (nowNum < 10) {
System.out.print("编号为: ");
} else if (nowNum < 100) {
System.out.print("编号为: ");
} else {
System.out.print("编号为: ");
}
System.out.print(nowNum + " 的员工,从前门入场!拿到的双色球彩票号码是:");
printCP();
pNum--;
qNum++;
}
// 后门进
if (name.equals("后门")) {
if (nowNum < 10) {
System.out.print("编号为: ");
} else if (nowNum < 100) {
System.out.print("编号为: ");
} else {
System.out.print("编号为: ");
}
System.out.print(nowNum + " 的员工,从后门入场!拿到的双色球彩票号码是:");
printCP();
pNum--;
hNum++;
}
// 人数为0时,打印人数
if (pNum == 0) {
System.out.println("从前门入场的员工总共:" + qNum + "位员工");
System.out.println("从后门入场的员工总共:" + hNum + "位员工");
}
}
Thread.yield();
}
}
// 打印彩票号的方法
public void printCP() {
ArrayList<Integer> list = new ArrayList<>();
// 循环添加到数组中
while(list.size() < 7) {
// 随机数
int num = (int)(Math.random() * (33 - 1 + 1) + 1);
// 添加到数组中
if (!list.contains(num)) {
list.add(num);
}
}
// 打印
System.out.println(list);
}
}
死锁
当两个或者两个以上的进程在执行工程中,由于竞争 CPU 资源
在相同时间或不同时间段内,循环等待该线程所要占用的资源,导致无限期的僵持
当多个线程需要相同的锁,但是按照不同的顺序加锁,就容易导致死锁的发生
模拟死锁
public class Demo {
public static void main(String[] args) {
DeadLockRunable deadLockRunable = new DeadLockRunable();
Thread thread = new Thread(deadLockRunable);
Thread thread1 = new Thread(deadLockRunable);
thread.start();
thread1.start();
}
}
// A 锁
class LockA{
// 私有构造方法
private LockA() {}
// 定义一个常量作为锁对象,不能修改,也不能创建
public static final LockA LOCK_A = new LockA();
}
class LockB{
private LockB() {}
public static final LockB LOCK_B = new LockB();
}
// 线程
class DeadLockRunable implements Runnable{
// 声明一个标记
// 标记一次是先 A 后 B ,一次先 B 后 A
private boolean isTrue = true;
@Override
public void run() {
// 死循环(增加死锁几率)
while (true) {
// 按标记
if (isTrue) {
// A 锁 -> B 锁
synchronized (LockA.LOCK_A) {
System.out.println("if...LOCK_A");
synchronized (LockB.LOCK_B) {
System.out.println("if...LOCK_B");
}
}
} else {
// B 锁 -> A 锁
synchronized (LockB.LOCK_B) {
System.out.println("else...LOCK_B");
synchronized (LockA.LOCK_A) {
System.out.println("else...LOCK_A");
}
}
}
// 修改标记
isTrue = !isTrue;
}
}
}
Lock
Lock(JDK 1.5 锁)
定义 Lock 锁
Lock.lock();
try{
写操作共享数据的代码
} finally{
解锁
lock.unlock();
}
public class Demo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread thread = new Thread(ticket);
Thread thread2 = new Thread(ticket);
Thread thread3 = new Thread(ticket);
thread.start();
thread2.start();
thread3.start();
}
}
class Ticket implements Runnable {
private static int tickets = 50;
// 声明锁对象
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
// 加锁
lock.lock();
try {
// 锁住操作共享数据的代码
if (tickets > 0) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + ":" + tickets);
tickets--;
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
// 让出 CPU 资源
Thread.yield();
}
}
}
线程中断
使用 stop 方法终止线程(不推荐,已过期)
使用 interrupt 方法终止线程
停止线程
只要线程停了,就是停止线程
使用 boolean 标记即可
测试 interrupt 中断线程
public class Demo {
public static void main(String[] args){
StopRunnable stopRunnable = new StopRunnable();
Thread thread = new Thread(stopRunnable);
thread.start();
// 给 thread 线程有执行的时间
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
System.out.println("中断线程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
class StopRunnable implements Runnable {
private boolean isOver = false;
public boolean isOver() {
return isOver;
}
public void setOver(boolean isOver) {
this.isOver = isOver;
}
@Override
public void run() {
// 利用死循环方式测试能不能停止线程
while (!Thread.currentThread().isInterrupted()) {
// 休眠一秒(死循环,让循环执行1s 结束)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Date date = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String format = dateFormat.format(date);
System.out.println(format);
}
}
}
// 使用标记法中断线程
public class Demo {
public static void main(String[] args){
StopRunnable stopRunnable = new StopRunnable();
Thread thread = new Thread(stopRunnable);
thread.start();
// 给 thread 线程有执行的时间
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stopRunnable.setOver(true);
System.out.println("中断线程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
class StopRunnable implements Runnable {
private boolean isOver = false;
public boolean isOver() {
return isOver;
}
public void setOver(boolean isOver) {
this.isOver = isOver;
}
@Override
public void run() {
// 利用死循环方式测试能不能停止线程
while (!isOver) {
// 休眠一秒(死循环,让循环执行1s 结束)
long currentTimeMillis = System.currentTimeMillis();
while (System.currentTimeMillis() - currentTimeMillis < 1000) {
}
Date date = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String format = dateFormat.format(date);
System.out.println(format);
}
}
}
/*
* 测试结果:不能中断线程(结束子线程)
* 这个方法的作用:
* interrupt()这个方法,设置了 isInterrupted 的布尔值
* 通过循环判断这个布尔值,来达到结束死循环从而结束线程的目的
*
* 如果线程中有 wait() 等待或者 sleep() 休眠
* 这时调用 interrupt()这个方法
* 会抛出一个异常 InterruptedException
* 并且清除中断状态
*
* 如果线程中没有 wait() 或者 sleep()
* 这时调用 interrupt() 这个方法
* 会设置中断状态(true/false的改变)
*/
/*
* 测试中断状态
* 注意:interrupt 方法尽量不要使用
* 如果要停止线程,直接使用标记法停止即可
* 只有遇到了等待状态时,可以使用该方法
* 强行清楚该等待状态
*
*
* IllegalMonitorStateException
* 对象监视器就是对象锁 synchronized
*/
public class Demo {
public static void main(String[] args) {
InterruptRunnable interruptRunnable = new InterruptRunnable();
Thread thread1 = new Thread(interruptRunnable, "线程①");
Thread thread2 = new Thread(interruptRunnable, "线程②");
thread1.start();
thread2.start();
for (int i = 0; i < 50; i++) {
if (i == 25) {
// 调用中断方法,来清除状态
thread1.interrupt();
thread2.interrupt();
break;
}
System.out.println(i + "*-*-*");
}
System.out.println("主线程结束");
}
}
class InterruptRunnable implements Runnable {
private boolean isOver = false;
public boolean isOver() {
return isOver;
}
public void setOver(boolean isOver) {
this.isOver = isOver;
}
@Override
public synchronized void run() {
while (true) {
try {
/*
* 线程1进来带着锁,遇到 wait 方法
* 放弃 CPU 的执行权,锁还回去
* 线程2进来带着锁,遇到 wait 方法
* 放弃 CPU 的执行权,锁还回去
* 相当于两个线程都在这里等着
* 进入冷冻(中断)状态
* 强行解决冷冻(中断)状态
* 调用 interrupt 方法 清除该状态
*/
wait();// 线程等待,放弃了 CPU 的执行资源
// wait 方法是 Object 类的
// 注意: wait 方法必须用锁对象去调用
} catch (InterruptedException e) {
e.printStackTrace();
}
Date date = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String format = dateFormat.format(date);
System.out.println(Thread.currentThread().getName() + format);
}
}
}
public class Demo {
public static void main(String[] args) {
VolRunnable volRunnable = new VolRunnable();
Thread thread = new Thread(volRunnable, "测试线程");
thread.start();
// 写个循环卡住主线程
// 子线程修改的值,没有同步到主线程中
while (!volRunnable.isOver()) {
}
System.out.println("主线程结束了");
}
}
class VolRunnable implements Runnable {
// volatile 关键字,可以把线程中的值,在修改之后,立即更新出去
private volatile boolean isOver = false;
private int num = 0;
public boolean isOver() {
return isOver;
}
public void setOver(boolean isOver) {
this.isOver = isOver;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public void run() {
Date date = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String format = dateFormat.format(date);
while (!isOver) {
num++;
// 把线程的执行时间加大
// 防止上面还没执行到循环处,这里的标记值就已经改了
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(format);
if (num > 4) {
isOver = true;
}
}
}
}
/*
* 开启2个线程
* 一个叫线程A
* 一个叫线程B
* 要求
* 先输入5遍 我是线程A
* 再输出5遍 我是线程B
*/
public class Demo {
public static void main(String[] args) {
ABRunnable abRunnable = new ABRunnable();
Thread thread = new Thread(abRunnable, "线程 A");
Thread thread2 = new Thread(abRunnable, "线程 B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
* 线程 A 先进来
* 直接打印,唤醒(但是没有线程在等待)
* 线程 B 再进来,进入等待...无期限等待
* 解决方法
* B 后进来不进入等待,直接打印
*
* 线程 B 先进来
* 进入等待,把锁还回去
* 线程 A 再进来,直接打印
* 唤醒线程 B, 线程 B 打印
*/
thread.start();
thread2.start();
}
}
class ABRunnable implements Runnable {
// 利用标记,解决 A 先进来的情况
/*
* 如果 A 先进来,直接打印,判断一下,如果是 A 则改变标记值
* 让 B 在进来的时候不去等待
*/
private boolean flag = false;
@Override
public void run() {
String name = Thread.currentThread().getName();
// 保证一个线程没打完,另一个线程不能进去
// 但是保证不了谁先进来
while (true) {
synchronized (this) {
// 判断如果是 B 就让 B 进入等待状态( wait 状态)
if (name.equals("线程 B") && flag == false) {
// 调用 wait 方法,进入等待
// 注意:使用锁对象去调用
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印名字5遍
for (int i = 0; i < 5; i++) {
System.out.println("我是" + name);
}
flag = !flag;
// 等 A 打印完,需要唤醒等待中的线程
// 注意: wait 和 notify 一般成对出现,都是使用锁对象调用
// 随机唤醒等待线程中一个
// notifyAll() 唤醒所有等待的线程
this.notify();
}
Thread.yield();
}
}
}
/*
* Person类 姓名 性别
* 开启两个线程
* 一个对Person对象进行赋值
* 一个对Person对象进行打印
* 要求
* 一次打印 张三 男
* 一次打印 张珊 女
* 间隔输出
*
* 1.先保证要操作的是同一个对象
* 2.要保证数据安全需要使用锁,并且要使用同一把锁
* 3.保证操作的逻辑顺序要对(先赋值,在打印)用 wait 和 notify
*/
public class Demo {
public static void main(String[] args) {
// 创建对象(只能使用同一个对象)
Person person = new Person();
// 创建线程
SetRunnable setRunnable = new SetRunnable(person);
Thread thread1 = new Thread(setRunnable);
PrintRunnable printRunnable = new PrintRunnable(person);
Thread thread2 = new Thread(printRunnable);
thread1.start();
thread2.start();
}
}
// 赋值线程
class SetRunnable implements Runnable {
// 利用成员变量,来操作同一个对象
private Person person;
// 定义一个标识通过改变标识来进行间隔赋值
private boolean isTrue = true;
// 利用构造方法来给成员变量赋值
public SetRunnable() {
super();
}
public SetRunnable(Person person) {
super();
this.person = person;
}
@Override
public void run() {
while (true) {
// 多个线程操作共享数据
// 需要添加锁
// 为了保证两个同步锁的锁对象相同,使用传进来的对象 person
/*
* 赋值线程需要赋值完毕,打印线程才能去打印
* 赋值时,打印线程等待,赋值完毕后,通知打印线程,打印线程才能打印
*
* 同样,打印线程打印完毕,赋值线程才能赋值
* 打印时,赋值线程等待,打印完毕后,通知赋值线程,赋值线程才能赋值
*
* wait 和 notify 使用一个标记来进行切换
* 这个标记赋值线程使用,也要给打印线程使用
* 注意:必须保证使用的是用一个标记,标记声明在 person 类中,即声明在共同对象中
*
*/
synchronized (person) {
// 判断 flag 为 true 时等待,不为 true 时赋值
if (person.flag == true) {
// 进行等待
try {
person.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 间隔赋值
if (isTrue) {
person.name = "张三";
person.gender = "男";
} else {
person.name = "zhangsan";
person.gender = "nv";
}
// 改变标识符
isTrue = !isTrue;
// 修改标记
person.flag = true;
// 唤醒线程
person.notify();
}
}
}
}
// 打印线程
class PrintRunnable implements Runnable {
// 利用成员变量,来操作同一个对象
private Person person;
public PrintRunnable() {
super();
}
public PrintRunnable(Person person) {
super();
this.person = person;
}
@Override
public void run() {
while (true) {
synchronized (person) {
if (person.flag == false) {
try {
person.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 打印
System.out.println(person);
// 修改标记
person.flag = false;
// 唤醒线程
person.notify();
}
}
}
}
class Person {
public String name;
public String gender;
// 声明标记来切换打印和赋值
public boolean flag = false;
@Override
public String toString() {
return "姓名:" + name + "\n年龄:" + gender;
}
}
对上个例题的代码优化
/*
* Person1类 姓名 性别
* 开启两个线程
* 一个对Person1对象进行赋值
* 一个对Person1对象进行打印
* 要求
* 一次打印 张三 男
* 一次打印 张珊 女
* 间隔输出
*
* 1.先保证要操作的是同一个对象
* 2.要保证数据安全需要使用锁,并且要使用同一把锁
* 3.保证操作的逻辑顺序要对(先赋值,在打印)用 wait 和 notify
*/
public class Demo {
public static void main(String[] args) {
// 创建对象(只能使用同一个对象)
Person1 person1 = new Person1();
// 创建线程
SetRunnable1 setRunnable1 = new SetRunnable1(person1);
Thread thread1 = new Thread(setRunnable1);
PrintRunnable1 printRunnable1 = new PrintRunnable1(person1);
Thread thread2 = new Thread(printRunnable1);
thread1.start();
thread2.start();
}
}
// 赋值线程
class SetRunnable1 implements Runnable {
// 利用成员变量,来操作同一个对象
private Person1 person1;
// 定义一个标识通过改变标识来进行间隔赋值
private boolean isTrue = true;
// 利用构造方法来给成员变量赋值
public SetRunnable1() {
super();
}
public SetRunnable1(Person1 person1) {
super();
this.person1 = person1;
}
@Override
public void run() {
while (true) {
/*
* 代码优化
* 把之前上锁的代码封装到类中
* 从同步代码块封装成同步方法
*/
if (isTrue) {
person1.setPerson("张三", "男");
} else {
person1.setPerson("张珊", "女");
}
isTrue = !isTrue;
}
}
}
// 打印线程
class PrintRunnable1 implements Runnable {
// 利用成员变量,来操作同一个对象
private Person1 person1;
public PrintRunnable1() {
super();
}
public PrintRunnable1(Person1 person1) {
super();
this.person1 = person1;
}
@Override
public void run() {
while (true) {
person1.print();
}
}
}
class Person1 {
public String name;
public String gender;
// 声明标记来切换打印和赋值
public boolean flag = false;
// 把操作共享数据的部分写成一个同步的方法
public synchronized void setPerson(String name, String gender) {
// 等待和唤醒
if (flag == true) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name = name;
this.gender = gender;
// 更改标记
flag = true;
// 唤醒线程
this.notify();
}
// 打印方法
public synchronized void print() {
if (flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(name + " " + gender);
flag = false;
this.notify();
}
@Override
public String toString() {
return "姓名:" + name + "\n年龄:" + gender;
}
}
public class Demo {
public static void main(String[] args) {
SingleRunnable singleRunnable = new SingleRunnable();
Thread thread1 = new Thread(singleRunnable);
Thread thread2 = new Thread(singleRunnable);
thread1.start();
thread2.start();
}
}
// 单例类 懒汉式
class Single {
private static Single single = null;
// 私有化构造方法
private Single() {
}
// 获取单例对象的方法
public static Single getInstance() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 提高效率
if (single == null) {
// 在这可以会有多个线程停留在这
synchronized (Single.class) {
// 造成数据不安全的原因
// 同时有多个线程访问相同数据
// 通过上锁去解决
if (single == null) {
single = new Single();
}
}
}
return single;
}
}
class SingleRunnable implements Runnable {
@Override
public void run() {
while (true) {
// 获取单例对象
Single single = Single.getInstance();
// 查看是否为单例对象
System.out.println(single.hashCode());
}
}
}
join 方法
public class Demo {
public static void main(String[] args) {
JRunnable jRunnable = new JRunnable();
Thread thread = new Thread(jRunnable);
thread.start();
try {
/*
* 告诉当前线程(主线程),线程要加入
* 等线程执行完毕时,当前线程再执行
*/
// 拿到所有 CPU 的执行权,停滞在这里
// 等自己的方法执行完,再把 CPU 的执行权交回去
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
System.out.println("main is over~");
}
}
class JRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
例题:
/*
* 保姆做家务
* 保姆 来到洗衣间 衣服放入洗衣机
* 洗衣机开始洗衣服 5次(1秒打印一次洗衣中…)
* 然后 保姆来到客厅 扫地 12次
* 洗衣机洗衣服 不影响保姆去客厅扫地
* 然后 保姆去晒衣间 晒衣服 2次
* (要求洗衣机洗完衣服 保姆也要扫完地才能去晒衣服)
* 然后打印 家务处理完毕
* 然后 保姆开始做 蹲 起 10次(交替 打印 蹲 起 各5次)
* 蹲起完成后 最后打印 蹲起完成 很开心
*/
// 睡眠时间,让线程"沉睡"1s
public class Sleep {
public static void time() {
long currentTimeMillis = System.currentTimeMillis();
while (System.currentTimeMillis() - currentTimeMillis < 1000) {
}
}
}
class CleanRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 12; i++) {
Sleep.time();
System.out.println("扫地中~");
}
System.out.println("打扫完成~");
}
}
class SunRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 2; i++) {
Sleep.time();
System.out.println("晒衣服~");
}
System.out.println("衣服晒完了~");
}
}
class WashRunnable implements Runnable {
@Override
public void run() {
System.out.println("来到洗衣间,衣服放入洗衣机~");
System.out.println("洗衣机开始洗衣服~");
for (int i = 0; i < 5; i++) {
Sleep.time();
System.out.println("洗衣中~");
}
System.out.println("洗衣机结束洗衣服~");
}
}
class DRunnable implements Runnable{
private BM bm;
public DRunnable() {
super();
}
public DRunnable(BM bm) {
super();
this.bm = bm;
}
@Override
public void run() {
while (true) {
// 跳出循环的条件
if (bm.num == 10) {
break;
}
bm.num++;
synchronized (bm) {
Sleep.time();
if (bm.isOver == true) {
try {
bm.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("蹲~");
bm.notify();
bm.isOver = !bm.isOver;
}
}
}
}
class QRunnable implements Runnable{
private BM bm;
public QRunnable() {
super();
}
public QRunnable(BM bm) {
super();
this.bm = bm;
}
@Override
public void run() {
while (true) {
// 跳出循环的条件
if (bm.num == 10) {
break;
}
bm.num++;
synchronized (bm) {
Sleep.time();
if (bm.isOver == false) {
try {
bm.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("起~");
bm.notify();
bm.isOver = !bm.isOver;
}
}
}
}
class BM implements Runnable{
public boolean isOver = false;
// 计数器
public int num = 0;
@Override
public void run() {
// 线程管理
// 洗衣服
WashRunnable washRunnable = new WashRunnable();
Thread thread = new Thread(washRunnable);
thread.start();
// 衣服是洗衣机洗的,保姆可以继续去扫地
CleanRunnable cleanRunnable = new CleanRunnable();
Thread thread2 = new Thread(cleanRunnable);
thread2.start();
try {
thread.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 等洗衣洗完,扫地扫完,去晒衣服
SunRunnable sunRunnable = new SunRunnable();
Thread thread3 = new Thread(sunRunnable);
thread3.start();
try {
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("家务处理完毕~");
// 蹲起
// 把本类对象传进,使用 this
DRunnable dqRunnable = new DRunnable(this);
Thread thread4 = new Thread(dqRunnable);
QRunnable qqRunnable = new QRunnable(this);
Thread thread5 = new Thread(qqRunnable);
thread4.start();
thread5.start();
try {
thread4.join();
thread5.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("蹲起完成 很开心~");
}
}
public class Test {
public static void main(String[] args) {
BM bm = new BM();
Thread thread = new Thread(bm);
System.out.println("保姆开始做家务~");
thread.start();
}
}
接口回调
/*
* 键盘输入
* 1.红色打印
* 2.黑色打印
* 根据输入数字的不同进行打印(打印"接口回调"即可)
*
* 定义一个接口
* 接口方法: 要求可以接收一个字符串做参数
*
* 定义一个功能类
* 要求: 接收一个接口对象作为参数 利用接口对象调用接口方法
*
* 定义两个类 实现接口
* 第1个类 普通打印
* 第2个类 错误(err)打印
*
*/
public class Demo {
@SuppressWarnings("resource")
public static void main(String[] args) {
System.out.println("请输入 1.红色打印 2.黑色打印");
while (true) {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
// 创建一个接口的空对象
Inter inter = null;
if (num == 1) {
// 创建打印红色类的对象
inter = new RedPrint();
} else {
// 创建打印黑色类的对象
inter = new BlackPrint();
}
// 使用功能类(按照不同的对象打印)
PrintClass.print(inter);
}
}
}
// 创建一个接口
interface Inter {
// 打印字符串
public abstract void print(String string);
}
// 创建两个类,实现接口
class RedPrint implements Inter {
@Override
public void print(String string) {
// 使用红色打印
System.err.println(string);
}
}
class BlackPrint implements Inter {
@Override
public void print(String string) {
// 使用黑色打印
System.out.println(string);
}
}
// 创建一个功能类,专门负责打印
// 不用管对象时谁,也不用管谁调用什么方法
// 只负责接受一个接口对象,调用接口方法
class PrintClass {
// 打印根据不同的对象,调用不同的方法
// 多态方法:增加方法扩展性
// 使用接口当参数
public static void print(Inter inter) {
// 调用接口中的方法
inter.print("接口回调");
}
}
public interface ReadFileInter {
public abstract void readFile(String string);
}
/*
* 功能类
*/
public class Read {
// 使用接口对象作为参数
public static void printFile(ReadFileInter readFileInter) {
// 开辟线程读取文件
new Thread(new Runnable() {
@Override
public void run() {
String str = "";
try {
FileReader fileReader = new FileReader("src/com/lanou3g/BM/BM.java");
BufferedReader bufferedReader = new BufferedReader(fileReader);
while ((str = bufferedReader.readLine()) != null) {
// 读文件
readFileInter.readFile(str);
}
bufferedReader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
/*
* 利用接口实现,主线程处理逻辑
* 子线程去读取文件,并打印
*/
public class Demo02 {
public static void main(String[] args) {
// 使用匿名对象方式传参
Read.printFile(new ReadFileInter() {
// 匿名对象重写的方法
@Override
public void readFile(String string) {
// 打印读取完的文件
System.out.println(string);
}
});
}
}
http://blog.csdn.net/huzongnan/article/list