Java多线程学习二

发布于:2024-12-08 ⋅ 阅读:(128) ⋅ 点赞:(0)

线程的安全问题与线程的同步机制

引出:

多线程卖票,出现的问题:出现了重票和错票

 原因分析:

线程操作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(){

            @Override
            public 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(){

            @Override
            public 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();
    @Override
    public 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{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{

    @Override
    public 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());//ThreadPoolExecutor
        service1.setMaximumPoolSize(50); //设置线程池中线程数的上限


        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }

}

这里的线程池没有做过多的解释