java多线程

多线程的实现

可以通过继承Tread类和实现Runnable接口来实现多线程,通过卖票实现多线程代码如下:

package Thread;
// 继承Thread类
class ConductorThread extends Thread{
	private int ticketNum = 5;
	@Override
	public void run() {
		for(int i=5;i>0;i--) {
			if(this.ticketNum>0) {
				this.ticketNum--;
				System.out.println(Thread.currentThread().getName() + "卖了1张票剩余" + this.ticketNum+ "张票");
			}
		}
	}
}
//实现Runnable接口
class ConductorRunnable implements Runnable{
	private int ticketNum = 5;
	@Override
	public void run() {
		for(int i=5;i>0;i--) {
			if(this.ticketNum>0) {
				this.ticketNum--;
				System.out.println(Thread.currentThread().getName()+"卖了1张票剩余"+this.ticketNum+"张票");
			}
		}
	}
}
public class RailwayStation {
	public static void main(String args[]) {
		// 创建实例对象
		ConductorThread conductorThread = new ConductorThread();
		ConductorRunnable conductorRunnable = new ConductorRunnable();
		
		//为实例对象创建两个售票员线程
		Thread conductor1 = new Thread(conductorThread,"售票员1") ;
		Thread conductor2 = new Thread(conductorThread,"售票员2") ;

		Thread conductor3 = new Thread(conductorRunnable,"售票员3");
		Thread conductor4 = new Thread(conductorRunnable,"售票员4");
		// 启动线程
		conductor1.start();
		conductor2.start();	
		conductor3.start();
		conductor4.start();
	}
}

运行结果:

售票员1卖了1张票剩余4张票
售票员1卖了1张票剩余2张票
售票员1卖了1张票剩余1张票
售票员1卖了1张票剩余0张票
售票员2卖了1张票剩余3张票
售票员4卖了1张票剩余3张票
售票员3卖了1张票剩余3张票
售票员4卖了1张票剩余2张票
售票员3卖了1张票剩余1张票
售票员4卖了1张票剩余0张票

如果创建了两个实例对象,则会卖两倍的票数,这是因为普通成员变量的ticketNum会被初始化两次。

Thread和Runnable的区别

阅读源码

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
public
class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

    private volatile String name;
    private int            priority;
    private Thread         threadQ;
    private long           eetop;

    /* Whether or not to single_step this thread. */
    private boolean     single_step;
    ......
}
  • Thread类实现了Runnable接口,当一个类A继承了类B,类B没有继承Thread,由于java的单继承性,类B要想实现多线程,则不能继承Thread类,只能实现Runnable接口。
  • Runnable可以看做一个一个具体的task,在task下有多个线程共同来执行这个task,不同线程之间的资源是可以共享的。Thread作为线程的载体,不同Thread对象之间不可以进行资源共享。

线程的状态

  • 新建状态

new出新对象,调用start方法,线程进入就绪状态,已经启动的线程不能再次调用start方法

  • 就绪状态

具备的运行条件,但是没有分到CPU,等待系统为其分配CPU

  • 运行状态

运行状态的线程有可能变为阻塞状态
注: 当发生如下情况是,线程会从运行状态变为阻塞状态:

 ①线程调用sleep方法主动放弃所占用的系统资源
 ②线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
 ③线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有
 ④线程在等待某个通知(notify)
 ⑤程序调用了线程的suspend方法将线程挂起。不过该方法容易导致死锁,所以程序应该尽量避免使用该方法。
  • 阻塞状态

线程执行了sleep方法或者等待I/O设备

  • 死亡状态

线程执行完毕或者强制终止

线程同步

在上面的卖票例子中,可能会出现不同的售票员卖同一张票的现象,导致最后剩余票数为负数,显然是不符合实际的,为了使卖票结果最后的剩余值为0,应该使用线程同步方法,来避免这种情况的发生。

