多线程指的是程序中包含多个不同的执行单元,一个程序中可以允许多个线程执行不同的任务
优点:
提高程序的响应.
提高CPU的利用率.
改善程序结构,将复杂任务分为对个线程,独立运行.
缺点:
并发:多个CPU同时执行多个任务
并行:一个CPU同时执行多个任务(同一个对象被多个线程同时操作)
多线程同时读取同一份共享资源时,可能会引起冲突,需要引入线程"同步机制"(即各个线程之间有先来后到的顺序)
同步: 排队 + 锁
排队 : 对共享资源操作时,几个线程之间要排队,不能同时操作
锁: 保证数据在方法中被访问时的正确性,在访问时加入锁机制
Java中synchronized关键字同步方法或代码块
synchronized(同步对象){需要被同步的代码块}
synchronized 返回值类型 方法名(){需要 被同步的代码} 适用于实现了Runnable接口的类
static synchronized 返回值类型 方法名(){需要 被同步的代码} 适用于继承Thread的类
我们用一个窗口售票的程序来演示
为代码块添加同步锁
/* 售票线程 */ public class TicketThread extends Thread{ //使用static关键字,将成员变量设为整个类的共享资源 static int num = 10;//剩余票数 static Object obj = new Object();//保证只有一个 @Override public void run() { while (true){ //synchronized 同步锁,就像一个对象,但要求对象只有一个线程 synchronized (obj){ if(num>0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"剩余票:"+num); num--; }else { break; } } //synchronized同步代码块,执行完成后,同步对象(锁)会自动释放 } } }
为方法添加同步锁
/* 售票线程 */ public class TicketThread extends Thread{ //使用static关键字,将成员变量设为整个类的共享资源 static int num = 10;//剩余票数 static Object obj = new Object();//保证只有一个 /*@Override public void run() { while (true){ if(num>0){ print(); }else{ break; } } } /* synchronized修饰方法时,同步对象锁,默认是this 一旦创建多个线程对象 用static 修饰同步方法,同步对象-->线程类 这种情况只针对继承了Thread的类(Thread的类实例化一个线程对象,就执行一个单独的任务,不加static,多个线程之间对共享资源num 的使用有冲突) */ public static synchronized void print(){ if(num>0){ try { Thread.sleep(1000);//休眠1s } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"剩余票:"+num); num--; } }
public class TicketThreadTest { public static void main(String[] args) { //创建两个线程对象 执行两个任务 TicketThread t1 = new TicketThread(); TicketThread t2 = new TicketThread(); t1.setName("窗口1"); t2.setName("窗口2"); t1.start(); t2.start(); } }
运行结果:
/* Thread : 多个线程对象,执行多个任务 Runnable : 多个线程对象,执行一个任务 */ public class TicketRunnable implements Runnable{ //使用static关键字,将成员变量设为整个类的共享资源 static int num = 10;//剩余票数 @Override public void run() { while (true){ /*方式一 synchronized(){要同步的代码块} 添加同步对象,这种方式可以使用this表示锁对象(Runnable接口的实现类这样使用,Thread的子类需要创一个静态的对象,使用静态对象作为同步对象) */ synchronized (this){ if(num>0){ try { Thread.sleep(1000);//休眠 } catch (InterruptedException e) { e.printStackTrace(); } //输出剩余票数 System.out.println(Thread.currentThread().getName()+"剩余票:"+num); num--;//票数-1 }else{ break; } }//同步代码块,执行完成后,同步对象(锁)会自动释放 } } }
同步方法
/* Thread : 多个线程对象,执行多个任务 Runnable : 多个线程对象,执行一个任务 */ public class TicketRunnable implements Runnable{ //使用static关键字,将成员变量设为整个类的共享资源 static int num = 10;//剩余票数 @Override public void run() { while (true){ if(num>0){//票数>0时输出剩余票数 print(); }else{ break;//<0退出 } } } //在方法声明中添加synchronized同步监视器,表示整个方法为同步方法 public synchronized void print(){ if(num>0){ try { Thread.sleep(1000);//休眠 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"剩余票:"+num); num--;//票数-1 } }
//测试类 public class TicketRunnableTest { public static void main(String[] args) { //创建一个任务对象 TicketRunnable ticketRunnable = new TicketRunnable(); //创建两个线程,执行同一任务 Thread t1 = new Thread(ticketRunnable,"窗口1"); Thread t2 = new Thread(ticketRunnable,"窗口2"); t1.start(); t2.start(); } }
运行结果:
JDK5.0开始,java提供了更强大的线程同步机制,通过显式定义同步锁对象
同步锁使用Lock对象 ,每次只能有一个线程对Lock对象加锁,线程访问共享资源之前要先获得Lock对象
ReetrantLock是Lock的实现类,可以显式的添加/释放锁
package day2.demo2; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockThreadDemo1 implements Runnable{ //使用static关键字,将成员变量设为整个类的共享资源 static int num = 10;//剩余票数 Lock lock = new ReentrantLock(); @Override public void run() { while (true){ if(num>0){ print(); }else{ break; } } } public void print(){ try { lock.lock();//上锁 try { Thread.sleep(1000);//休眠1s } catch (InterruptedException e) { e.printStackTrace(); } if(num>0){ System.out.println(Thread.currentThread().getName()+"剩余票:"+num); num--; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock();//解锁,放在finally中,锁一定会被释放 } } }
public class LockThreadDemo1Test { public static void main(String[] args) { LockThreadDemo1 l = new LockThreadDemo1(); //两个线程执行一个任务 l Thread t1 = new Thread(l); Thread t2 = new Thread(l); t1.start(); t2.start(); } }
运行结果:
死锁 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步 资源,就形成了线程的死锁. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续.
public class LockThreadDemo2 extends Thread{ static Object objA = new Object(); static Object objB = new Object(); boolean flag; public LockThreadDemo2(boolean flag) { this.flag = flag; } @Override public void run() { if(flag){ synchronized (objA){ System.out.println("If_objA"); synchronized (objB){ System.out.println("If_objB"); } } }else { synchronized (objB){ System.out.println("Else_objB"); synchronized (objA){ System.out.println("Else_objA"); } } } } }
public class LockThreadDemo2Test { public static void main(String[] args) { LockThreadDemo2 l1 = new LockThreadDemo2(true); LockThreadDemo2 l2 = new LockThreadDemo2(false); l1.start(); l2.start(); } }
运行结果:
解决方法:
设计时考虑清楚锁的顺序,尽量减少嵌套的加锁交互数量
package day2.demo2; /* wait() 当前线程阻塞,释放同步监视器 notify() 唤醒被wait的一个或多个线程(先唤醒优先级最高的) notifyAll() 唤醒所有被wait的线程 */ public class PrintNum extends Thread{ public static void main(String[] args) { PrintNum p1 = new PrintNum(); PrintNum p2 = new PrintNum(); p1.start(); p2.start(); } private static int num=0; private static Object obj = new Object(); @Override public void run() { while (true){ /* 此处synchronized()内不能使用this,因为该类继承了Thread类 创建线程对象会有不同的任务 */ synchronized (obj){//为代码块加锁 obj.notify();//唤醒等待的线程,由于已经有线程中持有锁了,也是不能进入同步代码中 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(num<100){ System.out.println(Thread.currentThread().getName()+":"+(++num)); }else { break; } try { obj.wait();//线程等待,释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } } } }
运行结果: 可以看到两个线程之间交替打印数字
生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台 处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能 再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来 取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待
package day2.demo3; /* 柜台 */ public class Counter { //柜台商品数,所有线程共享 private static int num=1; //生产 //同步对象锁是this,(同一个Counter对象) public synchronized void add(){ while (true){ this.notify();//唤醒消费者线程 if(num<1){//当商品数不足一时,开始生产(++) num++; System.out.println("生产了"+num+"个"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }else { try { wait();//阻塞生产者线程 } catch (InterruptedException e) { e.printStackTrace(); } } } } //消费 public synchronized void sub(){ while (true){ this.notify();//唤醒生产者线程 if(num>0){//当商品数>0时,开始消费(--) System.out.println("消费了"+num+"个"); num--; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }else{ try { this.wait();//阻塞消费者线程 } catch (InterruptedException e) { e.printStackTrace(); } } } } }
//消费者线程 public class ConsumerThread extends Thread { Counter counter; //构造方法 public ConsumerThread(Counter counter) { this.counter = counter; } @Override public void run() { counter.sub(); } }
//生产者线程 public class ProducterThread extends Thread { Counter counter; //构造方法 public ProducterThread(Counter counter) { this.counter = counter; } @Override public void run() { counter.add(); } }
package day2.demo3; public class Test { public static void main(String[] args) { //新建Counter任务对象 Counter counter = new Counter(); //新建消费者线程 ConsumerThread consumerThread = new ConsumerThread(counter); //新建生产者线程 ProducterThread producterThread = new ProducterThread(counter); consumerThread.start();//启动消费者线程 producterThread.start();//启动生产者线程 } }
运行结果:
实现Callable接口
package day2.demo4; import java.util.concurrent.Callable; public class CallableDemo implements Callable<Byte> { /* 实现Callable与Runnable接口相比 call()相比于run(): 可以有返回值 方法可以抛出异常 支持泛型的返回值 借助FutureTask类,获取返回结果 */ @Override public Byte call() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(i); } return 10; } }
import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; /* 测试类 */ public class Test { public static void main(String[] args) { CallableDemo callableDemo = new CallableDemo(); //接收任务 FutureTask<Byte> f = new FutureTask(callableDemo); //创建线程 Thread t = new Thread(f); t.start(); try { Byte res = f.get();//获取call()的返回值 System.out.println("返回值:"+res);// } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
运行结果: