多线程(三)

线程的死锁问题

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
解决方法
1.专门的算法、原则
2.尽量减少同步资源的定义
3.尽量避免嵌套同步

public class ThreadTest {

	public static void main(String[] args) {
		StringBuffer s1=new StringBuffer();
		StringBuffer s2=new StringBuffer();
		
		new Thread() {
			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(new Runnable() {
			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();
	}

}

在两段同步代码中加入Thread.sleep(100);导致第一个线程拿到锁s1时停留短暂的时间,在这短时间内第二个线程那到了锁s2,接下来第一个线程需要锁s2才能执行下去,而第二个线程则需要锁s1才能执行下去,导致两个线程僵持不下,形成死锁局面。
当不加入Thread.sleep(100)时,程序也有概率会出现死锁局面,只是加入后使得出现死锁的概率大大增加了。

Lock锁解决线程同步安全问题

在这里插入图片描述
Lock实际是一个接口。

import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable{
    private int ticket=100;

    //1.实例化ReentrantLock
    private ReentrantLock lock=new ReentrantLock(true);
    //true表示公平的意思,就是假如三条线程先后进来,则按顺序进入同步资源
    //先进先出原则,若括号内不写true,则默认是false

    public void run() {
        while(true) {
            try{
                //2.调用lock方法
                lock.lock();
                if(ticket>0) {

                    try {
                        Thread.sleep(100);
                    }catch(InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+":"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }finally {
                lock.unlock();//3.调用解锁方法
            }
        }
    }
}


public class ThreadTest {
    public static void main(String[] args) {
        Window w=new Window();
        Thread t1=new Thread(w);
        Thread t2=new Thread(w);
        t1.setName("线程一");
        t2.setName("线程二");
        t1.start();
        t2.start();
    }
}

面试题:synchronized与lock的异同?
同:二者都可以解决线程的安全问题
不同:synchronize机制在执行完相应的同步代码后,自动释放同步监视器。
Lock需要手动启动同步(lock()),同时结束同步也需要手动实现(unlock())。
在这里插入图片描述
其实用哪种方法解决线程安全都行都行。
面试题:如何解决线程安全问题?有几种方式?
三种:synchronize两种(同步代码块、同步方法),还有一种Lock。

例:

import java.util.concurrent.locks.ReentrantLock;

/*
解决银行存钱两个人同时操作一个账户线程安全问题
分析:
是否是多线程?
是否有共享数据?
是否有线程安全问题?
需要考虑如何解决线程安全问题?同步机制:三种方法
 */
class Account{
    private double balance;
    public Account(double balance){
        this.balance=balance;
    }

    private ReentrantLock lock=new ReentrantLock(true);

    //存钱
    public void deposit(double amt){
        try{
            lock.lock();
            if(amt>0){
                balance+=amt;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
            }
        }finally {
            lock.unlock();
        }

    }
}

class Customer extends Thread{
    private Account acct;
    public Customer(Account acct){
        this.acct=acct;
    }

    @Override
    public void run() {
        for(int i=0;i<3;i++){
            acct.deposit(1000);
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        Account acct=new Account(0);
        Customer c1=new Customer(acct);
        Customer c2=new Customer(acct);
        c1.setName("甲");
        c2.setName("乙");
        c1.start();
        c2.start();
    }
}

注意,多线程的lock锁也必须是同一个锁,具体情况具体分析需不需要加上static。

线程通信的例题

在这里插入图片描述
前提:wait()方法导致线程进入阻塞状态的同时会释放同步锁。
notify():唤醒一个线程,若有多个线程处于wait(阻塞)状态,则先唤醒优先级更高的线程,同级别则随机唤醒。
notifyAll():唤醒所有wait(阻塞)状态的线程。
sleep()导致先暂停一会儿,但不会释放同步锁。
wait,notify,notifyAll方法都必须使用在同步方法块或同步方法中,且这些方法的调用者都是当前的同步监视器。

class Number implements Runnable{
    private int number=1;

    @Override
    public void run() {
        while (true){
            synchronized (this){
                notify();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(number<=100){
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                }else {
                    break;
                }
                try {
                    wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        Number n = new Number();
        Thread h1=new Thread(n);
        Thread h2=new Thread(n);
        h1.setName("线程一");
        h2.setName("线程二");
        h1.start();
        h2.start();

    }
}

面试题:
sleep()和wait()方法的异同:
相同:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同:
1.两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2.调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块中调用。
3.关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放锁,但是wait会释放。

例:
实现柜台进货售货

//柜员
class Clerck{
    private int num=0;
    public synchronized void produce(){
        if(num<20){
            num++;
            System.out.println(Thread.currentThread().getName()+"开始生产第:"+num+"个产品");
            notify();
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void buy(){
        if(num>0){
            System.out.println(Thread.currentThread().getName()+"开始购买第:"+num+"个产品");
            num--;
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//生产者
class Producer extends Thread{
    private Clerck c;

    public Producer(Clerck c){
        this.c=c;
    }

    @Override
    public void run() {
        while (true){
            try{
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            c.produce();
        }
    }
}

//消费者
class Consummer extends Thread{
    private Clerck c;

    public Consummer(Clerck c){
        this.c=c;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            c.buy();
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        Clerck c=new Clerck();
        Consummer h=new Consummer(c);
        Producer p=new Producer(c);
        h.setName("消费线");
        p.setName("生产线");
        h.start();
        p.start();
    }
}

JDK 5.0新增线程创建方式

新增方式一:实现Callable接口
与使用Runnable相比,Callable功能更强大些。
1.相比run()方法,可以有返回值
2.方法可以抛出异常
3.支持泛型的返回值
4.需借助Future Task类,比如获取返回结果

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/*
打印出1-10的数,并且求和输出
 */
//1.创建一个实现Callable的实现类
class numThread implements Callable {
    private int num=0;
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        for(int i=0;i<10;i++){
            System.out.println(i);
            num+=i;
        }
        return num;
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        numThread numThread = new numThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTack构造器中
        //创建FutureTask的对象
        FutureTask futureTask=new FutureTask(numThread);
        //将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象并调用start
        new Thread(futureTask).start();

        try {
            //6.获取Callable中的call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Object num=futureTask.get();
            System.out.println("总和为:"+num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
1.call()可以有返回值。
2.call()可以抛出异常,被外面的操作捕获,获取异常信息。
3.Callable是支持泛型的。

新增方式二:使用线程池

在这里插入图片描述

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

class numThread implements Runnable {
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService service= Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1=(ThreadPoolExecutor) service;
        //设置线程池的属性
        //System.out.println(service.getClass());
        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();

        //2.执行指定的线程操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new numThread());//适用于Runnable
        //service.submit();适用于Callable
        service.shutdown();//3.关闭线程池
    }
}

面试题:创建多线程有四种方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值