Day18_进程(中)
常用的一些方法
join():半路杀出程咬金
sleep(1000):线程停止1000ms。
yield():暂停当前线程,使当前线程转入就绪阶段
stop():过期方法
伴随线程
public class Bansui extends Thread{
@Override
public void run() {
for (int i = 1; i <=1000 ; i++) {
System.out.println("Bansui----"+i);
}
}
public static void main(String[] args) {
for (int i = 1; i <=10 ; i++) {
if(i==6){
Bansui bs=new Bansui();
//设置伴随线程,参数设置为true,实现"同生共死",但是两个线程停止时间点会有时间间隔
bs.setDaemon(true);
bs.start();
}
System.out.println("main---"+i);
}
}
}
输出
main---1
main---2
main---3
main---4
main---5
main---6
main---7
main---8
main---9
Bansui----1
main---10
Bansui----2
Bansui----3
Bansui----4
Bansui----5
Bansui----6
Bansui----7
Bansui----8
Bansui----9
Bansui----10
Bansui----11
Bansui----12
Bansui----13
线程的同步
加锁
同步代码块
public class Ticket implements Runnable{
private int ticketNum=10;
@Override
public void run() {
for (int i = 1; i <=100; i++) {
//同步代码块
//加锁
synchronized (Ticket.class){
if(ticketNum>0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了剩余的第"+ticketNum--+"张火车票!");
}
}
}
}
}
public class ticketTest {
public static void main(String[] args) {
Ticket tk=new Ticket();
Thread t1=new Thread(tk,"窗口1");
t1.start();
Thread t2=new Thread(tk,"窗口2");
t2.start();
Thread t3=new Thread(tk,"窗口3");
t3.start();
}
}
输出:
我在窗口1买到了剩余的第10张火车票!
我在窗口1买到了剩余的第9张火车票!
我在窗口1买到了剩余的第8张火车票!
我在窗口1买到了剩余的第7张火车票!
我在窗口1买到了剩余的第6张火车票!
我在窗口1买到了剩余的第5张火车票!
我在窗口1买到了剩余的第4张火车票!
我在窗口1买到了剩余的第3张火车票!
我在窗口1买到了剩余的第2张火车票!
我在窗口1买到了剩余的第1张火车票!
同步方法
public class Ticket implements Runnable{
private int ticketNum=10;
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
buy();
}
}
//同步方法
public synchronized void buy(){
if(ticketNum>0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了剩余的第"+ticketNum--+"张火车票!");
}
}
}
public class ticketTest {
public static void main(String[] args) {
//同一个tk对象,所以一把锁即可实现同步
Ticket tk=new Ticket();
Thread t1=new Thread(tk,"窗口1");
t1.start();
Thread t2=new Thread(tk,"窗口2");
t2.start();
Thread t3=new Thread(tk,"窗口3");
t3.start();
}
}
我在窗口1买到了剩余的第10张火车票!
我在窗口1买到了剩余的第9张火车票!
我在窗口1买到了剩余的第8张火车票!
我在窗口1买到了剩余的第7张火车票!
我在窗口1买到了剩余的第6张火车票!
我在窗口1买到了剩余的第5张火车票!
我在窗口3买到了剩余的第4张火车票!
我在窗口3买到了剩余的第3张火车票!
我在窗口3买到了剩余的第2张火车票!
我在窗口3买到了剩余的第1张火车票!
总结
synchronized
- 必须是引用数据类型,不能是基本数据类型
- 在同步代码块中可以改变同步监视器对象的值,不能改变其引用
- 尽量不要String和包装类Integer做同步监视器。如果使用了,只要保证代码块不对其进行任何操作也没关系
- 一般使用共享资源做同步监视器即可
- 也可以创建一个专门的同步监视器,没有任何业务含义
- 建议使用final修饰同步监视器
同步代码块的执行过程
- 第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码
- 第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去CPU,但是没有开锁open
- 第二个线程获取了CPU,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态
- 第一个线程再次获取了CPU,接着执行了后续的代码,同步代码块执行完毕,释放锁open
- 第二个线程也再次获取CPU,来到了同步代码块,发现同步监视器open状态,重复第一个线程的处理过程(加锁)。强调:同步代码块中能发生线程切换吗?能!!但是后续的被执行的线程也无法执行同步代码块(锁仍旧close;
线程同步优缺点
优点:安全
缺点:效率低下,可能出现死锁
第三种加锁方式Lock
Lock lock=new ReentrantLock();
//加锁
lock.lock();
try{
//要锁的代码
}finally{
//解除锁
lock.unlock();
}
Lock更加灵活,但是只能使用同步块,不能用同步方法。
线程的通信
锁池:synchronized
等待池:wait(),notify(),notifyAll()
如果一个线程调用了某个对象的wait()方法,那么该线程进入到该对象的等待池中(并且已经将锁释放),如果未来的某个时刻,另外一个线程调用了相同对象的notify方法或者notifyAll方法,那么该等待池中的线程就会被唤起,然后进入到对象的锁池里面去获得该对象的锁,如果获得锁成功后,那么该线程就会沿着wait方法之后的语句继续执行,注意是沿着wait方法之后的语句继续执行。
public class Shangpin {
private String name;
private String brand;
//信号灯
boolean flag=false;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
//生产者
public synchronized void setSp(String brand,String name){
//有商品,生产者等待
if(flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setBrand(brand);
this.setName(name);
System.out.println("生产者生产了"+this.getBrand()+"---"+this.getName());
//生产出来了,通知消费者来取产品
flag=true;
notify();
}
//消费者
public synchronized void getSp() {
//无商品,消费者等待
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了" + this.getBrand() + "---" + this.getName());
//消费者取完产品,通知生产者生产
flag = false;
notify();
}
}
public class Shengchanzhe implements Runnable {
private Shangpin sp;
public Shengchanzhe(Shangpin sp) {
this.sp = sp;
}
@Override
public void run() {
//为什么要加for循环,因为要生产10个商品,每生产一个商品,就会停下来等待消费者消费商品
//可以尝试把消费者中的for循环删除,如果能解释结果,就说明对线程的运行有所了解了
//原因是:没加for循环时,消费者只消费了一次,所以线程就停在那里了
for (int i = 1; i <=10 ; i++) {
if(i%2==0){
sp.setSp("DOVE","chocolate");
}else{
sp.setSp("青岛","啤酒");
}
}
}
}
public class Xiaofeizhe implements Runnable{
private Shangpin sp;
public Xiaofeizhe(Shangpin sp) {
this.sp = sp;
}
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
sp.getSp();
}
}
}
public class ShangpinTest {
public static void main(String[] args) {
Shangpin sp=new Shangpin();
Shengchanzhe scz=new Shengchanzhe(sp);
new Thread(scz).start();
Xiaofeizhe xfz=new Xiaofeizhe(sp);
new Thread(xfz).start();
}
}
输出
生产者生产了青岛---啤酒
消费者消费了青岛---啤酒
生产者生产了DOVE---chocolate
消费者消费了DOVE---chocolate
生产者生产了青岛---啤酒
消费者消费了青岛---啤酒
生产者生产了DOVE---chocolate
消费者消费了DOVE---chocolate
生产者生产了青岛---啤酒
消费者消费了青岛---啤酒
生产者生产了DOVE---chocolate
消费者消费了DOVE---chocolate
生产者生产了青岛---啤酒
消费者消费了青岛---啤酒
生产者生产了DOVE---chocolate
消费者消费了DOVE---chocolate
生产者生产了青岛---啤酒
消费者消费了青岛---啤酒
生产者生产了DOVE---chocolate
消费者消费了DOVE---chocolate