class ConductorRunnable implements Runnable{
	private int ticketNum = 5;
	@Override
	public void run() {
		while(this.ticketNum>0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.ticketNum--;
			System.out.println(Thread.currentThread().getName() + "卖了1张票剩余" + this.ticketNum+ "张票");
		}
	}
}
public class RailwayStation {
	@Test
	public void test1() {
		// 局部变量创建一个实例对象  实现多线程
		ConductorRunnable conductorRunnable = new ConductorRunnable();

		Thread conductor3 = new Thread(conductorRunnable,"售票员3");
		Thread conductor4 = new Thread(conductorRunnable,"售票员4");
		// 启动线程
		conductor3.start();
		conductor4.start();
	}
        public static void main(String args[]) {
		RailwayStation rw = new RailwayStation();
		rw.test1();
	}

运行结果:

售票员3卖了1张票剩余4张票
售票员4卖了1张票剩余3张票
售票员3卖了1张票剩余2张票
售票员4卖了1张票剩余1张票
售票员3卖了1张票剩余0张票
售票员4卖了1张票剩余-1张票

线程同步的几种方法 :

  • 同步方法

在方法前加关键字synchronized,代码入下:

class ConductorRunnable implements Runnable{
	private int ticketNum = 5;
	@Override
	public synchronized void run() {
		while(this.ticketNum>0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.ticketNum--;
			System.out.println(Thread.currentThread().getName() + "卖了1张票剩余" + this.ticketNum+ "张票");
		}
	}
}

运行结果入下:

售票员3卖了1张票剩余4张票
售票员3卖了1张票剩余3张票
售票员3卖了1张票剩余2张票
售票员3卖了1张票剩余1张票
售票员3卖了1张票剩余0张票

在java中每一个对象都有一个内置锁,synchronized关键字修饰方法,内置锁会保护整个方法,线程要想执行该方法,必须先获取该方法的内置锁。当一个线程在执行该方法时,另外一个线程就无法获得该该方法的内置锁,会处于block状态,直到前一个方法执行结束,释放内置锁。

  • 同步代码块

除了使用synchronized修饰整个方法外,还可以用synchronized来修饰关键代码,来达到同步的目的。代码入下:

class ConductorRunnable implements Runnable{
	private int ticketNum = 5;
	@Override
	public void run() {
		synchronized(this) {
			while(this.ticketNum>0) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				this.ticketNum--;
				System.out.println(Thread.currentThread().getName() + "卖了1张票剩余" + this.ticketNum+ "张票");
			}
		}	
	}
}

被synchronized修饰的代码块会被加上内置锁,从而达到同步的目的。

  • 使用特殊域变量(volatile)实现线程同步
    可以将线程操作的变量前面加volaile修饰,给变量加入内置锁,代码如下:
class ConductorRunnable implements Runnable{
	private volatile int ticketNum = 5;
	@Override
	public void run() {
		while(this.ticketNum>0) {
			try {
				this.ticketNum--;
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "卖了1张票剩余" + this.ticketNum+ "张票");
		}	
	}
}

运行结果:

售票员3卖了1张票剩余3张票
售票员4卖了1张票剩余3张票
售票员4卖了1张票剩余1张票
售票员3卖了1张票剩余1张票
售票员4卖了1张票剩余0张票

1.volatile关键字为域变量的访问提供了一种免锁机制;
2.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新;
3.因此每次使用该域就要重新计算,而不是使用寄存器中的值;
4.多线程的内存模型为主存和线程栈,在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。
5.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。

  • 使用重入锁(Lock)实现线程同步

代码入下:

class ConductorRunnable implements Runnable{
	private int ticketNum = 5;
	private Lock lock = new ReentrantLock(); // 声明一个锁
	@Override
	public void run() {
		while(this.ticketNum>0) {
			lock.lock();
			try {
				this.ticketNum--;
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "卖了1张票剩余" + this.ticketNum+ "张票");
		}	
	}
}

执行结果如下:

售票员3卖了1张票剩余4张票
售票员3卖了1张票剩余3张票
售票员3卖了1张票剩余2张票
售票员3卖了1张票剩余1张票
售票员3卖了1张票剩余0张票

悲观锁和乐观锁

  • 悲观锁

对世界充满了绝望,觉得人和人都会修改数据,每次拿到数据后,都会上锁,避免其他线程更改数据,直到执行完毕释放资源

  • 乐观锁

乐观主义者,觉得任何人都不会修改数据,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量

线程通信

  • 借助于Object类的wait()、notify()和notifyAll()实现通信
  • 使用Condition控制线程通信
原文地址:https://www.cnblogs.com/xxyxt/p/11360030.html