java核心技术-多线程基础

Posted Talk is cheap.Show me your cod

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java核心技术-多线程基础相关的知识,希望对你有一定的参考价值。

进程、线程

​ 进程(Process) 是程序的运行实例。例如,一个运行的 Eclipse 就是一个进程。进程是程序向操作系统申请资源(如内存空间和文件句柄)的基本单位。线程(Thread)是进程中可独立执行的最小单位。一个进程可以包含多个线程。进程和线程的关系,好比一个营业中的饭店与其正在工作的员工之间的关系。

 

线程的使用

在 Java 中实现多线程通常有三种方式

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口,可以有返回值

下面我们来看看这几个去实现多线程的方式,用心感受下它们的不同

 

Thread

Thread类是Java多线程中的重要组成部分,下面我们介绍Thread类的用法

很多人说Thread类和Runnable接口的不同,Runnable接口可以共享数据,Thread类不可以,其实不然,根本原因还是使用方式造成的差异,下面我会一种啰嗦的方式去调用Thread,让其像Runnable一样

class MyThread extends Thread {

     private int ticket = 100;

    @Override
    public void run() {
        synchronized (MyThread.class) {
            while(ticket > 0){
                System.out.println("余票:" + (--ticket));
            }
        }

    }
}
public class TestThread {
    public static void main(String[] args) {
        //Thread和Runnable一样使用就不会出现 double票 所以根本问题还是new了两个thread
        /**  
         *    Thread a1 = new MyThread();
         *    Thread a2 = new MyThread();
         *     a1.start();
         *     a2.start();
         */
        Thread t1 = new MyThread();
        Thread a1 = new Thread(t1);
        Thread a2 = new Thread(t1);
        a1.start();
        a2.start();
    }

}

 

Runnable

通过实现Runnable接口的方式去使用多线程,要优于继承Thread的类,一方面可以解耦,一方面可以避免单继承的局限性

class MyRunnable implements Runnable {

    int ticket = 100;

    @Override
    public void run(){
        synchronized (MyRunnable.class){
            while(ticket > 0){
                System.out.println("余票:" + (--ticket));
            }
        }
    }

}
public class TestRunnable {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
    }

}

 

Callable

为了缓解多线程实现过程中没有返回值的问题,JDK1.5提供了 Callable接口,当其和Future一起使用时便可以获取返回值。但要注意,Future#get 是一个阻塞方法

class MyCallable implements Callable<Integer> {

    private int ticket = 100;

    @Override
    public Integer call() throws Exception {
        synchronized (MyCallable.class) {
            while (ticket > 0) {
                System.out.println("余票:" + (--ticket));
            }
        }
        return ticket;
    }

}
public class TestCallable {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*
        Callable<Integer> callable = new MyCallable();
        FutureTask<Integer> ft = new FutureTask<>(callable);
        Thread t1 = new Thread(ft);
        Thread t2 = new Thread(ft);
        t1.start();
        t2.start();
        System.out.println("--------------------");
        //你会发现这玩意会阻塞 直到拿到结果
        System.out.println("ft:" + ft.get());
        */
        Callable<Integer> callable = new MyCallable();
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(callable);
        executor.shutdown();
        System.out.println(future.get());
    }

}

 

 

线程的控制

Java的调度方法

同优先级线程组成先进先出队列(先到先服务),使用时间片策略

对高优先级,使用优先调度的抢占式策略

 

Thread类的相关方法

  • sleep(long millis) : 是 Thread 类中的静态方法,使当前线程进入睡眠状态
  • join() / join(long millis) : 是一个实例方法,使当前线程进入阻塞状态
  • interrupt() : 中断阻塞状态的线程
  • isAlive() : 判断当前线程是否处于存活状态
  • yield() : 线程让步
public class TestThread3 {
	public static void main(String[] args) throws Exception {
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i = 1; i < 100;i++){
					if(i % 2 == 0){
					System.out.println(Thread.currentThread().getName() + "=" + i);
					}
				}
					
			}
		},"线程1");
		t1.start();
		//线程1在 sleep之前就执行完了
		t1.sleep(10000);
		//join方法 迫使t2 必须等线程1 执行完 才能执行 然而 t1输出完自己的 睡着了 t2被迫等了10秒
		t1.join();

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i = 1; i < 100;i++){
					if(i % 2 != 0){
						System.out.println(Thread.currentThread().getName() + "=" + i);
					}
				}
			}
		},"线程2");
		t2.start();
		
	}
}

 

 

线程的同步

线程同步:模拟售票程序,实现三个窗口同时售票 100 张 (1.1案例)

问题:当三个窗口同时访问共享数据时,产生了无序、重复、超额售票等多线程安全问题

解决:将需要访问的共享数据“包起来”,视为一个整体,确保一次只能有一个线程执行流访问该“共享数据”

Java给上述问题提供了几种相应的解决方法

 

同步代码块

synchronized(同步监视器) {

​ //需要访问的共享数据

}

同步监视器 : 俗称“锁”,可以使用任意对象的引用充当,注意确保多个线程持有同一把锁(同一个对象)

 

public class SafeTicket implements Runnable{
	
	private int ticket = 100;
	
