线程的安全问题与线程的同步机制
引出:
多线程卖票,出现的问题:出现了重票和错票
原因分析:
线程操作ticket的过程中,尚未结束的情况下,其他线程也参与进来,对ticket进行操作。
如何解决多线程不安全的问题
必须保证一个线程a在操作ticket的过程中,其它线程必须等待,直到线程a操作ticket结束以后,其它线程才可以进来继续操作ticket。
Java是如何解决线程的安全问题的?使用线程的同步机制。
方式1:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
> 需要被同步的代码,即为操作共享数据的代码。
> 共享数据:即多个线程都需要操作的数据。比如:ticket
> 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其
它线程必须等待。
> 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。
> 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。
注意:在实现Runnable接口的方式中,同步监视器可以考虑使用:this。
在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class
方式2:同步方法
说明:
> 如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
重要:
> 非静态的同步方法,默认同步监视器是this静态的同步方法,默认同步监视器是当前类本身。
synchronized好处:解决了线程的安全问题。
弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。
方式三:同步锁
调用方法luck()和unlock()来对共享数据区域进行同步操作
死锁问题
原因:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
条件:
互斥条件
占用且等待
不可抢夺(或不可抢占)
循环等待
满足所有一才会导致死锁
代码举例:
class A {public synchronized void foo(B b) {System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 进入了A实例的foo方法");try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用B实例的last方法");b.last();}public synchronized void last() {System.out.println("进入了A类的last方法内部");}
}
class B {public synchronized void bar(A a) {System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 进入了B实例的bar方法");try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用A实例的last方法");a.last();}public synchronized void last() {System.out.println("进入了B类的last方法内部");}
}public class DeadLock implements Runnable {A a = new A();B b = new B();public void init() {Thread.currentThread().setName("主线程");// 调用a对象的foo方法a.foo(b);System.out.println("进入了主线程之后");}public void run() {Thread.currentThread().setName("副线程");// 调用b对象的bar方法b.bar(a);System.out.println("进入了副线程之后");}public static void main(String[] args) {DeadLock dl = new DeadLock();new Thread(dl).start();dl.init();}
}
测试:
public class DeadLockTest {public static void main(String[] args) {StringBuilder s1 = new StringBuilder();StringBuilder s2 = new StringBuilder();new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();new Thread(){@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}.start();}
}
线程通信
理解:
当我们`需要多个线程`来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据
通信方法
wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用
notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程的优先级相同,则随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
代码举例:
class PrintNumber implements Runnable{private int number = 1;Object obj = new Object();@Overridepublic void run() {while(true){// synchronized (this) {synchronized (obj) {obj.notify();if(number <= 100){try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":" + number);number++;try {obj.wait(); //线程一旦执行此方法,就进入等待状态,同时,会释放对同步监视器的调用,释放} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}}
}public class PrintNumberTest {public static void main(String[] args) {PrintNumber p = new PrintNumber();Thread t1 = new Thread(p,"线程1");//创建对象Thread t2 = new Thread(p,"线程2");t1.start();t2.start();}
}
新增的多线程创建的方式
实现Callable
call()可以有返回值,更灵活
call()可以使用throws的方式处理异常,更灵活(可以直接抛出异常)
Callable使用了泛型参数,可以指明具体的call()的返回值类型,更灵活
实现Callable的缺点:
如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的
使用线程池来创建多线程
优点:
提高了程序执行的效率。(因为线程已经提前创建好了)(优点像饿汉式)
提高了资源的复用率。(因为执行完的线程并未销毁,而是可以继续执行其他的任务)
可以设置相关的参数,对线程池中的线程的使用进行管理
代码:
class NumberThread implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}class NumberThread1 implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}public class ThreadPool {public static void main(String[] args) {//1. 提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// //设置线程池的属性
// System.out.println(service.getClass());//ThreadPoolExecutorservice1.setMaximumPoolSize(50); //设置线程池中线程数的上限//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());//适合适用于Runnableservice.execute(new NumberThread1());//适合适用于Runnable// service.submit(Callable callable);//适合使用于Callable//3.关闭连接池service.shutdown();}}
这里的线程池没有做过多的解释