Java多线程

Java多线程

先说进程和线程的区别:

  • 进程是一个资源分配的最小单位,就是一个执行中的程序,它包括一个或多个线程。每个进程由3部分组成:CPU,data,code。每个进程都是独立的,保有自己的CPU时间,代码和数据。进程之间的切换开销很大。
  • 线程是CPU调度的最小单元,一系列线程共享当前进行的进程资源,线程之间切换开销小 ,每个线程都有自己的运行栈和程序计数器。

并行和并发的区别:

  • 并发,在单个处理器上采用单核执行多个任务即为并发。在这种情况下,操作系统的任务调度程序会很快从一个任务切换到下个任务,因此看起来所有任务都是同步的。并发概念重点是说一个事件触发机制,即同时存在多个任务,但不是同时运行。例如秒杀下,许多用户下单的操作,可以同时存在下单操作,但运行还是看服务器的调度。
  • 并行,同一时间在不同计算机或处理器或处理器核心上同时运行多个任务,就是所谓的并行。这是一个真正的同时运行,并行概念重点是同时运行的多个任务。

Java中实现多线程

前面已经提到,如果是单个处理器的单个核心,那么多线程的实现仅仅是由于CPU调度很快,造成了同时运行的错觉,真正的多线程还是需要多个处理器核心。

1、实现多线程需要的类或接口

​ 1)、Thread类:描述了执行并发Java应用程序的所有线程。实现方式为继承Thread类,并重写run()方法

public class ThreadDemo {
	public static void main(String[] args) {
		MyThread thread1 = new MyThread("A线程");
		MyThread thread2 = new MyThread("B线程");
		thread1.start();
		thread2.start();
	}
}
class MyThread extends Thread{
	public MyThread(String name) {
		super(name);
	}
	@Override
	public void run() {
		for(int i = 0;i<5;i++) {
			System.out.println(Thread.currentThread().getName()+i);
		}
	}
}

运行结果跟计算机的性能有关,结果可能A、B线程交替打印语句,实现了多线程。

​ 2)、实现Runnable接口,重写run()方法

public class RunnableDemo {
	public static void main(String[] args) {
		TicketWindow tw = new TicketWindow();
		Thread t1  = new Thread(tw,"张三");
		Thread t2  = new Thread(tw,"李四");
		t1.start();
		t2.start();
	}
}
//售票窗口
class TicketWindow implements Runnable{
	//资源->100张票
	private int ticket = 10;

	@Override
	public void run() {
		while(ticket>0) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"正在买第"+ticket--+"票");
		}
		
	}
}

与继承Thread相比,实现Runnable接口优点:

  • 实现Runnable接口,还可以继承其他类。而第一种方式只能继承Thread类,有局限。
  • Runnable接口的实现类对象相当于拥有一份相同资源,开启的多线程可以共享这份资源。

以上两种方式实现run方法时,只能抛出运行时异常,因为在接口中的run方法没有抛出任何异常。注意子类重写父类方法时只能抛出比父类抛出异常限定更小的异常。而且这两种run方法都不可以有返回值。

​ 3)、实现Callable接口

public class CallableDemo {
	public static void main(String[] args) {
		MyCallable thread=new MyCallable();
		FutureTask<String> future=new FutureTask<String>(thread);
		new Thread(future).start();
		System.out.println("这是主线程begin!");
		try {
			System.out.println("得到的返回结果是:"+future.get());
			
		}catch(InterruptedException e) {
			e.printStackTrace();
		}catch(ExecutionException e) {
			e.printStackTrace();
		}
		
		System.out.println("这是主线程end!");
	}
}
class MyCallable implements Callable<String>{
	@Override
	public String call() throws Exception {
		try {
			Thread.sleep(500L);
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("这是线程C");
		return "thread C";
	}
}

Callable接口在guc包下,通过FutureTask线程结果处理类可以得到线程返回结果。Callable接口是一个函数式接口,重写call方法,call方法有返回值,且接口中的方法抛出了Exception异常,因此子类重写可以抛出任何异常。

2、线程的状态

​ Thread类中的States枚举中定义了线程的所有状态:

public enum State {
        /**
         * 新生线程,还未运行,仅仅由JVM分配了内存
         */
        NEW,

        /**
         * 可运行中线程的状态。处于可运行状态的某一线程正在 Java 虚拟机中运行,但它可能正在等待操作		     * 系统中的其他资源,比如处理器。调用start方法后处于这种状态,又分为READY就绪状态和RUNNING			 * 运行状态,两者可以相互转换。
         */
        RUNNABLE,

        /**
         * 正在等待监视器锁的阻塞线程,只能重新进入就绪状态才有可能运行。
         */
        BLOCKED,

        /**
         *等待状态,当处于运行的线程调用无时间参数限制的方法,如wait()、join(),就转为等待状态
         */
        WAITING,

        /**
         * 定时等待状态,调用有时间参数限制的方法。
         */
        TIMED_WAITING,

        /**
         * 终止状态,线程run()、call()方法正常执行完毕或抛出一个未捕获的异常,线程就进入终止状态
         */
        TERMINATED;
    }

3、线程类的部分方法

public class ThreadMain{
	public static void main(String[] args) throws ExecutionException {
		ThreadB threadb=new ThreadB();
		
		for(int i=0;i<5;i++) {
			new Thread(threadb,"线程名称:("+i+")").start();
		}
		Thread threadMain=Thread.currentThread();
		System.out.println("这是主线程;");
		System.out.println("返回当前线程的线程组中活动线程的数目:"+Thread.activeCount());
		System.out.println("主线程的名称:"+threadMain.getName());
		System.out.println("返回该线程的标识符:"+threadMain.getId());
		System.out.println("返回该线程的优先级:"+threadMain.getPriority());
		System.out.println("返回该线程的状态:"+threadMain.getState());
		System.out.println("返回该线程所属线程组:"+threadMain.getThreadGroup());
		System.out.println("测试该线程是否为守护线程:"+threadMain.isDaemon());
		try {
			Thread.sleep(10000L);
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class ThreadB implements Runnable{

	@Override
	public void run() {
		try {
			Thread.sleep(10000L);
			
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
		Thread curThread=Thread.currentThread();
		String curThreadName=curThread.getName();
		System.out.println("这是线程的名字:"+curThread.getName());
		System.out.println("返回当前线程"+curThreadName+"的线程组中活动线程的数目:"+Thread.activeCount());
		System.out.println("返回该线程"+curThreadName+"的标识符"+curThread.getId());
		System.out.println("返回该线程"+curThreadName+"的优先级"+curThread.getPriority());
		System.out.println("返回该线程"+curThreadName+"的状态"+curThread.getState());
		System.out.println("返回该线程"+curThreadName+"所属的线程组"+curThread.getThreadGroup());
		System.out.println("测试该线程"+curThreadName+"是否处于活动状态"+curThread.isAlive());		
		System.out.println("测试该线程"+curThreadName+"是否为守护线程"+curThread.isDaemon());
	}	
}

重点介绍sleep()、join()、yield()方法:

  • sleep()方法是Thread类的静态方法,作用是使当前线程休眠一段时间,传入long类型的参数,单位是毫秒。这种方式会使线程阻塞,阻塞结束线程会变成就绪状态,之后才可能被CPU调度。sleep()方法会持有线程的同步锁休眠,在这段时间内,仍可以控制同步。

  • join()方法是Thread类的成员方法,由线程对象调用,插队线程。调用该方法的对象代表的线程会插队,直到这个线程结束或插队时间结束,被阻塞的线程才能转为就绪状态继续进行。

  • yield()方法是Thread的静态方法,不会阻塞线程,使得当前线程从运行状态直接进入就绪状态。

sleep()和wait()的区别:

  • 原理不同。sleep()是Thread的静态方法,而wait()是Object类的方法。
  • 对锁的处理机制不一样。由于sleep()方法主要是让线程休息一段时间,不涉及线程间的通信,因此调用这个方法不会释放锁。而wait()方法会释放线程占用的锁。所以sleep()容易造成死锁,推荐使用wait()方法。
  • 使用区域不同。wait方法必须放在同步控制方法或者同步语句块中使用。而sleep则可在任何地方使用。
  • sleep()必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。在sleep过程中,有可能被其他对象调用它的interrupt(),产生InterrupedException异常。

多线程的同步

上面的售票例子中,多个线程共享一个票的资源,会造成同时买同一张票的问题,这样就需要一定方法控制。Java中提供了一种简单的方式,就是锁的机制,用synchronized关键字实现。

  • 同步方法,直接在需要同步的方法上加上关键字即可,多线程同时操作时,会排队执行这个方法,例如Hashtable实现线程安全就是在方法上加了这个关键字。这种方式虽然简单但效率低。

  • 同步块,在有可能出现同步问题的代码外加上一个代码块synchronized(锁){}。锁可以有三种形式:

    1)、类.class,每个类只存在一个Class对象,因此可以用来当作锁。

    public class SynchronizedDemo {
    	public static void main(String[] args) {
    		MyThread1 th = new MyThread1();
    		
    		Thread th1 = new Thread(th);
    		Thread th2 = new Thread(th);
    		th1.start();
    		th2.start();
    	}
    }
    class MyThread1 implements Runnable {
    
    	@Override
    	public void run() {
    		method();
    	}
    	static void method() {
    		System.out.println("静态方法开始");
    		synchronized(MyThread1.class) {
    			try {
    				System.out.println("静态方法锁定中...");
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		System.out.println("静态方法锁定结束");
    	}
    }
    

    2)、this。指当前调用的对象,成员方法中可以用。

    public class SynchronizedDemo {
    	public static void main(String[] args) {
    		MyThread1 th = new MyThread1();
    		
    		Thread th1 = new Thread(th);
    		Thread th2 = new Thread(th);
    		th1.start();
    		th2.start();
    	}
    }
    class MyThread1 implements Runnable {
    	@Override
    	public void run() {
    		method();
    	}
    	void method() {
    
    		System.out.println("成员方法开始");
    		synchronized(this) {
    			try {
    				System.out.println("成员方法锁定中...");
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		System.out.println("成员方法锁定结束");
    	}
    }
    

    3)、不变的资源,即成员属性。Java不允许基本数据类型作为锁,可以用自定类的引用当作锁,锁的特点是不可变,因为引用是地址,只要不改变引用地址,锁就可以生效。

    public class SynchronizedDemo {
    	public static void main(String[] args) {
    		MyThread1 th = new MyThread1();
    		
    		Thread th1 = new Thread(th);
    		Thread th2 = new Thread(th);
    		th1.start();
    		th2.start();
    	}
    }
    
    class MyThread1 implements Runnable {
    	String str = "aaa";
    	@Override
    	public void run() {
    		method();
    
    	}
    	void method() {
    		System.out.println("成员方法开始");
    		//用String类型引用做参数
            synchronized(str) {
    			try {
                    //这里相当于重新创建一个String对象,改变了引用
    				str  = "123";
    				System.out.println("成员方法锁定中...");
    				Thread.sleep(2000);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		System.out.println("成员方法锁定结束");
    	}
    }
    

    上面的代码,两个线程同时进入了锁定块,说明并没有锁定成功,因此我们在使用成员属性所谓锁时,要保证不变性。

    实现售票的同步安全,可以在票卖出前锁定这一段代码。

    public class Thread03 {
    	public static void main(String[] args) {
    		TicketsWindow tw = new TicketsWindow();
    		Thread thread1 = new Thread(tw,"张三");
    		Thread thread2 = new Thread(tw,"李四");
    		Thread thread3 = new Thread(tw,"王五");
    		thread1.start();
    		thread2.start();
    		thread3.start();
    	}
    }
    class TicketsWindow implements Runnable{
    	private int tickets=100;
    	@Override
    	public  void run() {
    		while(tickets>0) {
    			synchronized(this) {
    				if(tickets>0) {
    					System.out.println(Thread.currentThread().getName()+"购买第"+tickets--+"张票");	
    				}
    			}
    			//买完休息一会
    			try {
    				Thread.sleep(10);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			
    		}
    	}
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值