	@Override
	public void run() {
		while(true){
			//使用同步代码块
			synchronized (this) {
                if(ticket > 0){
                        System.out.println(Thread.currentThread().getName() + " 完成售票,余票:" + --ticket);
                }
			}
			
			
		}
	}

}

 

同步方法

同步方法 : 在方法声明处加 synchronized. 注意:非静态同步方法隐式的锁 ---- this

例如:

public synchronized void show(){}

 

public class SafeTicket implements Runnable{
	
	private int ticket = 100;
	
	@Override
	public void run() {
		while(true){
			//使用同步代码块
			sale();
			
		}
	}
	
	public synchronized void sale(){
		if(ticket > 0){
			System.out.println(Thread.currentThread().getName() + " 完成售票,余票:" + 
--ticket);
		}
	}
	
}

 

同步锁

同步锁 : Lock 接口

 

public class SafeTicket implements Runnable{
	
	private int ticket = 100;
	
	private Lock l = new ReentrantLock();
	
	@Override
	public void run() {
		while(true){
			l.lock();
			try {
				if(ticket > 0){
					System.out.println(Thread.currentThread().getName() + " 完成售票,余票:" + 
--ticket);
				}
			} finally {
				l.unlock();//释放锁
			}
			
		}
	}
	
}

 

死锁

死锁 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于 死锁 状态或系统产生了 死锁 ,这些永远在互相等待的进程称为 死锁 进程

 

public class TestDeadLock {
	public static void main(String[] args) {
		final StringBuffer s1 = new StringBuffer();
		final StringBuffer s2 = new StringBuffer();
		
		new Thread() {
			public void run() {
				synchronized (s1) {
					s2.append("A");
					
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					
					synchronized (s2) {
						s2.append("B");
						System.out.print(s1);
						System.out.print(s2);
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				synchronized (s2) {
					s2.append("C");
					synchronized (s1) {
						s1.append("D");
						System.out.print(s2);
						System.out.print(s1);
					}
				}
			}
		}.start();
	}
}

 

线程的通信

在 java.lang.Object 类中:

wait() : 使当前“同步监视器”上的线程进入等待状态。同时释放锁

notify() / notifyAll() : 唤醒当前“同步监视器”上的(一个/所有)等待状态的线程

注意:上述方法必须使用在同步中

场景1:使用两个线程打印 1-100 线程1和线程2交替打印

public class MyThread implements Runnable{
	
	int i = 0;
	
	@Override
	public void run() {
		
		while(true){
			synchronized (this) {
				this.notify();
				
				if(i <= 100){
					System.out.println(Thread.currentThread().getName() + "=" + i++);
				}
				
				try {
					this.wait();
				} catch (InterruptedException e) {
				}
			}
			
		}
		
	}
	
}
public class TestThread4 {
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		
		Thread t1 = new Thread(myThread,"线程1");
		Thread t2 = new Thread(myThread,"线程2");
		
		t1.start();
		t2.start();
	}
}

 

经典例题:生产者/消费者问题

  • 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
  • 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,
  • 如果店中有空位放产品了再通知生产者继续生产;
  • 如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
public class TestProduct {
	
	public static void main(String[] args) {
		Clerk clerk = new Clerk();
		
		Productor pro = new Productor(clerk);
		Customer cus = new Customer(clerk);
		
		new Thread(pro).start();
		new Thread(cus).start();
	}

}

// 店员
class Clerk {

	private int product;

	// 进货
	public synchronized void getProduct() {
		if (product >= 20) {
			System.out.println("产品已满!");
			
			try {
				wait();
			} catch (InterruptedException e) {
			}
			
		} else {
			System.out.println("生产者生产了第" + ++product + " 个产品");
			
			notifyAll();
		}
	}

	// 卖货
	public synchronized void saleProduct() {
		if (product <= 0) {
			System.out.println("缺货!");
			
			try {
				wait();
			} catch (InterruptedException e) {
			}
			
		} else {
			System.out.println("消费者消费了第" + --product + " 个产品");
			
			notifyAll();
		}
	}

}

// 生产者
class Productor implements Runnable {

	private Clerk clerk;

	public Productor() {
	}

	public Productor(Clerk clerk) {
		this.clerk = clerk;
	}

	public Clerk getClerk() {
		return clerk;
	}

	public void setClerk(Clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		while (true) {
			clerk.getProduct();
		}
	}

}

// 消费者
class Customer implements Runnable {

	private Clerk clerk;

	public Customer() {
	}

	public Customer(Clerk clerk) {
		this.clerk = clerk;
	}

	public Clerk getClerk() {
		return clerk;
	}

	public void setClerk(Clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public String toString() {
		return "Customer [clerk=" + clerk + "]";
	}

	@Override
	public void run() {
		while(true){
			clerk.saleProduct();
		}
	}

}

以上是关于java核心技术-多线程基础的主要内容,如果未能解决你的问题,请参考以下文章

java核心技术-多线程基础

号称史上最全Java多线程与并发面试题总结—基础篇

《java多线程编程核心技术》和《java并发编程的艺术》两本书的异同

java多线程编程核心技术怎么样

《Java多线程编程核心技术》推荐

Java多线程具体解释