一、
1.说起线程,首先,java的线程有三个特性:原子性,可见性和有序性。
原子性:即,一个操作或多个操作,要么全部执行并且执行不会被打断,要么都不执行。例如,从账户A转账到账户B,需要两个操作,从账户A扣钱,账户B加钱,这两个操作都必须具备原子性才能不出现问题。例如i+1=i,需要读取i的值,+1,再写入i,其不具备原子性,一出现问题,所以就需要synchronzed和lock来解决这些问题。
可见性:当多个线程访问同一个变量时,线程A对这个变量进行修改,其他线程可以立即看到修改的值。
有序性:程序执行的顺序按照代码的先后顺序执行。例如账号A取钱和账户B存钱,必须是先取再存或者先存再取。
2.synchronized
使用synchronzed修饰的方法或者代码块可以看成是一个原子操作。 使用synchronized在某些情况下会造成死锁,例如一个共享变量被两个方法使用,一个有sync修饰,一个没有用于异步回调,就会造成死锁,在sync修饰的方法没有执行完毕释放共享变量时,用于异步回调的方法就会等待。
每个锁对象(JLS中叫monitor)都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个线程被唤醒(notify)后,才会进入到就绪队列,等待CPU的调度,反之,当一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒,这个涉及到线程间的通信,下一篇博文会说明。看我们的例子,当第一个线程执行输出方法时,获得同步锁,执行输出方法,恰好此时第二个线程也要执行输出方法,但发现同步锁没有被释放,第二个线程就会进入就绪队列,等待锁被释放。
一个线程执行互斥(同步)代码过程如下:
1. 获得同步锁;
2. 清空工作内存;
3. 从主内存拷贝对象副本到工作内存;
4. 执行代码(计算或者输出等);
5. 刷新主内存数据;
6. 释放同步锁。
所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
2.Thread和Runnable的区别
java有两种方式来实现多线程,一个是继承Thread类,重写Thread的run()方法,将线程运行的逻辑放入其中;一个是实现Runnable接口,实例化Thread类。
以铁路售票为例,如图:
第一种就是继承Thread类,第二张是实现Runnable接口。其区别显而易见,一个是多个线程分别完成自己的任务,一个是多个线程完成共同的任务。
我们一般在应用中多使用Runnable接口,其好处如下:
1.适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
2.可以避免由于Java的单继承特性带来的局限
3.有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
3.Callable和Runnable的区别
Runnable和Callable的区别是,
(1)Callable规定的方法是call(),Runnable规定的方法是run()。
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
1、通过实现Runnable接口来创建Thread线程:
步骤1:创建实现Runnable接口的类:
class SomeRunnable implements Runnable
{
public void run()
{
//do something here
}
}
步骤2:创建一个类对象:
Runnable oneRunnable = new SomeRunnable();
步骤3:由Runnable创建一个Thread对象:
Thread oneThread = new Thread(oneRunnable);
步骤4:启动线程:
oneThread.start();
至此,一个线程就创建完成了。
注释:线程的执行流程很简单,当执行代码oneThread.start();时,就会执行oneRunnable对象中的void run();方法,
该方法执行完成后,线程就消亡了。
2、与方法1类似,通过实现Callable接口来创建Thread线程:其中,Callable接口(也只有一个方法)定义如下:
public interface Callable<V>
{
V call() throws Exception;
}
步骤1:创建实现Callable接口的类SomeCallable<Integer>(略);
步骤2:创建一个类对象:
Callable<Integer> oneCallable = new SomeCallable<Integer>();
步骤3:由Callable<Integer>创建一个FutureTask<Integer>对象:
FutureTask<Integer> oneTask = new FutureTask<Integer>(oneCallable);
注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。
步骤4:由FutureTask<Integer>创建一个Thread对象:
Thread oneThread = new Thread(oneTask);
步骤5:启动线程:
oneThread.start();
至此,一个线程就创建完成了。
3、通过继承Thread类来创建一个线程:
步骤1:定义一个继承Thread类的子类:
class SomeThead extends Thraad
{
public void run()
{
//do something here
}
}
步骤2:构造子类的一个对象:
SomeThread oneThread = new SomeThread();
步骤3:启动线程:
oneThread.start();
至此,一个线程就创建完成了。