今天来进行JavaSE多线程的总结:
先从几个概念说起:
程序:是一组指令的集合,一段静态的代码。
进程:每一个运行的程序,都会对应一个进程。
线程:一个进程中包含一个或多个线程,如果包含多个线程,那么这个程 序就是支持多线程的。
多线程的优点:
①提高了计算机对CPU的利用率。
②提高了程序的响应速度
线程的创建,运行
线程的创建:
①继承Thread类:必须重写run()方法
②实现Runnable接口:必须实现run()方法
通常将run()的主体称为线程体
线程的启动:
通过start()方法来启动一个线程,不是通过调用run()方法来启动,同时start()方法也不是运行一个线程。
来看第一种方式的一个经典例子,多窗口售票问题。
这里我们假设有三个窗口,100张票同时出售。
窗口类:
public class Window extends Thread{
static int tickets = 100;
public Window(String name){
super(name);//Thread父类中有这个构造器
}
public void run(){
while(true){
if(tickets > 0){
System.out.println(Thread.currentThread().getName() + "售出一张票,剩余" + tickets-- + "张票");
}else{
break;
}
}
}
}
测试:
public static void main(String[] args){
Window w1 = new Window("窗口1");
Window w2 = new Window("窗口2");
Window w3 = new Window("窗口3");
w1.start();
w2.start();
w3.start();
}
我们看到了三个窗口售出了100张票,需要注意的是,tickets为什么要声明成静态的?其余的问题我们暂且不说。
第二种方式:
public class Window implements Runnable{
int ticket = 100;
public void run(){
//具体执行和上面一样
}
}
来看测试:
public static void main(String[] args){
Window w = new Window();
Thread w1 = new Thread(w);
Thread w2 = new Thread(w);
Thread w3 = new Thread(w);
w1.start();
w2.start();
w3.start();
}
看到的运行结果和上面没什么两样,但是我这里要说明一点:这里的ticket为什么不是静态的?
我来解释一下,通过继承的方式,创建了三个Window对象,而通过实现的方式,创建了一个Window对象。对于继承方式,三个对象要共享一个数据,只能将他们声明为类变量。而实现的方式,值创建了一个对象,所以这个对象不需要声明成静态的。
通过上面的对比,我们发现通过实现的方式更有优点,具体表现在:
①多个线程可共享同一个接口子类的对象,共享对象里的属性
②避免了单继承的局限性
③不需要将共享数据声明为静态的,就可以实现数据的共享,减少了内存的开销。
接下来来说Thread类中的常用方法:
上面已经说过两个:
run() 方法体为线程体
start():启动一个线程
currentThread():当前线程, static
getName():得到线程名
setName():设置线程名
yield():线程让步,调用此方法的线程放弃当前CPU的执行权,该线程可继续竞争CPU执行权
join():调用join的线程,优先获取CPU资源,直到该线程执行完毕,其他线程才可以竞争到CPU的执行权
sleep():调用该方法的线程放弃当前CPU的执行权,转换成一种睡眠状态,等睡眠时间结束,重新竞争CPU资源
isAlive():判断一个线程是否存活着。
线程通信:
wait(); 使线程进入等待状态
notify(); 唤醒进入等待状态
notifyAll(); 唤醒所有进入等待转态的线程
线程优先级:
getPriority():获取线程的优先级
setPriority():设置线程的优先级
线程的调度:
同优先级的线程组成队列,先来先服务,使用时间片策略
对于高优先级的线程,使用优先调度的抢占式策略
守护线程:通过在start()前调用,thread.setDaemon(true),可以将一个线程设置为守护线程。当JVM中只剩守护线程时,JVM将自动退出。
垃圾回收就是守护线程
线程的生命周期:
注意理解这张图,后面遇到什么问题,可以返回来,看看这张图。
线程同步:
来解决上面出现的重票和错票问题。
为什么会出现这样的错误呢?
因为:多个线程在操作同一对象的共享数据时,一个线程只执行了其中的一部分语句,还没有执行完,而另一个线程就加进来了,导致数据共享错误。
解决办法:多个线程在操作同一对象的共享数据时,让一个线程执行完,再执行下一个线程
线程同步:
①同步代码块
将需要同步的代码用synchronized包起来
synchronized(同步监视器/锁){
//操作共享数据的代码
}
②同步方法
将方法的声明处加上synchronized关键字
public synchronized void show(){
//需要同步的代码
}
这里涉及到一个锁的同步监视器,即锁,我们来看第一种,这里的这个锁可以是任意的对象来充当,但是必须保证这把锁是唯一的。对于实现Runnable接口的类来说,可以使用this来充当,因为就创建了一个对象;对于继承Thread类来说,不可使用this来充当,因为创建了三个对象,每个对象握各自的锁,前面所说的问题都会出现,但是我们也不需要创建静态的类对象来充当这把锁,我们可以使用 当前类.class
来充当这把锁,不管是实现还是继承都可以使用,可以是说它是一个万能的锁。
再来看第二种:没有发现锁?其实它默认的是当前对象充当的锁
对于静态的同步方法来说,它的锁是当前的类
对于非静态的同步方法来说,它的锁是当前对象
还有一种方式也能实现,Lock接口
Lock lock = new ReentrantLock();
lock.lock();
//需要同步的代码
lock.unlock();//需要手动的释放锁
使用线程同步当然就使效率降低了。
单例设计懒汉模式:
饿汉模式:
public class Singleton{
private Singleton(){
}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
懒汉模式(使用线程同步):
public class Singleton{
privata Singleton(){
}
private static Singleton instance = null;
public Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
在线程同步的过程中也会发生死锁的问题:
死锁:不同的线程分别占着对方的资源不放弃,都等待对方放弃自己需要的资源,就形成了死锁。
public class DeadLock{
static StringBuffer sb1 = new StringBuffer();
static StringBuffer sb2 = new StringBuffer();
public static void main(String[] args){
new Thread(){
synchronized(sb1){
Thread.currentThread().sleep(10);
sb1.append("A");
synchronized(sb2){
sb2.append("B");
}
}
}.start();
new Thread(){
synchronized(sb2){
Thread.currentThread().sleep(10);
sb1.append("C");
synchronized(sb1){
sb2.append("D");
}
}
}.start();
}
}
这样就发生了死锁。使用Thread.currentThread().sleep(10);只不过是将问题放大了。
在使用的过程中,应该尽量避免死锁,减少同步资源的定义。
线程通信:
wait()
notify()
notifyAll()
使两个线程交替打印1-100之间的数
public class Print implements Runnable{
int num = 1;
public void run() {
while (true) {
synchronized (this) {
notify();
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + ":"
+ num);
num++;
} else {
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Print p = new Print();
new Thead(p).start();
new Thead(p).start();
明天补充消费者生产者线程。
原文地址:http://blog.csdn.net/sloverpeng/article/details/44186845