Java基础(多线程2)
五、线程同步(5.1两种实现 5.2一种实现)
5.1、 synchronized对象及方法
有的时候需要几行代码作为整体一起执行,实现线程同步,需要用到synchronized代码块
相关源码及其注释:
package day25;public class Window implements Runnable {private Integer no = 100;//表示票的编号 1~100@Overridepublic void run() {while(true) {//this即Window对象,唯一的对象也被称之为锁,也可以写no,必须保证同一时刻只有一个线程拿到它/* synchronized (this) {if(no <= 0) {break;}//必须让下面两条语句一块运行才能同步System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}*/if(no <= 0){break;}sale();try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public synchronized void sale(){if(no <= 0) {return;}System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}
}
package day25;/*
* 1、三个窗口销售同一堆票
* 2、每个编号的票只能被销售一次
* 3、票的编号的范围是1~100
*
* 线程同步机制:
* 1、synonronized代码块
* synchronized(对象) {
*
* }
* 1) synchronized代码块()中的对象必须是唯一的对象
* 2)唯一的对象 —— 锁,同一时刻只能有一个线程占据这个唯一的对象
* 3)哪个线程占据了这个对象,哪个线程就可以执行synchronized代码块中的代码,
* 此时其他线程就不能执行synchronized代码块中的代码,陷入阻塞状态
* 4)占据这个唯一的对象的线程执行完synchronized代码块后就会释放这个锁
* 2、synonronized方法 即Window里的sale方法
*
*
* */
public class MyTest {public static void main(String[] args) {Window w = new Window();Thread window1 = new Thread(w);Thread window2 = new Thread(w);Thread window3 = new Thread(w);window1.setName("窗口1");window2.setName("窗口2");window3.setName("窗口3");window1.start();window2.start();window3.start();}
}
5.2、Lock锁
从JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
常用ReentrantLock来加锁实现锁
package day25;import java.util.concurrent.locks.ReentrantLock;public class Window implements Runnable {private Integer no = 100;//表示票的编号 1~100private ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while(true) {//this即Window对象,唯一的对象也被称之为锁,也可以写no,必须保证同一时刻只有一个线程拿到它/* synchronized (this) {if(no <= 0) {break;}//必须让下面两条语句一块运行才能同步System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}*//* if(no <= 0){break;}sale();*/try{//加锁lock.lock();if(no <= 0) {break;}System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}finally {lock.unlock();}try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public synchronized void sale(){if(no <= 0) {return;}System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}
}
5.3、线程安全的单例模式——懒汉式
package day25;//懒汉式
public class SingleObject {private static SingleObject obj;private SingleObject() {}//加了synchronized关键字就是唯一和排他的了,整体运行完,这样就只会创建一次对象,单例synchronized public static SingleObject getObject() {if (obj == null) {obj = new SingleObject();}return obj;}
}
package day25;public class MyTest2 {private static SingleObject obj1;private static SingleObject obj2;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {obj1 = SingleObject.getObject();}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {obj2 = SingleObject.getObject();}});t1.start();t2.start();Thread.sleep(1000);System.out.println(obj1 == obj2);}
}
5.4、死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
相关源码现象展示,运行时会卡住:
package day27;
//发生嵌套同步了
public class A extends Thread{@Overridepublic void run() {while(true){synchronized (Lock.m){synchronized (Lock.n){System.out.println("A............");}}}}
}
package day27;public class B extends Thread{@Overridepublic void run() {while(true){synchronized (Lock.n){synchronized (Lock.m){System.out.println("B............");}}}}
}
package day27;public class Lock {public static Object m = new Object();public static Object n = new Object();
}
package day27;public class MyTest {public static void main(String[] args) {A a = new A();B b = new B();a.start();b.start();}
}
解决方法:
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
六、线程通信
相关方法:
wait():令当前线程挂起并放弃CPU,同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待;
notifyAll():唤醒正在排队等待资源的所有线程结束等待。
以上三个方法只有在synchronized方法或代码块中才能使用。
6.1、打印数字:
package day28;//1~20
/*
* wait()
* notify()
* notifyAll()
*
* */
public class PrintNum implements Runnable {private Integer i = 1;@Overridepublic void run() {while (true) {synchronized (Object.class){if(i > 20){break;}Object.class.notifyAll();//唤醒其他线程,通知别人我要睡了,wait方法System.out.println(Thread.currentThread().getName() + ": " + i);i++;try {Object.class.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}try {Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
package day28;public class MyTest {public static void main(String[] args) {PrintNum printNum = new PrintNum();Thread th1 = new Thread(printNum);Thread th2 = new Thread(printNum);th1.setName("线程1");th2.setName("线程2");th1.start();th2.start();}
}
6.2、生产者消费者问题
此问题也称有限缓冲问题,是一个多线程同步问题的经典案例。
这里需要让生产者在缓冲区满时休眠(要么干脆放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,在唤醒消费者。
见源码及其注释:
package day28;import java.util.ArrayList;
import java.util.Objects;public class Store {public static ArrayList<Object> list= new ArrayList<>(100);public static final Integer MAX_NUM = 100;}
package day28;/*
* 生产者
* */
public class Producer extends Thread {@Overridepublic void run() {while (true) {synchronized (Store.list) {if (Store.list.size() < Store.MAX_NUM){Store.list.notifyAll();//生产Object item = new Object();Store.list.add(item);System.out.println(Thread.currentThread().getName() + " - 生产商品,仓库目前数量:" + Store.list.size());}else{//停止生产System.out.println(Thread.currentThread().getName() + " - 仓库已满,停止生产" );try {Store.list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}
}
package day28;/*
* 消费者
* */
public class Consumer extends Thread {@Overridepublic void run() {while (true) {synchronized (Store.list) {if (Store.list.size() > 0){Store.list.notifyAll();//消费Store.list.remove(0);System.out.println(Thread.currentThread().getName() + " - 消费商品,仓库目前数量:" + Store.list.size());}else{//停止消费System.out.println(Thread.currentThread().getName() + " - 仓库已空,停止消费" );try {Store.list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}
}
package day28;public class MyTest2 {public static void main(String[] args) {Producer p = new Producer();Consumer c = new Consumer();p.start();c.start();}
}
6.3、wait和sleep的区别
共同点:
wait(),wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态。
不同点:
方法归属不同:
sleep(long)是Thread的静态方法
而wait(),wait(long)都是Object的成员方法,每个对象都有
醒来时机不同:
执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
wait(long)和wait()还可以被notify唤醒,wait()如果不唤醒就一直等下去。
锁特性不同(重点)
wait方法的调用必须先获取wait对象的锁,而sleep则无此限制
wait方法执行后会释放对象锁,允许其他线程获得该对象锁(我放弃CPU,但你们还可以用)
而sleep方法如果在synchronized代码块中执行,并不会释放对象锁(我放弃CPU,但你们也不能用)
七、JDK5.0新增线程创建方式
7.1、实现Callable接口
与Runnable相比,Callable功能更强大些:
相比run()方法,可以有返回值;
方法可以抛出异常;
支持泛型的返回值;
需要借助FutureTask类,比如获取返回结果。
package day29;import java.util.concurrent.Callable;/*
* 实现Callable接口实现线程
* 1、实现Callable接口 - 泛型是返回值的类型
* 2、重写call方法
* */
public class SumThread implements Callable<Integer> {@Overridepublic Integer call() throws Exception {Integer sum = 0;for (int i = 0; i <=100 ; i++) {sum+=i;}return sum;}
}
package day29;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class MyTest {public static void main(String[] args) {SumThread sumThread = new SumThread();FutureTask<Integer> futureTask = new FutureTask<Integer>(sumThread);Thread thread = new Thread(futureTask);thread.start();try {Integer sum = futureTask.get();System.out.println(sum);} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}}
}
7.2、面试题:
Runnable和Callable的区别?
1)Runnable接口run方法没有返回值,Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果;
2)Callable接口支持返回执行结果,有返回值,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞;
3)Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。
7.2、线程池_创建线程,使用过程
池化技术:
线程池
连接池 - 网络连接
内存池
目的:减少对性能消耗比较大的操作(创建),提升系统的工作效率
如何解决:提前创建好,直接拿来用
相关源码:
package day30;public class Num1Thread implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + " " + i);}}}
}
package day30;public class Num2Thread implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + " " + i);}}}
}
package day30;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class MyTest1 {public static void main(String[] args) {//创建自定义线程类的对象Num1Thread num1 = new Num1Thread();Num2Thread num2 = new Num2Thread();//创建有固定数量线程的线程池ExecutorService pool = Executors.newFixedThreadPool(100);//通过线程池执行线程pool.execute(num1);pool.execute(num2);//关闭线程池pool.shutdown();}
}
7.3、Collections_创建线程安全的集合