实现多线程编程一:继承Thread类,复写父类的run方法,父类引用调用的是子类方法
1.继承Thread类
2.重写run方法(线程要执行的任务)
3.创建并启动(通过start方法)
下面以售票例子说明:
class SaleTicket extends Thread{//继承Thread方法
private int ticket=100;
public void run(){//重写run方法
while(true){
if(ticket>0)
System.out.println(Thread.currentThread().getName()+"....."+ticket--);
}
}
}
public class TicketDemo {
public static void main(String[] args) {
//创建并启动四个线程
SaleTicket st1=new SaleTicket();
SaleTicket st2=new SaleTicket();
SaleTicket st3=new SaleTicket();
SaleTicket st4=new SaleTicket();
st1.start();//有可能执行权先给st1,先不继续往下
st2.start();
st3.start();
st4.start();
}
}
用这种方法大家可能会发现有些问题,就是原本我们是希望100张票开四个窗口买,可现在变成四个窗口各有100张票。解决方案有我们可以用静态关键字修饰ticket 或者用单例模式解决。但这都不是最佳的因为这种实现继承方式本身有些不好的地方,一、类不符合继承思想,什么时候用继承?当类是某个类的一种,符合is a 关系,而为了实现继承而去继承一个类不符合这个思想。二、Java不支持多继承,如果某个类是子类,那不是无法实现多线程。三、耦合性问题,像这个例子每个线程都包含资源,结果每个线程都用自己的资源,不符合本意,当然是可以修改的,但即使修改了线程与资源耦合性也太高,没有实现线程与资源分离,这与线程思想(轻装上阵,最小的执行单元)不符。
因此推出第二种继承方式,将资源与线程分离,独立封装,更符合oo思想,那就是实现Runnable接口,将实现的类的示例作为参数传给Thread(查阅API文档可知有个Thread(Runnable)构造方法)
class SaleTicket implements Runnable{//实现Runnable接口
private int ticket=100;
public void run(){//重写run方法
while(true){
if(ticket>0)
System.out.println(Thread.currentThread().getName()+"....."+ticket--);
}
}
}
public class TicketDemo {
public static void main(String[] args) {
//给多个线程传共享资源
SaleTicket st=new SaleTicket();
Thread t1=new Thread(st);
t1.start();
Thread t2=new Thread(st);
t2.start();
Thread t3=new Thread(st);
t3.start();
Thread t4=new Thread(st);
t4.start();
}
}
上述的多线程存在安全问题,因为你对共享资源进行了并发操作,举个例子,你银行卡有500块,又存了200,又花了200,应该是不变的,但如果同时并发操作,一个算300,另一个算700,这两个数据互相覆盖,无论是300还是700都是错的,要出大问题。这个例子也是,在票数为一时,让一个线程进入了减一操作,但还没减一时,又切换了线程,此时的值还是一,于是多个线程进入ticket--操作,有可能结果为负数。可以修改下程序,停顿一会,就能看出效果
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"....."+ticket--);
}
因此,我们应该保证对于共享数据同一时间段只有一个线程进行操作,于是引入同步代码块synchronized(对象){需要被同步的代码块;}。就好像一个锁,进入之后把它开关关上,阻止后面的线程进入,等离开后将开关打开允许后续进入。这就是同步的锁机制,不过要重复判断,降低了效率
synchronized(obj){//Object obj=new Object();
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"....."+ticket--);
}
}
}
不直接new Object(),因为这样锁不住,每次进去都新建一个对象,拿的是不一样的锁。加同步代码块的原则是寻找共享数据,在操作共享数据的位置加同步代码块。
要同步还可以用同步函数,加synchronized关键字,那么同步函数用的什么锁呢?其实就是this,我们可以验证一下
class SaleTicket implements Runnable {// 实现Runnable接口
private int ticket = 100;
boolean flag = true;
public void run() {// 重写run方法
if (flag)
while (true) {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "..block..." + ticket--);
}
}
}
else {
while (true)
sale();
}
}
public synchronized void sale() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "....func..." + ticket--);
}
}
}
}
public class TicketDemo {
public static void main(String[] args) {
SaleTicket st = new SaleTicket();
Thread t1 = new Thread(st);
t1.start();
Thread t2 = new Thread(st);
st.flag = false;
t2.start();
}
}
由代码可知如果在这时候输出结果还能保持正确,就说明它们持有的锁是一致的,否则无法达到同步效果。但是运行之后却发现问题了
发现运行结果全都是线程0,执行同步函数,之所以这样是因为线程t1(Thread-0)开启后立刻切换到主线程执行,主线程将flag改为false,所以线程执行的是同步函数。修改如下:
public class TicketDemo {
public static void main(String[] args) throws InterruptedException {
SaleTicket st = new SaleTicket();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
Thread.sleep(10);//主线程休眠,执行权给t1,防止主线程得到直接改了flag
st.flag = false;
t2.start();
}
}
结果大家去试试吧。接下来引入新问题,同步函数要是被static修饰呢?那就没有this指针。一个函数是被对象调用的,如果是静态可以被类直接调用。有点类似这边的锁为类名.class(字节码对象,静态方法随类加载,没有该类的对象但有该类的字节码文件对象。)。同样可以验证一下:
class SaleTicket implements Runnable {// 实现Runnable接口
private static int ticket = 100;
boolean flag = true;
public void run() {// 重写run方法
if (flag)
while (true) {
synchronized (SaleTicket.class) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "..block..." + ticket--);
}
}
}
else {
while (true)
sale();
}
}
public static synchronized void sale() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "....func..." + ticket--);
}
}
}
}
截屏大小有限看不出,自己去试试吧,结果应该是可以同步的
同步升级:生产者消费者问题
class Resource{
private String name;
private int count=1;
public void set(String name){
this.name=name+"...."+count++;
System.out.println(Thread.currentThread().getName()+"。。。。生产者。。。"+this.name);
}
public void get(){
System.out.println(Thread.currentThread().getName()+"。。。。消费者。。。"+this.name);
}
}
class Producer implements Runnable{
private Resource res;
public Producer(Resource res){
this.res=res;
}
public void run(){
while(true)
res.set("面包");
}
}
class Consumer implements Runnable{
private Resource res;
public Consumer(Resource res){
this.res=res;
}
public void run(){
while(true)
res.get();
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
Resource res=new Resource();
Producer pro=new Producer(res);
Consumer con=new Consumer(res);
new Thread(pro).start();
new Thread(con).start();
}
}
在还没加同步前会出现未生产先消费和重复消费的问题,这是由于生产者还没来得及输出就切换线程,消费者多次得到执行权,但是加了同步之后,消费者生产者操作都封闭了。生产者拼命加一,输出,消费者就拼命输出相同的面包(连续重复消费)
这很明显不是我们想要的效果,我们要的是生产一个消费一个交替协调。我们可以使用等待--唤醒机制,应该生产的时候却去消费就让那个线程wait(),让生产线程顺利生产,然后去唤醒(notify)被阻塞的线程。具体我们可以看代码:
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name) {
if (flag)// 为true说明生产者已经生产了,要让生产者去等待,消费者去消费
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name = name + "...." + count++;
System.out.println(Thread.currentThread().getName() + "。。。。生产者。。。" + this.name);
flag = true;// 生产了要消费,置为true
notify();// 唤醒等待的线程
}
public synchronized void get() {
if (!flag)// 没生产不能消费,等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "。。。。消费者。。。" + this.name);
flag = false;// 消费完了,去生产
this.notify();// 唤醒等待的线程
}
}
wait()和notify()方法要指明相应的锁对象,默认为this(同步函数)。唤醒也是有针对的唤醒,比如locka.notify()唤醒的一定是locka.wait()的等待线程。notify()是唤醒一个,notifyAll()是唤醒所有。
问题升级:多生产多消费
public class ProducerConsumerDemo {
public static void main(String[] args) {
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
new Thread(pro).start();
new Thread(con).start();
new Thread(pro).start();
new Thread(con).start();
}
}
出现问题:重复生产重复消费。原因:唤醒的不确定性,比如生产者线程可能唤醒了己方原本等待的生产者线程,生产者线程继续执行生产覆盖了还未被消费的面包,这就是重复生产。重复消费也是这样原因。因此,被唤醒的线程应该同样去判断标记flag。代码修改只需把两处if判断改成while循环。结果却很尴尬的发现死锁了
还是上述的原因,唤醒己方线程,判断标记后wait(),导致所有线程都进入等待状态,就陷入死锁状态。
解决方案一:用notifyAll()全部唤醒,即使本方再一次抢到也会进入wait()状态,让另一方执行。用notifyAll()替换notify()就修改好代码了,测试结果发现是理想的结果。但这种效率低。
解决方案二:根据先前说的针对性唤醒我们可以用locka.wait()....lockb.notify() lockb.wait()......locka.notify()模式去唤醒对方线程而不是己方,但这个程序同步嵌套明显容易发生死锁。我们需要引入新东西,那就是同步的那套设备全都升级了,我们有新的方法去替代旧的。锁不再是任意对象,而是被封装成对象Lock,更符合面向对象思想。
wait(),notify(),notifyAll()也升级成await(),signal()和signalAll(),不再直接关联在锁上,而是把这些监视器方法封装到Condition对象中,Condition通过lock.newConfition()获得
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
Lock myLock = new ReentrantLock();
Condition con = myLock.newCondition();
public void set(String name) {
myLock.lock();
try {
while (flag)// 为true说明生产者已经生产了,要让生产者去等待,消费者去消费
try {
con.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name = name + "...." + count++;
System.out.println(Thread.currentThread().getName() + "。。。。生产者。。。" + this.name);
flag = true;// 生产了要消费,置为true
con.signalAll();// 唤醒等待的线程
} finally {
myLock.unlock();
}
}
public void get() {
myLock.lock();
try {
while (!flag)// 没生产不能消费,等待
try {
con.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "。。。。消费者。。。" + this.name);
flag = false;// 消费完了,去生产
con.signalAll();// 唤醒等待的线程
} finally {
myLock.unlock();
}
}
}
上面的代码效果是与先前代码效果完全一样。而且还把方法从锁转移到Condition对象,就没有先前的顾虑,我们创建多个Condition,代码如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
Lock myLock = new ReentrantLock();
Condition producer_con = myLock.newCondition();
Condition consumer_con = myLock.newCondition();
public void set(String name) {
myLock.lock();
try {
while (flag)// 为true说明生产者已经生产了,要让生产者去等待,消费者去消费
try {
producer_con.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.name = name + "...." + count++;
System.out.println(Thread.currentThread().getName() + "。。。。生产者。。。" + this.name);
flag = true;// 生产了要消费,置为true
consumer_con.signal();// 唤醒等待的线程
} finally {
myLock.unlock();
}
}
public void get() {
myLock.lock();
try {
while (!flag)// 没生产不能消费,等待
try {
consumer_con.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "。。。。消费者。。。" + this.name);
flag = false;// 消费完了,去生产
producer_con.signal();// 唤醒等待的生产者线程
} finally {
myLock.unlock();
}
}
}
这就是好的解决方案。不过我们可以发现生产一个面包就消费一个,明显不符合实际情况,所以代码可以进一步升级,在API文档Condition示例恰好给出了代码
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}