概述
线程与进程的关系
一个进程相当于一个应用程序
一个线程是进程处理中会启用多个线程完成这个进程
进程A和进程B内存独立不共享。
线程A和线程B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈
main方法结束之后主栈结束了,主栈空了,但其他栈(线程)可能还在压栈弹栈
单核CPU表示只有一个大脑,不能做到真正的多线程并发,但是给人做到一种“多线程并发的感觉”,处理速度极快,多个线程之间频繁切换执行。
实现线程的两种方式
第一种方式:编写一个类继承java.lang.Thread,重写run方法
start()方法的作用是:启动一个分支线程,在JWM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。这段代码的任务只是为了开启一个新的栈空间,只要新的空间开出来,stant()方法就结束了。线程就启动成功了。
启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
public class Test1 {
public static void main(String[] args) {
Mythread m=new Mythread();
m.start();
for (int i = 0; i <100 ; i++) {
System.out.println("主线程-->"+i);
}
}
}
class Mythread extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("分支线程-->"+i);
}
}
}
第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法
public class Test02 {
public static void main(String[] args) {
//创建一个可运行的对象
//MyRunnable r=new MyRunnable();
//将可运行的对象封装成一个线程对象
//Thread t=new Thread(r);
//合并
Thread t=new Thread(new MyRunnable());
t.start();
for (int i = 0; i <100 ; i++) {
System.out.println("主线程-->"+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("分支线程-->"+i);
}
}
}
避免了单继承的局限性,继承了Thread就不能继承别的类了
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源
采用匿名内部类的方式
public class Test03 {
public static void main(String[] args) {
//创建线程对象,采用匿名内部类
//通过一个没有名字的类,new出来的对象
//接口不能new对象
Thread t=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("分支线程-->"+i);
}
}
});
t.start();
for (int i = 0; i <100 ; i++) {
System.out.println("主线程-->"+i);
}
}
}
线程的方法
currentThread()方法
静态方法
public class Test1 {
public static void main(String[] args) {
Mythread m=new Mythread();
Mythread t=new Mythread();
m.setName("m");
t.setName("t");
m.start();
t.start();
for (int i = 0; i <100 ; i++) {
//当前线程的名字,currentThread()是静态方法
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class Mythread extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
线程sleep方法
静态方法,参数是毫秒,让当前线程进入休眠,放弃当前占有的CPU时间片,给其他线程使用
public class Test04 {
public static void main(String[] args) {
//让当前线程进入休眠,睡眠5秒
//当前线程是主线程
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello world");
}
}
public class Test04 {
public static void main(String[] args) {
//让当前线程进入休眠,睡眠5秒
//当前线程是主线程
//间隔特定的时间执行特定的代码
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
try {
Thread.sleep(1000*1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
面试题
public class Test1 {
public static void main(String[] args) {
Thread t=new Mythread();//多态,下面要调用Thread的sleep方法,静态方法也可以new个对象出来调方法
t.setName("t");
t.start();
//调用sleep方法
try {
t.sleep(1000);//让当前线程就是main线程睡眠,所以helloworld1秒后输出
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello world");
}
}
class Mythread extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
终止线程的睡眠
public class Test02 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable());
t.start();
//希望5秒之后t线程醒来
t.interrupt();//一盆冷水过去,这种终端睡眠的方式依靠java的异常机制,会打印异常信息。
// 可以把e.printStackTrace();注释掉就不会打印了
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->begin");
//睡一年
//MyRunnable继承Runnable,Runnable中的run方法没有抛出异常
//run方法处理异常不能throws,只能try catch,因为run方法在父类中没有抛出异常
//子类不能抛出更多的异常
try {
Thread.sleep(1000*60*60*24*365);//这里处理异常不能throws,只能try catch
} catch (InterruptedException e) {
//e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->end");
}
}
强行终止线程的执行
t.stop();过时了,缺点,容易丢失数据,就相当于在任务管理器里直接结束进程
public class Test02 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable());
t.start();
try {
Thread.sleep(1000*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.stop();//过时了
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
try {
Thread.sleep(1000*1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
合理的终止线程
public class Test02 {
public static void main(String[] args) {
MyRunnable r=new MyRunnable();
Thread t=new Thread(r);
t.start();
try {
Thread.sleep(1000*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
r.run=false;
}
}
class MyRunnable implements Runnable{
boolean run=true;//打个布尔标记
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (run) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
try {
Thread.sleep(1000*1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//在结束之前可以进行保存数据
//save...
//终止线程
return;
}
}
}
}
线程调度
t.getPriority();获得优先级
t.setPriority(); 设置优先级
t.yield(); 让位
t.join();线程合并 t线程合并到当前线程上,t线程执行结束后,当前线程再继续执行,出现等待
线程安全
关于多线程并发环境下,数据安全问题
项目是运行在服务器上的,而服务器已经将线程的定义,线程对象的创建,线程的启动等实现完了,这些代码不需要编写。
需要关心的是编写的程序放到多线程的环境下运行,数据是否安全
什么时候数据会在多线程并发环境中存在安全问题?
三个条件:
条件1:多线程并发
条件2:有共享数据
条件3:共享数据有修改行为
怎么解决线程安全问题?
线程排队执行(不能并发),这种机制成为线程同步机制,实际上就是线程不能并发了,要排队执行了。会牺牲一部分效率
同步编程模型:
线程1和线程2,在线程1执行的时候,必须等待线程2执行结束时才能继续执行
异步编程模型:
线程1和线程2各自执行,谁也不管谁。就是多线程并发
总结:同步就是排队,异步就是并发
同一个账户,不同用户取钱
public class Mo {
public static void main(String[] args) {
Account a=new Account(3000);
Thread t1=new Thread(new User("支付宝",a,2000));
Thread t2=new Thread(new User("微信",a,2000));
t1.setName("支付宝线程");
t2.setName("微信线程");
t1.start();
t2.start();
}
}
class Account{
private double money;
// Object obj=new Object();
public Account(double money) {
this.money = money;
}
public void quqian(double x){
//以下代码必须线程排队,不能并发
//一个线程结束另外一个线程才能进来
/*
线程同步机制语法,synchronized (){}
小括号里面传进来数据的很关键,必须是多线程共享的数据
()填写共享数据,想让哪几个线程同步(排队),就填哪几个线程的共享对象,
这里是account对象,因为这是在Account类下,所以填this就行了
如果Account类下面有个其他类的对象属性,括号里也可以填这个对象
比如:Object obj=new Object();,括号里也可以填obj
synchronized ("abc")所有线程都会同步,因为"abc"在字符串常量池中
synchronized (){}原理是,t1线程过来,把共享对象锁锁住,就被t1占有了,等t1运行完,释放锁,t2才能接着执行
*/
synchronized (this){
if(money>x){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money=money-x;
System.out.println(Thread.currentThread().getName()+"取钱,取钱成功,余额为:"+money);
}else {
System.out.println(Thread.currentThread().getName()+"取钱,余额不足,当前余额为:"+money);
}
}
}
}
class User implements Runnable{
String name;
Account account;
double x;
public User(String name, Account account, double x) {
this.name = name;
this.account = account;
this.x = x;
}
@Override
public void run() {
this.account.quqian(x);
}
}
哪些变量有线程安全问题
实例变量:在堆中
静态变量:在方法区
局部变量:在栈中
局部变量不存在线程安全问题,因为局部变量不共享,(一个线程一个栈)
扩大同步范围
synchronized (){}中代码块越多效率越多
上述代码可以改成下面,共享对象就是account
public class Mo {
public static void main(String[] args) {
Account a=new Account(3000);
Thread t1=new Thread(new User("支付宝",a,2000));
Thread t2=new Thread(new User("微信",a,2000));
t1.setName("支付宝线程");
t2.setName("微信线程");
t1.start();
t2.start();
}
}
class Account{
private double money;
// Object obj=new Object();
public Account(double money) {
this.money = money;
}
public void quqian(double x){
//以下代码必须线程排队,不能并发
//一个线程结束另外一个线程才能进来
/*
线程同步机制语法,synchronized (){}
小括号里面传进来数据的很关键,必须是多线程共享的数据
()填写共享数据,想让哪几个线程同步(排队),就填哪几个线程的共享对象,
这里是account对象,因为这是在Account类下,所以填this就行了
如果Account类下面有个其他类的对象属性,括号里也可以填这个对象
比如:Object obj=new Object();,括号里也可以填obj
synchronized ("abc")所有线程都会同步,因为"abc"在字符串常量池中
synchronized (){}原理是,t1线程过来,会去锁池走一趟,找到共享对象,把共享对象锁锁住,就被t1占有了,等t1运行完,释放锁,t2才能接着执行
*/
if(money>x){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money=money-x;
System.out.println(Thread.currentThread().getName()+"取钱,取钱成功,余额为:"+money);
}else {
System.out.println(Thread.currentThread().getName()+"取钱,余额不足,当前余额为:"+money);
}
}
}
class User implements Runnable{
String name;
Account account;
double x;
public User(String name, Account account, double x) {
this.name = name;
this.account = account;
this.x = x;
}
@Override
public void run() {
synchronized (account){
this.account.quqian(x);
}
}
}
这种方式扩大了同步范围
synchronized出现在实例方法上
public synchronized void quqian(double x){}
缺点:
这种方法锁的是this,不能是其他对象了,这种方法不灵活
锁的是方法体,整个方法体会同步,可能会无故扩大同步范围,导致效率降低
优点:
代码简洁
注意:
如果使用局部变量,建议使用StringBuilder,因为局部变量不存在线程安全,StringBuffer有synchronized关键字,会去锁池走一趟,效率较低
ArrayList是非线程安全的,Vector是线程安全的
HashMap,HashSet是非线程安全的,Hashtable是线程安全的
synchronized关键字还可以加在静态方法上,形成类锁,类锁只有一把
以下四种情况
public class SynchronizedTest01 {
public static void main(String[] args) {
MyClass ms=new MyClass();
Thread t1=new MyThread(ms);
Thread t2=new MyThread(ms);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyClass{
public synchronized void dosome(){
System.out.println("dosome begin");
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("dosome over");
}
public void doother(){
System.out.println("doother begin");
System.out.println("doother over");
}
}
class MyThread extends Thread{
private MyClass mc ;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
mc.dosome();
}if(Thread.currentThread().getName().equals("t2")){
mc.doother();
}
}
}
1,synchronized出现在实例方法上,这种方法锁的是this,这里只有dosome方法加了synchronized,当t1线程开启时,会找到MyClass的对象mc,将它锁住,继续执行dosome方法,等待5秒后,t2线程开启,因为doother方法没加synchronized,所以不需要找对象锁,执行doother方法不需要等待dosome方法执行完毕释放锁后,再执行。
public class SynchronizedTest01 {
public static void main(String[] args) {
MyClass ms=new MyClass();
Thread t1=new MyThread(ms);
Thread t2=new MyThread(ms);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyClass{
public synchronized void dosome(){
System.out.println("dosome begin");
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("dosome over");
}
public synchronized void doother(){
System.out.println("doother begin");
System.out.println("doother over");
}
}
class MyThread extends Thread{
private MyClass mc ;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
mc.dosome();
}if(Thread.currentThread().getName().equals("t2")){
mc.doother();
}
}
}
2,这种情况,doother方法也加上了锁,t2线程执行时,也要去找对象锁,一个对象一把锁,但此时对象锁被t1占用,所以要等待t1执行完,释放掉锁之后才能执行
public class SynchronizedTest01 {
public static void main(String[] args) {
MyClass ms=new MyClass();
MyClass ms1=new MyClass();
Thread t1=new MyThread(ms);
Thread t2=new MyThread(ms1);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyClass{
public synchronized void dosome(){
System.out.println("dosome begin");
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("dosome over");
}
public synchronized void doother(){
System.out.println("doother begin");
System.out.println("doother over");
}
}
class MyThread extends Thread{
private MyClass mc ;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
mc.dosome();
}if(Thread.currentThread().getName().equals("t2")){
mc.doother();
}
}
}
3,这次new了两个MyClass对象,锁分别去找mc,和mc1的锁,两把锁,不需要等待
public class SynchronizedTest01 {
public static void main(String[] args) {
MyClass ms=new MyClass();
MyClass ms1=new MyClass();
Thread t1=new MyThread(ms);
Thread t2=new MyThread(ms1);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyClass{
public synchronized static void dosome(){
System.out.println("dosome begin");
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("dosome over");
}
public synchronized static void doother(){
System.out.println("doother begin");
System.out.println("doother over");
}
}
class MyThread extends Thread{
private MyClass mc ;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
mc.dosome();
}if(Thread.currentThread().getName().equals("t2")){
mc.doother();
}
}
}
4,方法加上了静态,虽然new了两个对象,但是在静态方法上加Synchronized是类锁,只有一把,这种情况也要等待
死锁
public class Deadlock {
public static void main(String[] args) {
Object o1=new Object();
Object o2=new Object();
Thread t1=new MyThread1(o1,o2);
Thread t2=new MyThread1(o1,o2);
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1){//这个大括号代码执行结束才能释放o1的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread{
Object o1;
Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (o1){
}
}
}
不要嵌套使用 synchronized
总结
解决线程安全问题,不要一上来就synchronized,这会让程序效率降低,系统的用户吞吐量(并发量)降低。
第一种方案: 尽量使用局部变量代替实例变量,静态变量
第二种方案:如果必须是实例变量,可以考虑创建多个对象,这样实例变量的内存不共享了(一个线程对应一个对象,对象不共享就没有线程安全问题了)
第三种方案:使用synchronized
守护线程
线程分为守护线程(后台线程),用户线程
主线程main方法是一个用户线程
后台线程,例如垃圾回收线程
一般守护线程是一个死循环,用户线程只要结束,守护线程自动结束
比如每天系统自动备份,要使用到定时器
t1.setDaemon(true);
设置为守护线程
定时器
间隔特定的时间执行特定的程序
每天进行数据备份
可以使用sleep
实际开发用spring底层是java.util.Timer实现的
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) throws Exception {
//创建定时器对象
//Timer timer=new Timer(true);设置成守护线程
Timer timer=new Timer();
//指定定时任务
//timer.schedule(定时任务,第一次执行的时间,时隔多久执行一次);
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firsttime=sdf.parse("2020-8-28 17:23:00");//将字符串转化成时间
//timer.schedule(null,firsttime,1000*5);
timer.schedule(new LogTimerTask(),firsttime,1000*5);
}
}
//编写一个定时任务类,第一个参数是个抽象类,不能new所以写一个类继承TimerTask
class LogTimerTask extends TimerTask{
@Override
public void run() {
//定时执行的代码
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime=sdf.format(new Date());
System.out.println(strTime+": 成功完成备份");
}
}
实现线程的第三种方式
实现Callable接口
这种方式可以获取线程的返回值
委派一个线程执行一个任务,该任务执行完成之后,可能会出现一个执行结果,怎么拿到这个执行结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;//java并发包,新特性
public class ThreadTest01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//第一步,创建一个未来任务类对象
//参数是个接口,Ctrl加左键可以看到
FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {//call方法相当于run方法,只不过这有返回值
System.out.println("call method begin");
Thread.sleep(1000*2);
System.out.println("call method over");
int i=100;
int b=2;
return i+b;//返回的是个对象,自动装箱
}
});
//第二步,创建线程对象
Thread t1=new Thread(task);
//第三步,启动线程
t1.start();
//拿到返回值
//这里是main方法,主线程中,怎么获取t1线程的返回结果
Object o=task.get();//main方法受阻,要等待t1线程完成,拿到返回值
System.out.println(Thread.currentThread().getName()+"返回结果是: "+o);//这里当前线程不是t1线程而是main线程
System.out.println("helloworld");//要等上面完成才能输出
}
}
优点:可以拿到返回值
缺点:效率低
关于Object中的wait和notify方法
Object o=new Object();
o.wait表示
让正在o对象上活动的线程进入等待状态,无限期等待,直到唤醒。并且释放o对象的锁
o.notify表示
唤醒等待的线程对象,只是通知,不会占有之前的锁
生产者消费者模式
生产一个消费一个
import java.util.ArrayList;
import java.util.List;
/*
使用wait方法和notify方法实现生产者消费者模式
生产线程负责生产,消费线程负责消费,生产消费要平衡
对同一个仓库操作,有线程安全问题
仓库采用list集合,假设只能存一个元素
生产一个消费一个
*/
public class Test06 {
public static void main(String[] args) {
//创建仓库对象,共享
List list =new ArrayList();
//创建两个线程对象
//生产者线程
Thread t1=new Thread(new Producer(list));
//消费者线程
Thread t2=new Thread(new Consumer(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
//生产线程
class Producer implements Runnable{
//仓库
private List list;
//保证同一个仓库,通过构造方法来实现同一个list
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
//一直生产
while (true){
//给仓库list加锁,生产过程中不能消费
synchronized (list) {
if(list.size()>0){
try {
//当前线程进入等待,释放list集合的锁,如果不释放锁,消费者就不能对list进行操作
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果执行到这,说明仓库是空的
Object obj=new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName()+"-->生产一个 :"+obj);
//唤醒消费者
list.notify();
}
}
}
}
//消费线程
class Consumer implements Runnable{
//仓库
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
//一直消费
while (true){
synchronized (list){
if(list.size()==0){
try {
//仓库已经空了,消费者线程进行等待,释放掉锁
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序到此处说明仓库有东西可以消费
Object obj=list.remove(0);
System.out.println(Thread.currentThread().getName()+"-->"+obj+"已经被消费");
try {
Thread.sleep(1000*1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒生产者生产
list.notify();
}
}
}
}
生产多个消费多个
import java.util.ArrayList;
import java.util.List;
/*
使用wait方法和notify方法实现生产者消费者模式
生产线程负责生产,消费线程负责消费,生产消费要平衡
对同一个仓库操作,有线程安全问题
仓库采用list集合,假设只能存一个元素
生产一个消费一个
*/
public class Test06 {
public static void main(String[] args) {
//创建仓库对象,共享
List list =new ArrayList();
//创建两个线程对象
//生产者线程
Thread t1=new Thread(new Producer(list));
//消费者线程
Thread t2=new Thread(new Consumer(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
//生产线程
class Producer implements Runnable{
//仓库
private List list;
//保证同一个仓库,通过构造方法来实现同一个list
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
//一直生产
while (true){
//给仓库list加锁,生产过程中不能消费
synchronized (list) {
if(list.size()>=10){
try {
//当前线程进入等待,释放list集合的锁,如果不释放锁,消费者就不能对list进行操作
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果执行到这,说明仓库是空的
Object obj=new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName()+"-->生产一个 :"+obj);
//唤醒消费者
if(list.size()==10){
list.notify();
}
}
}
}
}
//消费线程
class Consumer implements Runnable{
//仓库
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
//一直消费
while (true){
synchronized (list){
if(list.size()==0){
try {
//仓库已经空了,消费者线程进行等待,释放掉锁
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序到此处说明仓库有东西可以消费
Object obj=list.remove(0);
System.out.println(Thread.currentThread().getName()+"-->"+obj+"已经被消费");
try {
Thread.sleep(1000*1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒生产者生产
list.notify();
}
}
}
}
wait方法是该线程等待并且释放掉锁,notify是唤醒线程,唤醒之后可能还是被当前线程抢到,但没关系,因为有If判断
交替输出
public class ZuoYe {
public static void main(String[] args) {
num n =new num(1);
Thread t1=new Thread(new sout0(n));
Thread t2=new Thread(new sout1(n));
t1.setName("t2");
t2.setName("t1");
t1.start();
t2.start();
}
}
class sout0 implements Runnable{
num n;
public sout0(num n) {
this.n = n;
}
@Override
//如果是偶数就数这个线程输出,命名后是t2
public void run() {
while (true){
synchronized (n){
if(n.i%2!=0){
try {
n.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-->"+n.i);
n.i++;
n.notify();
}
}
}
}
class sout1 implements Runnable{
num n;
public sout1(num n) {
this.n = n;
}
@Override
//如果是奇数就数这个线程输出,命名后是t1
public void run() {
while (true){
synchronized (n){
if(n.i%2==0){
try {
n.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000*1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+n.i);
n.i++;
n.notify();
}
}
}
}
class num {
int i;
public num(int i) {
this.i = i;
}
}