java基础 -- 多线程总结(一)--基本概念

1.创建线程的三种方式对比

通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明并抛出异常而已。因此也可以将实现Runnable接口和Callable接口归为一种方式。这种方式与继承Thread方式之间的主要差别如下。

采用Runnable/Callable接口的方式创建多线程的优缺点:

  1. 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
  2. 在这种方式下,多线程可以共享一个target对象,所以非常适合多个线程来处理同一份资源的情况,从而可以将cpu、代码和数据分开形成清晰的模型,较好地体现了面向对象的思想。
  3. 劣势是,编程稍稍复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

 

Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,下面来看一个简单的例子:

public class CallableAndFuture {
    public static void main(String[] args) {
        Callable<Integer> callable = new Callable<Integer>() {
            public Integer call() throws Exception {
                return new Random().nextInt(100);
            }
        };
        FutureTask<Integer> future = new FutureTask<Integer>(callable);
        new Thread(future).start();
        try {
            Thread.sleep(5000);// 可能做一些事情
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

采用继承Thread类的方式创建多线程的优缺点:

  1.  劣势是,因为多线程已经继承了Thread类,所以不能再继承其他父类。
  2. 优势是,编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。


2.启动线程

启动线程使用start()方法,而不是run()方法!当对象调用了start()方法之后,该线程处于就绪状态,,Java虚拟机会为其创建方法栈和程序计数器,处于这个状态的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行取决于JVM里的线程调度器的调度。
永远不要调用线程的run()方法!调用strart()方法来启动线程,系统会把该run()方法当成线程执行体来处理;如果直接调用线程对象的run()方法,则run()方法就会立即执行,而且在run()方法返回之前其他线程无法并发执行--也就是说,如果调用当前线程的run(0方法,系统把线程对象当成一个普通对象,而run()方法也是一个普通方法,而不是线程执行体。

3.sleep()方法和yeild()方法的区别

  1. sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yeild()方法只会给优先级相同,或者优先级更高的线程执行机会。

  2. sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yeild()方法不会将线程转入阻塞状态,他只是强制将当前线程进入就绪状态。因此完全有可能某个线程调用yeild()方法暂停之后,立即在此获得处理器资源被执行。
  3. sleep()方法声明跑出了InterruptedException已成,所以使用sleep()方法时要么捕捉该异常,要么显示声明抛出该异常;而yeild()方法则没有声明抛出任何异常。
  4. sleep()方法比yeild()方法有更好的移植性,通常不建议使用yeild()方法来控制并发线程的执行。

4.synchronized

同步代码块

/*

JAVA对于多线程的安全问题提供了专业的解决方式

就是同步代码块

synchronized(对象)//这个对象可以为任意对象
{
	需要被同步的代码
}

对象如同锁,持有锁的线程可以在同步中执行
没持有锁的线程即使获取CPU的执行权,也进不去


同步的前提:
1,必须要有两个或者两个以上的线程
2,必须是多个线程使用同一个锁
	
必须保证同步中只能有一个线程在运行

好处:解决了多线程的安全问题

弊端:多个线程需要判断锁,较为消耗资源
*/

class Tick implements Runnable
{
	private int tick = 50;

	Object obj = new Object();//申请一个对象

	public void run()
	{

		while(true)
		{
			synchronized(obj)
			{
				if(tick > 0)
				{
					//try{Thread.sleep(40);}catch(Exception e){}
					System.out.println( Thread.currentThread().getName() + " sail --" + tick--);
				}	
			}
		}
	}
}

class TickDemo
{
	public static void main(String []args)
	{
		Tick t = new Tick();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);

		t1.start();
		t2.start();
		t3.start();
		t4.start();

	}
}

同步方法

public synchronized  static void method(){
  
  // ....code
 }
 
/........................................./


对于Synchronized修饰的实例方法(非static方法)而言无需指定同步监视器,同步方法的监视器就是this,也就是调用该方法的对象。


释放同步监视器的锁定

释放:

1.当前线程的同步方法,同步代码块正常运行结束.
2.当前线程在同步代码块,同步方法中遇到break,return什么的,
3发生异常,错误.
4.程序执行了同步监视器对象的wait()方法,线程暂停并释放同步监视器.

不释放:

1.在期内执行了Thread.sleep,thread.yield方法.不会放开.
2.用了suspend挂起,当然不应该用suspend和resume方法.



5.wait()、notify()和notifyAll()

      wait()、notify()和notifyAll()是Object类中的方法
   从这三个方法的文字描述可以知道以下几点信息:
  1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
  2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
  3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象monitor,
     则只能唤醒其中一个线程;
  4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;

有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。
  
上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);
  
notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。
  
同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
  
nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。
  
这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。







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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值