1、线程间的数据共享
(1)利用继承Thread类的方法实现模拟售票系统:
public class MyThread extends Thread{ //继承自Thread类 private int tickets=10; public void run() {//重写run方法 while(true){ if(tickets>0) System.out.println(getName()+"售票窗口,售卖了"+tickets--+"号票");
else
System.exit(0);
}
}
}
public class Test { public static void main(String[] args) { MyThread window1 = new MyThread(); MyThread window2 = new MyThread(); MyThread window3 = new MyThread(); window1.start(); window2.start(); window3.start(); } }
由运行结果可以看出,一张票在每一个窗口买了一次,在三个窗口卖了三次。因为在测试类中创建了三个线程对象,每个对象有各自的属性,他们处理的数据是相互独立的。因此,用这种方法处理售票系统是不成立的。
(2)实现Runnable接口,模拟售票系统:
package pers.zhb.runnable; public class MyThread implements Runnable { private int tickets = 10; public void run() { while (true) { if (tickets > 0) System.out.println(Thread.currentThread().getName() + "售票窗口,售卖了" + tickets-- + "号票");
else
System.exit(0);
}
}
}
package pers.zhb.runnable; public class RunnableDemo { public static void main(String[] args) throws InterruptedException { MyThread mt=new MyThread(); Thread thread1 = new Thread(mt); Thread thread2 = new Thread(mt); thread1.start(); thread2.start(); } }
此方法建立了三个线程,虽然他们是来自同一个对象的,即每一个线程调用的是同一个run方法,有相同的数据,但是依旧存在线程安全问题。
2、线程安全
(1)概念:
单线程相当于一个人做一件事,而多线程程序中,当需要多个线程共享同一个数据时,一个线程对共享的数据进行修改时,在他没有完成相关操作之前不允许其他线程进行相关操作,否则会出现线程安全问题。当对存在安全隐患的数据加锁后,会导致程序的性能降低,所以,如果是多个线程操作同一个数据,一定要选择线程安全的方法,如果不是,可以选择线程不安全的方式来处理问题。
(2)线程安全问题:
package pers.zhb.runnable;
public class MyThread implements Runnable {
private int tickets = 10;
public void run() {
while (true) {
if (tickets > 0)
System.out.println(Thread.currentThread().getName()
+ "售票窗口,售卖了" + tickets-- + "号票");
else
System.exit(0);
}
}
}
package pers.zhb.runnable;
public class RunnableDemo {
public static void main(String[] args) {
MyThread mt=new MyThread();
Thread thread1 = new Thread(mt);
Thread thread2 = new Thread(mt);
Thread thread3 = new Thread(mt);
Thread thread4 = new Thread(mt);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
可以看出,10号票在被1号窗口卖出后,又被2号窗口卖了一次,显然这是不符合常理的。产生的原因是这样的:2号线程开始执行,tickets为10,进行了 if (tickets > 0);语句的执行,可以继续往下执行了,但是他没有抢夺到CPU资源,接着是1号线程读取数据10,进行判断可以执行,1号线程执行完后,2号线程依旧没有抢夺到CPU资源,只能继续等待。接着是3号线程、0号线程进行相关操作。当2号线程抢夺到CPU资源时,他的数据还是10,就造成了有重复票的情况。
(3)添加同步语句:
package pers.zhb.runnable;
public class MyThread implements Runnable {
private int tickets = 10;
Object lock = new Object();
public void run() {
synchronized (lock) {
while (true) {
if (tickets > 0)
System.out.println(Thread.currentThread().getName()
+ "售票窗口,售卖了" + tickets-- + "号票");
else
System.exit(0);
}
}
}
}
package pers.zhb.runnable;
public class RunnableDemo {
public static void main(String[] args) {
MyThread mt=new MyThread();
Thread thread1 = new Thread(mt);
Thread thread2 = new Thread(mt);
Thread thread3 = new Thread(mt);
Thread thread4 = new Thread(mt);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
(4)创建同步方法:
package pers.zhb.runnable;
public class MyThread implements Runnable {
private int tickets = 10;
Object lock = new Object();
public void run() {
while (true)
lock();
}
public synchronized void lock() {
while (true) {
if (tickets > 0)
System.out.println(Thread.currentThread().getName()
+ "售票窗口,售卖了" + tickets-- + "号票");
else
System.exit(0);
}
}
}
package pers.zhb.runnable;
public class RunnableDemo {
public static void main(String[] args) {
MyThread mt=new MyThread();
Thread thread1 = new Thread(mt);
Thread thread2 = new Thread(mt);
Thread thread3 = new Thread(mt);
Thread thread4 = new Thread(mt);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
(5) ReentrantLock (重入锁):
可以对资源重复加锁:
package pers.zhb.runnable;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread implements Runnable {
private int tickets = 10;
ReentrantLock lock = new ReentrantLock();
public void run() {
while (true) {
lock.lock();
if (tickets > 0)
System.out.println(Thread.currentThread().getName()
+ "售票窗口,售卖了" + tickets-- + "号票");
else
System.exit(0);
}
}
}
package pers.zhb.runnable;
public class RunnableDemo {
public static void main(String[] args) {
MyThread mt=new MyThread();
Thread thread1 = new Thread(mt);
Thread thread2 = new Thread(mt);
Thread thread3 = new Thread(mt);
Thread thread4 = new Thread(mt);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
3、线程之间的通信
(1)常用方法
wait()
将正在执行的线程停止,并存放到线程池,直到被唤醒或等待时间到
属于Object类
wait用于线程间通信,这个方法会使当前拥有该对象锁的进程等待,当调用wait()方法后,线程会释放掉他所占用的锁
sleep()
是使线程停止一段时间的方法。在sleep 时间间隔期满后,线程不一定立即恢复执行。这是因为在那个时刻,其它线程可能正在运行,除非"醒来"的线程具有更高的优先级 或正在运行
的线程因为其它原因而阻塞
sleep()方法是Thread类的静态方法
sleep是线程用来控制自身流程的,不涉及线程间的通信,调用sleep()方法并不会释放锁。
notify()
唤醒在线程池中等待的线程
notifyAll()
唤醒全部
(2)举例:
package pers.Thread.conn;
public class Tickets {
private int size;
private int num = 0;
public Tickets(int size){
this.size=size;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
boolean flag = true;
public synchronized void put() {
if (flag) //还有剩余,等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;//等待结束后开始执行,增加票的操作
System.out.println("存入第" + num + "号票");
flag = true;//加票之后,有票,进入等待状态
notify();//唤醒等待的线程
}
public synchronized void sell() {
if (!flag) //没有剩余,等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}if(num>=1)
System.out.println("卖出第" + num + "号票");
flag = false;//减票之后,没有票,进入等待状态
notify();//唤醒另外一个等待的线程
if(num==size)num=size+1;
}
}
package pers.Thread.conn;
public class Add extends Thread{
Tickets t=null;
public Add(Tickets t){//为了保证卖出和增加的为统一数据对象
this.t=t;
}
public void run(){
while(t.getNum()<t.getSize())
t.put();
}
}
package pers.Thread.conn;
public class Sell extends Thread{
Tickets t=null;
public Sell(Tickets t){//为了保证卖出和增加的为统一数据对象
this.t=t;
}
public void run(){
while(t.getNum()<=t.getSize())
t.sell();
}
}
package pers.Thread.conn;
public class Test {
public static void main(String[] args) {
Tickets t=new Tickets(15);
new Add(t).start();//卖出和增加调用的为同一个对象
new Sell(t).start();
}
}